diff --git a/Apps/Sandcastle/gallery/Globe Materials.html b/Apps/Sandcastle/gallery/Globe Materials.html
new file mode 100644
index 000000000000..b6b8dbf4c842
--- /dev/null
+++ b/Apps/Sandcastle/gallery/Globe Materials.html
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+
+Loading...
+
+
+
+
diff --git a/Apps/Sandcastle/gallery/Globe Materials.jpg b/Apps/Sandcastle/gallery/Globe Materials.jpg
new file mode 100644
index 000000000000..d187a6262154
Binary files /dev/null and b/Apps/Sandcastle/gallery/Globe Materials.jpg differ
diff --git a/CHANGES.md b/CHANGES.md
index fa2be9350acc..ab1df1fddd90 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,7 @@ Change Log
### 1.40 - 2017-12-01
+* Added `Globe.material` to apply materials to the globe/terrain for shading such as height- or slope-based color ramps. See the new [Sandcastle example](https://cesiumjs.org/Cesium/Apps/Sandcastle/?src=Globe%20Materials.html&label=Showcases). [#5919](https://github.com/AnalyticalGraphicsInc/cesium/pull/5919/files)
* Added ability to support touch event in Imagery Layers Split demo application. [#5948](https://github.com/AnalyticalGraphicsInc/cesium/pull/5948)
### 1.39 - 2017-11-01
diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js
index 8a351098c7b9..0bec069078be 100644
--- a/Source/Scene/Globe.js
+++ b/Source/Scene/Globe.js
@@ -24,6 +24,7 @@ define([
'./GlobeSurfaceShaderSet',
'./GlobeSurfaceTileProvider',
'./ImageryLayerCollection',
+ './Material',
'./QuadtreePrimitive',
'./SceneMode',
'./ShadowMode'
@@ -53,6 +54,7 @@ define([
GlobeSurfaceShaderSet,
GlobeSurfaceTileProvider,
ImageryLayerCollection,
+ Material,
QuadtreePrimitive,
SceneMode,
ShadowMode) {
@@ -79,14 +81,7 @@ define([
this._imageryLayerCollection = imageryLayerCollection;
this._surfaceShaderSet = new GlobeSurfaceShaderSet();
-
- this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
- sources : [GroundAtmosphere, GlobeVS]
- });
-
- this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
- sources : [GlobeFS]
- });
+ this._material = undefined;
this._surface = new QuadtreePrimitive({
tileProvider : new GlobeSurfaceTileProvider({
@@ -99,6 +94,8 @@ define([
this._terrainProvider = terrainProvider;
this._terrainProviderChanged = new Event();
+ makeShadersDirty(this);
+
/**
* Determines if the globe will be shown.
*
@@ -276,9 +273,52 @@ define([
get: function() {
return this._surface.tileLoadProgressEvent;
}
+ },
+
+ /**
+ * Gets or sets the material appearance of the Globe. This can be one of several built-in {@link Material} objects or a custom material, scripted with
+ * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
+ * @memberof Globe.prototype
+ * @type {Material}
+ */
+ material: {
+ get: function() {
+ return this._material;
+ },
+ set: function(material) {
+ if (this._material !== material) {
+ this._material = material;
+ makeShadersDirty(this);
+ }
+ }
}
});
+ function makeShadersDirty(globe) {
+ var defines = [];
+
+ var fragmentSources = [];
+ if (defined(globe._material)) {
+ fragmentSources.push(globe._material.shaderSource);
+ defines.push('APPLY_MATERIAL');
+ globe._surface._tileProvider.uniformMap = globe._material._uniforms;
+ } else {
+ globe._surface._tileProvider.uniformMap = undefined;
+ }
+ fragmentSources.push(GlobeFS);
+
+ globe._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
+ sources : [GroundAtmosphere, GlobeVS],
+ defines : defines
+ });
+
+ globe._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
+ sources : fragmentSources,
+ defines : defines
+ });
+ globe._surfaceShaderSet.material = globe._material;
+ }
+
function createComparePickTileFunction(rayOrigin) {
return function(a, b) {
var aDist = BoundingSphere.distanceSquaredTo(a.pickBoundingSphere, rayOrigin);
@@ -525,6 +565,10 @@ define([
return;
}
+ if (defined(this._material)) {
+ this._material.update(frameState.context);
+ }
+
var surface = this._surface;
var pass = frameState.passes;
diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js
index 2442df1a5efb..56e4f586b452 100644
--- a/Source/Scene/GlobeSurfaceShaderSet.js
+++ b/Source/Scene/GlobeSurfaceShaderSet.js
@@ -12,9 +12,10 @@ define([
SceneMode) {
'use strict';
- function GlobeSurfaceShader(numberOfDayTextures, flags, shaderProgram) {
+ function GlobeSurfaceShader(numberOfDayTextures, flags, material, shaderProgram) {
this.numberOfDayTextures = numberOfDayTextures;
this.flags = flags;
+ this.material = material;
this.shaderProgram = shaderProgram;
}
@@ -30,6 +31,8 @@ define([
this._shadersByTexturesFlags = [];
this._pickShaderPrograms = [];
+
+ this.material = undefined;
}
function getPositionMode(sceneMode) {
@@ -92,7 +95,8 @@ define([
var surfaceShader = surfaceTile.surfaceShader;
if (defined(surfaceShader) &&
surfaceShader.numberOfDayTextures === numberOfDayTextures &&
- surfaceShader.flags === flags) {
+ surfaceShader.flags === flags &&
+ surfaceShader.material === this.material) {
return surfaceShader.shaderProgram;
}
@@ -104,7 +108,7 @@ define([
}
surfaceShader = shadersByFlags[flags];
- if (!defined(surfaceShader)) {
+ if (!defined(surfaceShader) || surfaceShader.material !== this.material) {
// Cache miss - we've never seen this combination of numberOfDayTextures and flags before.
var vs = this.baseVertexShaderSource.clone();
var fs = this.baseFragmentShaderSource.clone();
@@ -199,7 +203,7 @@ define([
attributeLocations : terrainEncoding.getAttributeLocations()
});
- surfaceShader = shadersByFlags[flags] = new GlobeSurfaceShader(numberOfDayTextures, flags, shader);
+ surfaceShader = shadersByFlags[flags] = new GlobeSurfaceShader(numberOfDayTextures, flags, this.material, shader);
}
surfaceTile.surfaceShader = surfaceShader;
diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js
index 2dc19b73ac65..83d188d4c5b5 100644
--- a/Source/Scene/GlobeSurfaceTileProvider.js
+++ b/Source/Scene/GlobeSurfaceTileProvider.js
@@ -6,6 +6,7 @@ define([
'../Core/Cartesian4',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
+ '../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
@@ -50,6 +51,7 @@ define([
Cartesian4,
Color,
ColorGeometryInstanceAttribute,
+ combine,
defaultValue,
defined,
defineProperties,
@@ -1262,6 +1264,10 @@ define([
uniformMapProperties.minMaxHeight.y = encoding.maximumHeight;
Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias);
+ if (defined(tileProvider.uniformMap)) {
+ uniformMap = combine(uniformMap, tileProvider.uniformMap);
+ }
+
command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog);
command.castShadows = castShadows;
command.receiveShadows = receiveShadows;
diff --git a/Source/Scene/Material.js b/Source/Scene/Material.js
index 5f5f53e2b430..f1af6eb48a25 100644
--- a/Source/Scene/Material.js
+++ b/Source/Scene/Material.js
@@ -21,6 +21,8 @@ define([
'../Shaders/Materials/BumpMapMaterial',
'../Shaders/Materials/CheckerboardMaterial',
'../Shaders/Materials/DotMaterial',
+ '../Shaders/Materials/ElevationContourMaterial',
+ '../Shaders/Materials/ElevationRampMaterial',
'../Shaders/Materials/FadeMaterial',
'../Shaders/Materials/GridMaterial',
'../Shaders/Materials/NormalMapMaterial',
@@ -29,6 +31,7 @@ define([
'../Shaders/Materials/PolylineGlowMaterial',
'../Shaders/Materials/PolylineOutlineMaterial',
'../Shaders/Materials/RimLightingMaterial',
+ '../Shaders/Materials/SlopeRampMaterial',
'../Shaders/Materials/StripeMaterial',
'../Shaders/Materials/Water',
'../ThirdParty/when'
@@ -55,6 +58,8 @@ define([
BumpMapMaterial,
CheckerboardMaterial,
DotMaterial,
+ ElevationContourMaterial,
+ ElevationRampMaterial,
FadeMaterial,
GridMaterial,
NormalMapMaterial,
@@ -63,6 +68,7 @@ define([
PolylineGlowMaterial,
PolylineOutlineMaterial,
RimLightingMaterial,
+ SlopeRampMaterial,
StripeMaterial,
WaterMaterial,
when) {
@@ -226,6 +232,23 @@ define([
* outlineColor
: diffuse color and alpha for the outline.
* outlineWidth
: width of the outline in pixels.
*
+ * ElevationContour
+ *
+ * color
: color and alpha for the contour line.
+ * spacing
: spacing for contour lines in meters.
+ * width
: Number specifying the width of the grid lines in pixels.
+ *
+ * ElevationRamp
+ *
+ * image
: color ramp image to use for coloring the terrain.
+ * minimumHeight
: minimum height for the ramp.
+ * maximumHeight
: maximum height for the ramp.
+ *
+ * SlopeRamp
+ *
+ * image
: color ramp image to use for coloring the terrain.
+ *
+ *
*
*
*
@@ -1470,5 +1493,60 @@ define([
}
});
+ /**
+ * Gets the name of the elevation contour material.
+ * @type {String}
+ * @readonly
+ */
+ Material.ElevationContourType = 'ElevationContour';
+ Material._materialCache.addMaterial(Material.ElevationContourType, {
+ fabric : {
+ type : Material.ElevationContourType,
+ uniforms : {
+ spacing: 100.0,
+ color: new Color(1.0, 0.0, 0.0, 1.0),
+ width: 1.0
+ },
+ source : ElevationContourMaterial
+ },
+ translucent : false
+ });
+
+ /**
+ * Gets the name of the elevation contour material.
+ * @type {String}
+ * @readonly
+ */
+ Material.ElevationRampType = 'ElevationRamp';
+ Material._materialCache.addMaterial(Material.ElevationRampType, {
+ fabric : {
+ type : Material.ElevationRampType,
+ uniforms : {
+ image: Material.DefaultImageId,
+ minimumHeight: 0.0,
+ maximumHeight: 10000.0
+ },
+ source : ElevationRampMaterial
+ },
+ translucent : false
+ });
+
+ /**
+ * Gets the name of the slope ramp material.
+ * @type {String}
+ * @readonly
+ */
+ Material.SlopeRampMaterialType = 'SlopeRamp';
+ Material._materialCache.addMaterial(Material.SlopeRampMaterialType, {
+ fabric : {
+ type : Material.SlopeRampMaterialType,
+ uniforms : {
+ image: Material.DefaultImageId
+ },
+ source : SlopeRampMaterial
+ },
+ translucent : false
+ });
+
return Material;
});
diff --git a/Source/Shaders/Builtin/Structs/materialInput.glsl b/Source/Shaders/Builtin/Structs/materialInput.glsl
index 4329dda238c7..ac7be862b44a 100644
--- a/Source/Shaders/Builtin/Structs/materialInput.glsl
+++ b/Source/Shaders/Builtin/Structs/materialInput.glsl
@@ -10,6 +10,8 @@
* @property {vec3} normalEC Unperturbed surface normal in eye coordinates.
* @property {mat3} tangentToEyeMatrix Matrix for converting a tangent space normal to eye space.
* @property {vec3} positionToEyeEC Vector from the fragment to the eye in eye coordinates. The magnitude is the distance in meters from the fragment to the eye.
+ * @property {float} height The height of the terrain in meters above or below the WGS84 ellipsoid. Only available for globe materials.
+ * @property {float} slope The slope of the terrain normalized from 0 to 1. 0 is completely vertical, 1 is completely flat. Only available for globe materials.
*/
struct czm_materialInput
{
@@ -19,4 +21,6 @@ struct czm_materialInput
vec3 normalEC;
mat3 tangentToEyeMatrix;
vec3 positionToEyeEC;
+ float height;
+ float slope;
};
diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl
index 998daae62d15..fe4c9c82b6d4 100644
--- a/Source/Shaders/GlobeFS.glsl
+++ b/Source/Shaders/GlobeFS.glsl
@@ -62,6 +62,11 @@ varying vec3 v_textureCoordinates;
varying vec3 v_normalMC;
varying vec3 v_normalEC;
+#ifdef APPLY_MATERIAL
+varying float v_height;
+varying float v_slope;
+#endif
+
#ifdef FOG
varying float v_distance;
varying vec3 v_rayleighColor;
@@ -184,6 +189,16 @@ void main()
}
#endif
+#ifdef APPLY_MATERIAL
+ czm_materialInput materialInput;
+ materialInput.st = v_textureCoordinates.st;
+ materialInput.normalEC = normalize(v_normalEC);
+ materialInput.slope = v_slope;
+ materialInput.height = v_height;
+ czm_material material = czm_getMaterial(materialInput);
+ color.xyz = mix(color.xyz, material.diffuse, material.alpha);
+#endif
+
#ifdef ENABLE_VERTEX_LIGHTING
float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalize(v_normalEC)) * 0.9 + 0.3, 0.0, 1.0);
vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a);
diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl
index a9cbc9ed2382..127915f7b49c 100644
--- a/Source/Shaders/GlobeVS.glsl
+++ b/Source/Shaders/GlobeVS.glsl
@@ -22,6 +22,11 @@ varying vec3 v_textureCoordinates;
varying vec3 v_normalMC;
varying vec3 v_normalEC;
+#ifdef APPLY_MATERIAL
+varying float v_slope;
+varying float v_height;
+#endif
+
#ifdef FOG
varying float v_distance;
varying vec3 v_mieColor;
@@ -154,7 +159,8 @@ void main()
#if defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)
v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;
v_positionMC = position3DWC; // position in model coordinates
- v_normalMC = czm_octDecode(encodedNormal);
+ vec3 normalMC = czm_octDecode(encodedNormal);
+ v_normalMC = normalMC;
v_normalEC = czm_normal3D * v_normalMC;
#elif defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(GENERATE_POSITION)
v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;
@@ -167,4 +173,11 @@ void main()
v_rayleighColor = atmosColor.rayleigh;
v_distance = length((czm_modelView3D * vec4(position3DWC, 1.0)).xyz);
#endif
+
+#ifdef APPLY_MATERIAL
+ vec3 finalNormal = normalMC;
+ vec3 ellipsoidNormal = normalize(position3DWC.xyz);
+ v_slope = abs(dot(ellipsoidNormal, finalNormal));
+ v_height = height;
+#endif
}
diff --git a/Source/Shaders/Materials/ElevationContourMaterial.glsl b/Source/Shaders/Materials/ElevationContourMaterial.glsl
new file mode 100644
index 000000000000..1634891eb44c
--- /dev/null
+++ b/Source/Shaders/Materials/ElevationContourMaterial.glsl
@@ -0,0 +1,27 @@
+#ifdef GL_OES_standard_derivatives
+ #extension GL_OES_standard_derivatives : enable
+#endif
+
+uniform vec4 color;
+uniform float spacing;
+uniform float width;
+
+czm_material czm_getMaterial(czm_materialInput materialInput)
+{
+ czm_material material = czm_getDefaultMaterial(materialInput);
+
+ float distanceToContour = mod(materialInput.height, spacing);
+
+#ifdef GL_OES_standard_derivatives
+ float dxc = abs(dFdx(materialInput.height));
+ float dyc = abs(dFdy(materialInput.height));
+ float dF = max(dxc, dyc) * width;
+ material.alpha = (distanceToContour < dF) ? 1.0 : 0.0;
+#else
+ material.alpha = (distanceToContour < (czm_resolutionScale * width)) ? 1.0 : 0.0;
+#endif
+
+ material.diffuse = color.rgb;
+
+ return material;
+}
diff --git a/Source/Shaders/Materials/ElevationRampMaterial.glsl b/Source/Shaders/Materials/ElevationRampMaterial.glsl
new file mode 100644
index 000000000000..36c017422175
--- /dev/null
+++ b/Source/Shaders/Materials/ElevationRampMaterial.glsl
@@ -0,0 +1,13 @@
+uniform sampler2D image;
+uniform float minimumHeight;
+uniform float maximumHeight;
+
+czm_material czm_getMaterial(czm_materialInput materialInput)
+{
+ czm_material material = czm_getDefaultMaterial(materialInput);
+ float scaledHeight = clamp((materialInput.height - minimumHeight) / (maximumHeight - minimumHeight), 0.0, 1.0);
+ vec4 rampColor = texture2D(image, vec2(scaledHeight, 0.5));
+ material.diffuse = rampColor.rgb;
+ material.alpha = rampColor.a;
+ return material;
+}
diff --git a/Source/Shaders/Materials/SlopeRampMaterial.glsl b/Source/Shaders/Materials/SlopeRampMaterial.glsl
new file mode 100644
index 000000000000..14e30a9086bb
--- /dev/null
+++ b/Source/Shaders/Materials/SlopeRampMaterial.glsl
@@ -0,0 +1,10 @@
+uniform sampler2D image;
+
+czm_material czm_getMaterial(czm_materialInput materialInput)
+{
+ czm_material material = czm_getDefaultMaterial(materialInput);
+ vec4 rampColor = texture2D(image, vec2(materialInput.slope, 0.5));
+ material.diffuse = rampColor.rgb;
+ material.alpha = rampColor.a;
+ return material;
+}
diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js
index f8075de3b874..83f83613c7da 100644
--- a/Specs/Scene/SceneSpec.js
+++ b/Specs/Scene/SceneSpec.js
@@ -28,6 +28,7 @@ defineSuite([
'Scene/EllipsoidSurfaceAppearance',
'Scene/FrameState',
'Scene/Globe',
+ 'Scene/Material',
'Scene/Primitive',
'Scene/PrimitiveCollection',
'Scene/Scene',
@@ -69,6 +70,7 @@ defineSuite([
EllipsoidSurfaceAppearance,
FrameState,
Globe,
+ Material,
Primitive,
PrimitiveCollection,
Scene,
@@ -506,6 +508,66 @@ defineSuite([
});
});
+ it('renders a globe with an ElevationContour', function() {
+ var s = createScene();
+
+ s.globe = new Globe(Ellipsoid.UNIT_SPHERE);
+ s.globe.material = Material.fromType('ElevationContour');
+ s.camera.position = new Cartesian3(1.02, 0.0, 0.0);
+ s.camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
+ s.camera.direction = Cartesian3.negate(Cartesian3.normalize(s.camera.position, new Cartesian3()), new Cartesian3());
+
+ // To avoid Jasmine's spec has no expectations error
+ expect(true).toEqual(true);
+
+ return expect(s).toRenderAndCall(function() {
+ return pollToPromise(function() {
+ render(s.frameState, s.globe);
+ return !jasmine.matchersUtil.equals(s._context.readPixels(), [0, 0, 0, 0]);
+ });
+ });
+ });
+
+ it('renders a globe with a SlopeRamp', function() {
+ var s = createScene();
+
+ s.globe = new Globe(Ellipsoid.UNIT_SPHERE);
+ s.globe.material = Material.fromType('SlopeRamp');
+ s.camera.position = new Cartesian3(1.02, 0.0, 0.0);
+ s.camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
+ s.camera.direction = Cartesian3.negate(Cartesian3.normalize(s.camera.position, new Cartesian3()), new Cartesian3());
+
+ // To avoid Jasmine's spec has no expectations error
+ expect(true).toEqual(true);
+
+ return expect(s).toRenderAndCall(function() {
+ return pollToPromise(function() {
+ render(s.frameState, s.globe);
+ return !jasmine.matchersUtil.equals(s._context.readPixels(), [0, 0, 0, 0]);
+ });
+ });
+ });
+
+ it('renders a globe with a ElevationRamp', function() {
+ var s = createScene();
+
+ s.globe = new Globe(Ellipsoid.UNIT_SPHERE);
+ s.globe.material = Material.fromType('ElevationRamp');
+ s.camera.position = new Cartesian3(1.02, 0.0, 0.0);
+ s.camera.up = Cartesian3.clone(Cartesian3.UNIT_Z);
+ s.camera.direction = Cartesian3.negate(Cartesian3.normalize(s.camera.position, new Cartesian3()), new Cartesian3());
+
+ // To avoid Jasmine's spec has no expectations error
+ expect(true).toEqual(true);
+
+ return expect(s).toRenderAndCall(function() {
+ return pollToPromise(function() {
+ render(s.frameState, s.globe);
+ return !jasmine.matchersUtil.equals(s._context.readPixels(), [0, 0, 0, 0]);
+ });
+ });
+ });
+
it('renders with multipass OIT if MRT is available', function() {
if (scene.context.drawBuffers) {
var s = createScene();
@@ -1167,4 +1229,16 @@ defineSuite([
expect(scene.terrainProviderChanged).toBeUndefined();
});
+
+ it('Sets material', function() {
+ var scene = createScene();
+ var globe = scene.globe = new Globe(Ellipsoid.UNIT_SPHERE);
+ var material = Material.fromType('ElevationContour');
+ globe.material = material;
+ expect(globe.material).toBe(material);
+
+ globe.material = undefined;
+ expect(globe.material).toBeUndefined();
+ });
+
}, 'WebGL');