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...

+
+
+
+ + + +
+
+
+ +
+
+ Spacing m +
+
+ Line Width px +
+
+ +
+
+
+ + + 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
  • + * + *
  • ElevationRamp
  • + * + *
  • SlopeRamp
  • + * + * * * * @@ -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');