123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587 |
- import BoundingRectangle from '../Core/BoundingRectangle.js';
- import BoundingSphere from '../Core/BoundingSphere.js';
- import BoxOutlineGeometry from '../Core/BoxOutlineGeometry.js';
- import Cartesian2 from '../Core/Cartesian2.js';
- import Cartesian3 from '../Core/Cartesian3.js';
- import Cartesian4 from '../Core/Cartesian4.js';
- import Cartographic from '../Core/Cartographic.js';
- import clone from '../Core/clone.js';
- import Color from '../Core/Color.js';
- import ColorGeometryInstanceAttribute from '../Core/ColorGeometryInstanceAttribute.js';
- import combine from '../Core/combine.js';
- import CullingVolume from '../Core/CullingVolume.js';
- import defaultValue from '../Core/defaultValue.js';
- import defined from '../Core/defined.js';
- import defineProperties from '../Core/defineProperties.js';
- import destroyObject from '../Core/destroyObject.js';
- import DeveloperError from '../Core/DeveloperError.js';
- import FeatureDetection from '../Core/FeatureDetection.js';
- import GeometryInstance from '../Core/GeometryInstance.js';
- import Intersect from '../Core/Intersect.js';
- import CesiumMath from '../Core/Math.js';
- import Matrix4 from '../Core/Matrix4.js';
- import OrthographicOffCenterFrustum from '../Core/OrthographicOffCenterFrustum.js';
- import PerspectiveFrustum from '../Core/PerspectiveFrustum.js';
- import PixelFormat from '../Core/PixelFormat.js';
- import Quaternion from '../Core/Quaternion.js';
- import SphereOutlineGeometry from '../Core/SphereOutlineGeometry.js';
- import WebGLConstants from '../Core/WebGLConstants.js';
- import ClearCommand from '../Renderer/ClearCommand.js';
- import ContextLimits from '../Renderer/ContextLimits.js';
- import CubeMap from '../Renderer/CubeMap.js';
- import DrawCommand from '../Renderer/DrawCommand.js';
- import Framebuffer from '../Renderer/Framebuffer.js';
- import Pass from '../Renderer/Pass.js';
- import PassState from '../Renderer/PassState.js';
- import PixelDatatype from '../Renderer/PixelDatatype.js';
- import Renderbuffer from '../Renderer/Renderbuffer.js';
- import RenderbufferFormat from '../Renderer/RenderbufferFormat.js';
- import RenderState from '../Renderer/RenderState.js';
- import Sampler from '../Renderer/Sampler.js';
- import Texture from '../Renderer/Texture.js';
- import TextureMagnificationFilter from '../Renderer/TextureMagnificationFilter.js';
- import TextureMinificationFilter from '../Renderer/TextureMinificationFilter.js';
- import TextureWrap from '../Renderer/TextureWrap.js';
- import Camera from './Camera.js';
- import CullFace from './CullFace.js';
- import DebugCameraPrimitive from './DebugCameraPrimitive.js';
- import PerInstanceColorAppearance from './PerInstanceColorAppearance.js';
- import Primitive from './Primitive.js';
- import ShadowMapShader from './ShadowMapShader.js';
- /**
- * Use {@link Viewer#shadowMap} to get the scene's shadow map originating from the sun. Do not construct this directly.
- *
- * <p>
- * The normalOffset bias pushes the shadows forward slightly, and may be disabled
- * for applications that require ultra precise shadows.
- * </p>
- *
- * @alias ShadowMap
- * @internalConstructor
- * @class
- *
- * @param {Object} options An object containing the following properties:
- * @param {Camera} options.lightCamera A camera representing the light source.
- * @param {Boolean} [options.enabled=true] Whether the shadow map is enabled.
- * @param {Boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
- * @param {Boolean} [options.pointLightRadius=100.0] Radius of the point light.
- * @param {Boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
- * @param {Number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
- * @param {Number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
- * @param {Number} [options.size=2048] The width and height, in pixels, of each shadow map.
- * @param {Boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
- * @param {Number} [options.darkness=0.3] The shadow darkness.
- * @param {Boolean} [options.normalOffset=true] Whether a normal bias is applied to shadows.
- *
- * @exception {DeveloperError} Only one or four cascades are supported.
- *
- * @demo {@link https://sandcastle.cesium.com/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}
- */
- function ShadowMap(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- // options.context is an undocumented option
- var context = options.context;
- //>>includeStart('debug', pragmas.debug);
- if (!defined(context)) {
- throw new DeveloperError('context is required.');
- }
- if (!defined(options.lightCamera)) {
- throw new DeveloperError('lightCamera is required.');
- }
- if (defined(options.numberOfCascades) && ((options.numberOfCascades !== 1) && (options.numberOfCascades !== 4))) {
- throw new DeveloperError('Only one or four cascades are supported.');
- }
- //>>includeEnd('debug');
- this._enabled = defaultValue(options.enabled, true);
- this._softShadows = defaultValue(options.softShadows, false);
- this._normalOffset = defaultValue(options.normalOffset, true);
- this.dirty = true;
- /**
- * Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
- * purposes should set this to false so as not to affect scene rendering.
- *
- * @private
- */
- this.fromLightSource = defaultValue(options.fromLightSource, true);
- /**
- * Determines the darkness of the shadows.
- *
- * @type {Number}
- * @default 0.3
- */
- this.darkness = defaultValue(options.darkness, 0.3);
- this._darkness = this.darkness;
- /**
- * Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
- *
- * @type {Number}
- * @default 5000.0
- */
- this.maximumDistance = defaultValue(options.maximumDistance, 5000.0);
- this._outOfView = false;
- this._outOfViewPrevious = false;
- this._needsUpdate = true;
- // In IE11 and Edge polygon offset is not functional.
- // TODO : Also disabled for instances of Firefox and Chrome running ANGLE that do not support depth textures.
- // Re-enable once https://github.com/AnalyticalGraphicsInc/cesium/issues/4560 is resolved.
- var polygonOffsetSupported = true;
- if (FeatureDetection.isInternetExplorer() || FeatureDetection.isEdge() || ((FeatureDetection.isChrome() || FeatureDetection.isFirefox()) && FeatureDetection.isWindows() && !context.depthTexture)) {
- polygonOffsetSupported = false;
- }
- this._polygonOffsetSupported = polygonOffsetSupported;
- this._terrainBias = {
- polygonOffset : polygonOffsetSupported,
- polygonOffsetFactor : 1.1,
- polygonOffsetUnits : 4.0,
- normalOffset : this._normalOffset,
- normalOffsetScale : 0.5,
- normalShading : true,
- normalShadingSmooth : 0.3,
- depthBias : 0.0001
- };
- this._primitiveBias = {
- polygonOffset : polygonOffsetSupported,
- polygonOffsetFactor : 1.1,
- polygonOffsetUnits : 4.0,
- normalOffset : this._normalOffset,
- normalOffsetScale : 0.1,
- normalShading : true,
- normalShadingSmooth : 0.05,
- depthBias : 0.00002
- };
- this._pointBias = {
- polygonOffset : false,
- polygonOffsetFactor : 1.1,
- polygonOffsetUnits : 4.0,
- normalOffset : this._normalOffset,
- normalOffsetScale : 0.0,
- normalShading : true,
- normalShadingSmooth : 0.1,
- depthBias : 0.0005
- };
- // Framebuffer resources
- this._depthAttachment = undefined;
- this._colorAttachment = undefined;
- // Uniforms
- this._shadowMapMatrix = new Matrix4();
- this._shadowMapTexture = undefined;
- this._lightDirectionEC = new Cartesian3();
- this._lightPositionEC = new Cartesian4();
- this._distance = 0.0;
- this._lightCamera = options.lightCamera;
- this._shadowMapCamera = new ShadowMapCamera();
- this._shadowMapCullingVolume = undefined;
- this._sceneCamera = undefined;
- this._boundingSphere = new BoundingSphere();
- this._isPointLight = defaultValue(options.isPointLight, false);
- this._pointLightRadius = defaultValue(options.pointLightRadius, 100.0);
- this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true);
- this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4);
- this._fitNearFar = true;
- this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];
- this._textureSize = new Cartesian2();
- this._isSpotLight = false;
- if (this._cascadesEnabled) {
- // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
- this._shadowMapCamera.frustum = new OrthographicOffCenterFrustum();
- } else if (defined(this._lightCamera.frustum.fov)) {
- // If the light camera uses a perspective frustum, then the light source is a spot light
- this._isSpotLight = true;
- }
- // Uniforms
- this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
- this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()];
- this._cascadeDistances = new Cartesian4();
- var numberOfPasses;
- if (this._isPointLight) {
- numberOfPasses = 6; // One shadow map for each direction
- } else if (!this._cascadesEnabled) {
- numberOfPasses = 1;
- } else {
- numberOfPasses = this._numberOfCascades;
- }
- this._passes = new Array(numberOfPasses);
- for (var i = 0; i < numberOfPasses; ++i) {
- this._passes[i] = new ShadowPass(context);
- }
- this.debugShow = false;
- this.debugFreezeFrame = false;
- this._debugFreezeFrame = false;
- this._debugCascadeColors = false;
- this._debugLightFrustum = undefined;
- this._debugCameraFrustum = undefined;
- this._debugCascadeFrustums = new Array(this._numberOfCascades);
- this._debugShadowViewCommand = undefined;
- this._usesDepthTexture = context.depthTexture;
- if (this._isPointLight) {
- this._usesDepthTexture = false;
- }
- // Create render states for shadow casters
- this._primitiveRenderState = undefined;
- this._terrainRenderState = undefined;
- this._pointRenderState = undefined;
- createRenderStates(this);
- // For clearing the shadow map texture every frame
- this._clearCommand = new ClearCommand({
- depth : 1.0,
- color : new Color()
- });
- this._clearPassState = new PassState(context);
- this._size = defaultValue(options.size, 2048);
- this.size = this._size;
- }
- /**
- * Global maximum shadow distance used to prevent far off receivers from extending
- * the shadow far plane. This helps set a tighter near/far when viewing objects from space.
- *
- * @private
- */
- ShadowMap.MAXIMUM_DISTANCE = 20000.0;
- function ShadowPass(context) {
- this.camera = new ShadowMapCamera();
- this.passState = new PassState(context);
- this.framebuffer = undefined;
- this.textureOffsets = undefined;
- this.commandList = [];
- this.cullingVolume = undefined;
- }
- function createRenderState(colorMask, bias) {
- return RenderState.fromCache({
- cull : {
- enabled : true,
- face : CullFace.BACK
- },
- depthTest : {
- enabled : true
- },
- colorMask : {
- red : colorMask,
- green : colorMask,
- blue : colorMask,
- alpha : colorMask
- },
- depthMask : true,
- polygonOffset : {
- enabled : bias.polygonOffset,
- factor : bias.polygonOffsetFactor,
- units : bias.polygonOffsetUnits
- }
- });
- }
- function createRenderStates(shadowMap) {
- // Enable the color mask if the shadow map is backed by a color texture, e.g. when depth textures aren't supported
- var colorMask = !shadowMap._usesDepthTexture;
- shadowMap._primitiveRenderState = createRenderState(colorMask, shadowMap._primitiveBias);
- shadowMap._terrainRenderState = createRenderState(colorMask, shadowMap._terrainBias);
- shadowMap._pointRenderState = createRenderState(colorMask, shadowMap._pointBias);
- }
- /**
- * @private
- */
- ShadowMap.prototype.debugCreateRenderStates = function() {
- createRenderStates(this);
- };
- defineProperties(ShadowMap.prototype, {
- /**
- * Determines if the shadow map will be shown.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @default true
- */
- enabled : {
- get : function() {
- return this._enabled;
- },
- set : function(value) {
- this.dirty = this._enabled !== value;
- this._enabled = value;
- }
- },
- /**
- * Determines if a normal bias will be applied to shadows.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @default true
- */
- normalOffset : {
- get : function() {
- return this._normalOffset;
- },
- set : function(value) {
- this.dirty = this._normalOffset !== value;
- this._normalOffset = value;
- this._terrainBias.normalOffset = value;
- this._primitiveBias.normalOffset = value;
- this._pointBias.normalOffset = value;
- }
- },
- /**
- * Determines if soft shadows are enabled. Uses pcf filtering which requires more texture reads and may hurt performance.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @default false
- */
- softShadows : {
- get : function() {
- return this._softShadows;
- },
- set : function(value) {
- this.dirty = this._softShadows !== value;
- this._softShadows = value;
- }
- },
- /**
- * The width and height, in pixels, of each shadow map.
- *
- * @memberof ShadowMap.prototype
- * @type {Number}
- * @default 2048
- */
- size : {
- get : function() {
- return this._size;
- },
- set : function(value) {
- resize(this, value);
- }
- },
- /**
- * Whether the shadow map is out of view of the scene camera.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @readonly
- * @private
- */
- outOfView : {
- get : function() {
- return this._outOfView;
- }
- },
- /**
- * The culling volume of the shadow frustum.
- *
- * @memberof ShadowMap.prototype
- * @type {CullingVolume}
- * @readonly
- * @private
- */
- shadowMapCullingVolume : {
- get : function() {
- return this._shadowMapCullingVolume;
- }
- },
- /**
- * The passes used for rendering shadows. Each face of a point light or each cascade for a cascaded shadow map is a separate pass.
- *
- * @memberof ShadowMap.prototype
- * @type {ShadowPass[]}
- * @readonly
- * @private
- */
- passes : {
- get : function() {
- return this._passes;
- }
- },
- /**
- * Whether the light source is a point light.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @readonly
- * @private
- */
- isPointLight : {
- get : function() {
- return this._isPointLight;
- }
- },
- /**
- * Debug option for visualizing the cascades by color.
- *
- * @memberof ShadowMap.prototype
- * @type {Boolean}
- * @default false
- * @private
- */
- debugCascadeColors : {
- get : function() {
- return this._debugCascadeColors;
- },
- set : function(value) {
- this.dirty = this._debugCascadeColors !== value;
- this._debugCascadeColors = value;
- }
- }
- });
- function destroyFramebuffer(shadowMap) {
- var length = shadowMap._passes.length;
- for (var i = 0; i < length; ++i) {
- var pass = shadowMap._passes[i];
- var framebuffer = pass.framebuffer;
- if (defined(framebuffer) && !framebuffer.isDestroyed()) {
- framebuffer.destroy();
- }
- pass.framebuffer = undefined;
- }
- // Destroy the framebuffer attachments
- shadowMap._depthAttachment = shadowMap._depthAttachment && shadowMap._depthAttachment.destroy();
- shadowMap._colorAttachment = shadowMap._colorAttachment && shadowMap._colorAttachment.destroy();
- }
- function createSampler() {
- return new Sampler({
- wrapS : TextureWrap.CLAMP_TO_EDGE,
- wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.NEAREST,
- magnificationFilter : TextureMagnificationFilter.NEAREST
- });
- }
- function createFramebufferColor(shadowMap, context) {
- var depthRenderbuffer = new Renderbuffer({
- context : context,
- width : shadowMap._textureSize.x,
- height : shadowMap._textureSize.y,
- format : RenderbufferFormat.DEPTH_COMPONENT16
- });
- var colorTexture = new Texture({
- context : context,
- width : shadowMap._textureSize.x,
- height : shadowMap._textureSize.y,
- pixelFormat : PixelFormat.RGBA,
- pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
- sampler : createSampler()
- });
- var framebuffer = new Framebuffer({
- context : context,
- depthRenderbuffer : depthRenderbuffer,
- colorTextures : [colorTexture],
- destroyAttachments : false
- });
- var length = shadowMap._passes.length;
- for (var i = 0; i < length; ++i) {
- var pass = shadowMap._passes[i];
- pass.framebuffer = framebuffer;
- pass.passState.framebuffer = framebuffer;
- }
- shadowMap._shadowMapTexture = colorTexture;
- shadowMap._depthAttachment = depthRenderbuffer;
- shadowMap._colorAttachment = colorTexture;
- }
- function createFramebufferDepth(shadowMap, context) {
- var depthStencilTexture = new Texture({
- context : context,
- width : shadowMap._textureSize.x,
- height : shadowMap._textureSize.y,
- pixelFormat : PixelFormat.DEPTH_STENCIL,
- pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8,
- sampler : createSampler()
- });
- var framebuffer = new Framebuffer({
- context : context,
- depthStencilTexture : depthStencilTexture,
- destroyAttachments : false
- });
- var length = shadowMap._passes.length;
- for (var i = 0; i < length; ++i) {
- var pass = shadowMap._passes[i];
- pass.framebuffer = framebuffer;
- pass.passState.framebuffer = framebuffer;
- }
- shadowMap._shadowMapTexture = depthStencilTexture;
- shadowMap._depthAttachment = depthStencilTexture;
- }
- function createFramebufferCube(shadowMap, context) {
- var depthRenderbuffer = new Renderbuffer({
- context : context,
- width : shadowMap._textureSize.x,
- height : shadowMap._textureSize.y,
- format : RenderbufferFormat.DEPTH_COMPONENT16
- });
- var cubeMap = new CubeMap({
- context : context,
- width : shadowMap._textureSize.x,
- height : shadowMap._textureSize.y,
- pixelFormat : PixelFormat.RGBA,
- pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
- sampler : createSampler()
- });
- var faces = [cubeMap.negativeX, cubeMap.negativeY, cubeMap.negativeZ, cubeMap.positiveX, cubeMap.positiveY, cubeMap.positiveZ];
- for (var i = 0; i < 6; ++i) {
- var framebuffer = new Framebuffer({
- context : context,
- depthRenderbuffer : depthRenderbuffer,
- colorTextures : [faces[i]],
- destroyAttachments : false
- });
- var pass = shadowMap._passes[i];
- pass.framebuffer = framebuffer;
- pass.passState.framebuffer = framebuffer;
- }
- shadowMap._shadowMapTexture = cubeMap;
- shadowMap._depthAttachment = depthRenderbuffer;
- shadowMap._colorAttachment = cubeMap;
- }
- function createFramebuffer(shadowMap, context) {
- if (shadowMap._isPointLight) {
- createFramebufferCube(shadowMap, context);
- } else if (shadowMap._usesDepthTexture) {
- createFramebufferDepth(shadowMap, context);
- } else {
- createFramebufferColor(shadowMap, context);
- }
- }
- function checkFramebuffer(shadowMap, context) {
- // Attempt to make an FBO with only a depth texture. If it fails, fallback to a color texture.
- if (shadowMap._usesDepthTexture && (shadowMap._passes[0].framebuffer.status !== WebGLConstants.FRAMEBUFFER_COMPLETE)) {
- shadowMap._usesDepthTexture = false;
- createRenderStates(shadowMap);
- destroyFramebuffer(shadowMap);
- createFramebuffer(shadowMap, context);
- }
- }
- function updateFramebuffer(shadowMap, context) {
- if (!defined(shadowMap._passes[0].framebuffer) || (shadowMap._shadowMapTexture.width !== shadowMap._textureSize.x)) {
- destroyFramebuffer(shadowMap);
- createFramebuffer(shadowMap, context);
- checkFramebuffer(shadowMap, context);
- clearFramebuffer(shadowMap, context);
- }
- }
- function clearFramebuffer(shadowMap, context, shadowPass) {
- shadowPass = defaultValue(shadowPass, 0);
- if (shadowMap._isPointLight || (shadowPass === 0)) {
- shadowMap._clearCommand.framebuffer = shadowMap._passes[shadowPass].framebuffer;
- shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
- }
- }
- function resize(shadowMap, size) {
- shadowMap._size = size;
- var passes = shadowMap._passes;
- var numberOfPasses = passes.length;
- var textureSize = shadowMap._textureSize;
- if (shadowMap._isPointLight) {
- size = (ContextLimits.maximumCubeMapSize >= size) ? size : ContextLimits.maximumCubeMapSize;
- textureSize.x = size;
- textureSize.y = size;
- var faceViewport = new BoundingRectangle(0, 0, size, size);
- passes[0].passState.viewport = faceViewport;
- passes[1].passState.viewport = faceViewport;
- passes[2].passState.viewport = faceViewport;
- passes[3].passState.viewport = faceViewport;
- passes[4].passState.viewport = faceViewport;
- passes[5].passState.viewport = faceViewport;
- } else if (numberOfPasses === 1) {
- // +----+
- // | 1 |
- // +----+
- size = (ContextLimits.maximumTextureSize >= size) ? size : ContextLimits.maximumTextureSize;
- textureSize.x = size;
- textureSize.y = size;
- passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
- } else if (numberOfPasses === 4) {
- // +----+----+
- // | 3 | 4 |
- // +----+----+
- // | 1 | 2 |
- // +----+----+
- size = (ContextLimits.maximumTextureSize >= size * 2) ? size : ContextLimits.maximumTextureSize / 2;
- textureSize.x = size * 2;
- textureSize.y = size * 2;
- passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
- passes[1].passState.viewport = new BoundingRectangle(size, 0, size, size);
- passes[2].passState.viewport = new BoundingRectangle(0, size, size, size);
- passes[3].passState.viewport = new BoundingRectangle(size, size, size, size);
- }
- // Update clear pass state
- shadowMap._clearPassState.viewport = new BoundingRectangle(0, 0, textureSize.x, textureSize.y);
- // Transforms shadow coordinates [0, 1] into the pass's region of the texture
- for (var i = 0; i < numberOfPasses; ++i) {
- var pass = passes[i];
- var viewport = pass.passState.viewport;
- var biasX = viewport.x / textureSize.x;
- var biasY = viewport.y / textureSize.y;
- var scaleX = viewport.width / textureSize.x;
- var scaleY = viewport.height / textureSize.y;
- pass.textureOffsets = new Matrix4(scaleX, 0.0, 0.0, biasX, 0.0, scaleY, 0.0, biasY, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
- }
- }
- var scratchViewport = new BoundingRectangle();
- function createDebugShadowViewCommand(shadowMap, context) {
- var fs;
- if (shadowMap._isPointLight) {
- fs = 'uniform samplerCube shadowMap_textureCube; \n' +
- 'varying vec2 v_textureCoordinates; \n' +
- 'void main() \n' +
- '{ \n' +
- ' vec2 uv = v_textureCoordinates; \n' +
- ' vec3 dir; \n' +
- ' \n' +
- ' if (uv.y < 0.5) \n' +
- ' { \n' +
- ' if (uv.x < 0.333) \n' +
- ' { \n' +
- ' dir.x = -1.0; \n' +
- ' dir.y = uv.x * 6.0 - 1.0; \n' +
- ' dir.z = uv.y * 4.0 - 1.0; \n' +
- ' } \n' +
- ' else if (uv.x < 0.666) \n' +
- ' { \n' +
- ' dir.y = -1.0; \n' +
- ' dir.x = uv.x * 6.0 - 3.0; \n' +
- ' dir.z = uv.y * 4.0 - 1.0; \n' +
- ' } \n' +
- ' else \n' +
- ' { \n' +
- ' dir.z = -1.0; \n' +
- ' dir.x = uv.x * 6.0 - 5.0; \n' +
- ' dir.y = uv.y * 4.0 - 1.0; \n' +
- ' } \n' +
- ' } \n' +
- ' else \n' +
- ' { \n' +
- ' if (uv.x < 0.333) \n' +
- ' { \n' +
- ' dir.x = 1.0; \n' +
- ' dir.y = uv.x * 6.0 - 1.0; \n' +
- ' dir.z = uv.y * 4.0 - 3.0; \n' +
- ' } \n' +
- ' else if (uv.x < 0.666) \n' +
- ' { \n' +
- ' dir.y = 1.0; \n' +
- ' dir.x = uv.x * 6.0 - 3.0; \n' +
- ' dir.z = uv.y * 4.0 - 3.0; \n' +
- ' } \n' +
- ' else \n' +
- ' { \n' +
- ' dir.z = 1.0; \n' +
- ' dir.x = uv.x * 6.0 - 5.0; \n' +
- ' dir.y = uv.y * 4.0 - 3.0; \n' +
- ' } \n' +
- ' } \n' +
- ' \n' +
- ' float shadow = czm_unpackDepth(textureCube(shadowMap_textureCube, dir)); \n' +
- ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
- '} \n';
- } else {
- fs = 'uniform sampler2D shadowMap_texture; \n' +
- 'varying vec2 v_textureCoordinates; \n' +
- 'void main() \n' +
- '{ \n' +
- (shadowMap._usesDepthTexture ?
- ' float shadow = texture2D(shadowMap_texture, v_textureCoordinates).r; \n' :
- ' float shadow = czm_unpackDepth(texture2D(shadowMap_texture, v_textureCoordinates)); \n') +
- ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
- '} \n';
- }
- var drawCommand = context.createViewportQuadCommand(fs, {
- uniformMap : {
- shadowMap_texture : function() {
- return shadowMap._shadowMapTexture;
- },
- shadowMap_textureCube : function() {
- return shadowMap._shadowMapTexture;
- }
- }
- });
- drawCommand.pass = Pass.OVERLAY;
- return drawCommand;
- }
- function updateDebugShadowViewCommand(shadowMap, frameState) {
- // Draws the shadow map on the bottom-right corner of the screen
- var context = frameState.context;
- var screenWidth = frameState.context.drawingBufferWidth;
- var screenHeight = frameState.context.drawingBufferHeight;
- var size = Math.min(screenWidth, screenHeight) * 0.3;
- var viewport = scratchViewport;
- viewport.x = screenWidth - size;
- viewport.y = 0;
- viewport.width = size;
- viewport.height = size;
- var debugCommand = shadowMap._debugShadowViewCommand;
- if (!defined(debugCommand)) {
- debugCommand = createDebugShadowViewCommand(shadowMap, context);
- shadowMap._debugShadowViewCommand = debugCommand;
- }
- // Get a new RenderState for the updated viewport size
- if (!defined(debugCommand.renderState) || !BoundingRectangle.equals(debugCommand.renderState.viewport, viewport)) {
- debugCommand.renderState = RenderState.fromCache({
- viewport : BoundingRectangle.clone(viewport)
- });
- }
- frameState.commandList.push(shadowMap._debugShadowViewCommand);
- }
- var frustumCornersNDC = new Array(8);
- frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
- frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
- frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
- frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
- frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
- frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
- frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
- frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);
- var scratchMatrix = new Matrix4();
- var scratchFrustumCorners = new Array(8);
- for (var i = 0; i < 8; ++i) {
- scratchFrustumCorners[i] = new Cartesian4();
- }
- function createDebugPointLight(modelMatrix, color) {
- var box = new GeometryInstance({
- geometry : new BoxOutlineGeometry({
- minimum : new Cartesian3(-0.5, -0.5, -0.5),
- maximum : new Cartesian3(0.5, 0.5, 0.5)
- }),
- attributes : {
- color : ColorGeometryInstanceAttribute.fromColor(color)
- }
- });
- var sphere = new GeometryInstance({
- geometry : new SphereOutlineGeometry({
- radius : 0.5
- }),
- attributes : {
- color : ColorGeometryInstanceAttribute.fromColor(color)
- }
- });
- return new Primitive({
- geometryInstances : [box, sphere],
- appearance : new PerInstanceColorAppearance({
- translucent : false,
- flat : true
- }),
- asynchronous : false,
- modelMatrix : modelMatrix
- });
- }
- var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA];
- var scratchScale = new Cartesian3();
- function applyDebugSettings(shadowMap, frameState) {
- updateDebugShadowViewCommand(shadowMap, frameState);
- var enterFreezeFrame = shadowMap.debugFreezeFrame && !shadowMap._debugFreezeFrame;
- shadowMap._debugFreezeFrame = shadowMap.debugFreezeFrame;
- // Draw scene camera in freeze frame mode
- if (shadowMap.debugFreezeFrame) {
- if (enterFreezeFrame) {
- // Recreate debug camera when entering freeze frame mode
- shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy();
- shadowMap._debugCameraFrustum = new DebugCameraPrimitive({
- camera : shadowMap._sceneCamera,
- color : Color.CYAN,
- updateOnChange : false
- });
- }
- shadowMap._debugCameraFrustum.update(frameState);
- }
- if (shadowMap._cascadesEnabled) {
- // Draw cascades only in freeze frame mode
- if (shadowMap.debugFreezeFrame) {
- if (enterFreezeFrame) {
- // Recreate debug frustum when entering freeze frame mode
- shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
- shadowMap._debugLightFrustum = new DebugCameraPrimitive({
- camera : shadowMap._shadowMapCamera,
- color : Color.YELLOW,
- updateOnChange : false
- });
- }
- shadowMap._debugLightFrustum.update(frameState);
- for (var i = 0; i < shadowMap._numberOfCascades; ++i) {
- if (enterFreezeFrame) {
- // Recreate debug frustum when entering freeze frame mode
- shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy();
- shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({
- camera : shadowMap._passes[i].camera,
- color : debugOutlineColors[i],
- updateOnChange : false
- });
- }
- shadowMap._debugCascadeFrustums[i].update(frameState);
- }
- }
- } else if (shadowMap._isPointLight) {
- if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
- var translation = shadowMap._shadowMapCamera.positionWC;
- var rotation = Quaternion.IDENTITY;
- var uniformScale = shadowMap._pointLightRadius * 2.0;
- var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
- var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);
- shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
- shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
- }
- shadowMap._debugLightFrustum.update(frameState);
- } else {
- if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
- shadowMap._debugLightFrustum = new DebugCameraPrimitive({
- camera : shadowMap._shadowMapCamera,
- color : Color.YELLOW,
- updateOnChange : false
- });
- }
- shadowMap._debugLightFrustum.update(frameState);
- }
- }
- function ShadowMapCamera() {
- this.viewMatrix = new Matrix4();
- this.inverseViewMatrix = new Matrix4();
- this.frustum = undefined;
- this.positionCartographic = new Cartographic();
- this.positionWC = new Cartesian3();
- this.directionWC = Cartesian3.clone(Cartesian3.UNIT_Z);
- this.upWC = Cartesian3.clone(Cartesian3.UNIT_Y);
- this.rightWC = Cartesian3.clone(Cartesian3.UNIT_X);
- this.viewProjectionMatrix = new Matrix4();
- }
- ShadowMapCamera.prototype.clone = function(camera) {
- Matrix4.clone(camera.viewMatrix, this.viewMatrix);
- Matrix4.clone(camera.inverseViewMatrix, this.inverseViewMatrix);
- this.frustum = camera.frustum.clone(this.frustum);
- Cartographic.clone(camera.positionCartographic, this.positionCartographic);
- Cartesian3.clone(camera.positionWC, this.positionWC);
- Cartesian3.clone(camera.directionWC, this.directionWC);
- Cartesian3.clone(camera.upWC, this.upWC);
- Cartesian3.clone(camera.rightWC, this.rightWC);
- };
- // Converts from NDC space to texture space
- var scaleBiasMatrix = new Matrix4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0);
- ShadowMapCamera.prototype.getViewProjection = function() {
- var view = this.viewMatrix;
- var projection = this.frustum.projectionMatrix;
- Matrix4.multiply(projection, view, this.viewProjectionMatrix);
- Matrix4.multiply(scaleBiasMatrix, this.viewProjectionMatrix, this.viewProjectionMatrix);
- return this.viewProjectionMatrix;
- };
- var scratchSplits = new Array(5);
- var scratchFrustum = new PerspectiveFrustum();
- var scratchCascadeDistances = new Array(4);
- var scratchMin = new Cartesian3();
- var scratchMax = new Cartesian3();
- function computeCascades(shadowMap, frameState) {
- var shadowMapCamera = shadowMap._shadowMapCamera;
- var sceneCamera = shadowMap._sceneCamera;
- var cameraNear = sceneCamera.frustum.near;
- var cameraFar = sceneCamera.frustum.far;
- var numberOfCascades = shadowMap._numberOfCascades;
- // Split cascades. Use a mix of linear and log splits.
- var i;
- var range = cameraFar - cameraNear;
- var ratio = cameraFar / cameraNear;
- var lambda = 0.9;
- var clampCascadeDistances = false;
- // When the camera is close to a relatively small model, provide more detail in the closer cascades.
- // If the camera is near or inside a large model, such as the root tile of a city, then use the default values.
- // To get the most accurate cascade splits we would need to find the min and max values from the depth texture.
- if (frameState.shadowState.closestObjectSize < 200.0) {
- clampCascadeDistances = true;
- lambda = 0.9;
- }
- var cascadeDistances = scratchCascadeDistances;
- var splits = scratchSplits;
- splits[0] = cameraNear;
- splits[numberOfCascades] = cameraFar;
- // Find initial splits
- for (i = 0; i < numberOfCascades; ++i) {
- var p = (i + 1) / numberOfCascades;
- var logScale = cameraNear * Math.pow(ratio, p);
- var uniformScale = cameraNear + range * p;
- var split = CesiumMath.lerp(uniformScale, logScale, lambda);
- splits[i + 1] = split;
- cascadeDistances[i] = split - splits[i];
- }
- if (clampCascadeDistances) {
- // Clamp each cascade to its maximum distance
- for (i = 0; i < numberOfCascades; ++i) {
- cascadeDistances[i] = Math.min(cascadeDistances[i], shadowMap._maximumCascadeDistances[i]);
- }
- // Recompute splits
- var distance = splits[0];
- for (i = 0; i < numberOfCascades - 1; ++i) {
- distance += cascadeDistances[i];
- splits[i + 1] = distance;
- }
- }
- Cartesian4.unpack(splits, 0, shadowMap._cascadeSplits[0]);
- Cartesian4.unpack(splits, 1, shadowMap._cascadeSplits[1]);
- Cartesian4.unpack(cascadeDistances, 0, shadowMap._cascadeDistances);
- var shadowFrustum = shadowMapCamera.frustum;
- var left = shadowFrustum.left;
- var right = shadowFrustum.right;
- var bottom = shadowFrustum.bottom;
- var top = shadowFrustum.top;
- var near = shadowFrustum.near;
- var far = shadowFrustum.far;
- var position = shadowMapCamera.positionWC;
- var direction = shadowMapCamera.directionWC;
- var up = shadowMapCamera.upWC;
- var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum);
- var shadowViewProjection = shadowMapCamera.getViewProjection();
- for (i = 0; i < numberOfCascades; ++i) {
- // Find the bounding box of the camera sub-frustum in shadow map texture space
- cascadeSubFrustum.near = splits[i];
- cascadeSubFrustum.far = splits[i + 1];
- var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
- var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
- var shadowMapMatrix = Matrix4.multiply(shadowViewProjection, inverseViewProjection, scratchMatrix);
- // Project each corner from camera NDC space to shadow map texture space. Min and max will be from 0 to 1.
- var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
- var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
- for (var k = 0; k < 8; ++k) {
- var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]);
- Matrix4.multiplyByVector(shadowMapMatrix, corner, corner);
- Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
- Cartesian3.minimumByComponent(corner, min, min);
- Cartesian3.maximumByComponent(corner, max, max);
- }
- // Limit light-space coordinates to the [0, 1] range
- min.x = Math.max(min.x, 0.0);
- min.y = Math.max(min.y, 0.0);
- min.z = 0.0; // Always start cascade frustum at the top of the light frustum to capture objects in the light's path
- max.x = Math.min(max.x, 1.0);
- max.y = Math.min(max.y, 1.0);
- max.z = Math.min(max.z, 1.0);
- var pass = shadowMap._passes[i];
- var cascadeCamera = pass.camera;
- cascadeCamera.clone(shadowMapCamera); // PERFORMANCE_IDEA : could do a shallow clone for all properties except the frustum
- var frustum = cascadeCamera.frustum;
- frustum.left = left + min.x * (right - left);
- frustum.right = left + max.x * (right - left);
- frustum.bottom = bottom + min.y * (top - bottom);
- frustum.top = bottom + max.y * (top - bottom);
- frustum.near = near + min.z * (far - near);
- frustum.far = near + max.z * (far - near);
- pass.cullingVolume = cascadeCamera.frustum.computeCullingVolume(position, direction, up);
- // Transforms from eye space to the cascade's texture space
- var cascadeMatrix = shadowMap._cascadeMatrices[i];
- Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix);
- Matrix4.multiply(pass.textureOffsets, cascadeMatrix, cascadeMatrix);
- }
- }
- var scratchLightView = new Matrix4();
- var scratchRight = new Cartesian3();
- var scratchUp = new Cartesian3();
- var scratchTranslation = new Cartesian3();
- function fitShadowMapToScene(shadowMap, frameState) {
- var shadowMapCamera = shadowMap._shadowMapCamera;
- var sceneCamera = shadowMap._sceneCamera;
- // 1. First find a tight bounding box in light space that contains the entire camera frustum.
- var viewProjection = Matrix4.multiply(sceneCamera.frustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
- var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
- // Start to construct the light view matrix. Set translation later once the bounding box is found.
- var lightDir = shadowMapCamera.directionWC;
- var lightUp = sceneCamera.directionWC; // Align shadows to the camera view.
- var lightRight = Cartesian3.cross(lightDir, lightUp, scratchRight);
- lightUp = Cartesian3.cross(lightRight, lightDir, scratchUp); // Recalculate up now that right is derived
- Cartesian3.normalize(lightUp, lightUp);
- Cartesian3.normalize(lightRight, lightRight);
- var lightPosition = Cartesian3.fromElements(0.0, 0.0, 0.0, scratchTranslation);
- var lightView = Matrix4.computeView(lightPosition, lightDir, lightUp, lightRight, scratchLightView);
- var cameraToLight = Matrix4.multiply(lightView, inverseViewProjection, scratchMatrix);
- // Project each corner from NDC space to light view space, and calculate a min and max in light view space
- var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
- var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
- for (var i = 0; i < 8; ++i) {
- var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
- Matrix4.multiplyByVector(cameraToLight, corner, corner);
- Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
- Cartesian3.minimumByComponent(corner, min, min);
- Cartesian3.maximumByComponent(corner, max, max);
- }
- // 2. Set bounding box back to include objects in the light's view
- max.z += 1000.0; // Note: in light space, a positive number is behind the camera
- min.z -= 10.0; // Extend the shadow volume forward slightly to avoid problems right at the edge
- // 3. Adjust light view matrix so that it is centered on the bounding volume
- var translation = scratchTranslation;
- translation.x = -(0.5 * (min.x + max.x));
- translation.y = -(0.5 * (min.y + max.y));
- translation.z = -max.z;
- var translationMatrix = Matrix4.fromTranslation(translation, scratchMatrix);
- lightView = Matrix4.multiply(translationMatrix, lightView, lightView);
- // 4. Create an orthographic frustum that covers the bounding box extents
- var halfWidth = 0.5 * (max.x - min.x);
- var halfHeight = 0.5 * (max.y - min.y);
- var depth = max.z - min.z;
- var frustum = shadowMapCamera.frustum;
- frustum.left = -halfWidth;
- frustum.right = halfWidth;
- frustum.bottom = -halfHeight;
- frustum.top = halfHeight;
- frustum.near = 0.01;
- frustum.far = depth;
- // 5. Update the shadow map camera
- Matrix4.clone(lightView, shadowMapCamera.viewMatrix);
- Matrix4.inverse(lightView, shadowMapCamera.inverseViewMatrix);
- Matrix4.getTranslation(shadowMapCamera.inverseViewMatrix, shadowMapCamera.positionWC);
- frameState.mapProjection.ellipsoid.cartesianToCartographic(shadowMapCamera.positionWC, shadowMapCamera.positionCartographic);
- Cartesian3.clone(lightDir, shadowMapCamera.directionWC);
- Cartesian3.clone(lightUp, shadowMapCamera.upWC);
- Cartesian3.clone(lightRight, shadowMapCamera.rightWC);
- }
- var directions = [
- new Cartesian3(-1.0, 0.0, 0.0),
- new Cartesian3(0.0, -1.0, 0.0),
- new Cartesian3(0.0, 0.0, -1.0),
- new Cartesian3(1.0, 0.0, 0.0),
- new Cartesian3(0.0, 1.0, 0.0),
- new Cartesian3(0.0, 0.0, 1.0)
- ];
- var ups = [
- new Cartesian3(0.0, -1.0, 0.0),
- new Cartesian3(0.0, 0.0, -1.0),
- new Cartesian3(0.0, -1.0, 0.0),
- new Cartesian3(0.0, -1.0, 0.0),
- new Cartesian3(0.0, 0.0, 1.0),
- new Cartesian3(0.0, -1.0, 0.0)
- ];
- var rights = [
- new Cartesian3(0.0, 0.0, 1.0),
- new Cartesian3(1.0, 0.0, 0.0),
- new Cartesian3(-1.0, 0.0, 0.0),
- new Cartesian3(0.0, 0.0, -1.0),
- new Cartesian3(1.0, 0.0, 0.0),
- new Cartesian3(1.0, 0.0, 0.0)
- ];
- function computeOmnidirectional(shadowMap, frameState) {
- // All sides share the same frustum
- var frustum = new PerspectiveFrustum();
- frustum.fov = CesiumMath.PI_OVER_TWO;
- frustum.near = 1.0;
- frustum.far = shadowMap._pointLightRadius;
- frustum.aspectRatio = 1.0;
- for (var i = 0; i < 6; ++i) {
- var camera = shadowMap._passes[i].camera;
- camera.positionWC = shadowMap._shadowMapCamera.positionWC;
- camera.positionCartographic = frameState.mapProjection.ellipsoid.cartesianToCartographic(camera.positionWC, camera.positionCartographic);
- camera.directionWC = directions[i];
- camera.upWC = ups[i];
- camera.rightWC = rights[i];
- Matrix4.computeView(camera.positionWC, camera.directionWC, camera.upWC, camera.rightWC, camera.viewMatrix);
- Matrix4.inverse(camera.viewMatrix, camera.inverseViewMatrix);
- camera.frustum = frustum;
- }
- }
- var scratchCartesian1 = new Cartesian3();
- var scratchCartesian2 = new Cartesian3();
- var scratchBoundingSphere = new BoundingSphere();
- var scratchCenter = scratchBoundingSphere.center;
- function checkVisibility(shadowMap, frameState) {
- var sceneCamera = shadowMap._sceneCamera;
- var shadowMapCamera = shadowMap._shadowMapCamera;
- var boundingSphere = scratchBoundingSphere;
- // Check whether the shadow map is in view and needs to be updated
- if (shadowMap._cascadesEnabled) {
- // If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
- if (sceneCamera.frustum.near >= shadowMap.maximumDistance) {
- shadowMap._outOfView = true;
- shadowMap._needsUpdate = false;
- return;
- }
- // If the light source is below the horizon then the shadow map is out of view
- var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
- var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
- var dot = Cartesian3.dot(surfaceNormal, lightDirection);
- // Shadows start to fade out once the light gets closer to the horizon.
- // At this point the globe uses vertex lighting alone to darken the surface.
- var darknessAmount = CesiumMath.clamp(dot / 0.1, 0.0, 1.0);
- shadowMap._darkness = CesiumMath.lerp(1.0, shadowMap.darkness, darknessAmount);
- if (dot < 0.0) {
- shadowMap._outOfView = true;
- shadowMap._needsUpdate = false;
- return;
- }
- // By default cascaded shadows need to update and are always in view
- shadowMap._needsUpdate = true;
- shadowMap._outOfView = false;
- } else if (shadowMap._isPointLight) {
- // Sphere-frustum intersection test
- boundingSphere.center = shadowMapCamera.positionWC;
- boundingSphere.radius = shadowMap._pointLightRadius;
- shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
- shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
- BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
- } else {
- // Simplify frustum-frustum intersection test as a sphere-frustum test
- var frustumRadius = shadowMapCamera.frustum.far / 2.0;
- var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
- boundingSphere.center = frustumCenter;
- boundingSphere.radius = frustumRadius;
- shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
- shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
- BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
- }
- }
- function updateCameras(shadowMap, frameState) {
- var camera = frameState.camera; // The actual camera in the scene
- var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
- var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
- var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera
- // Clone light camera into the shadow map camera
- if (shadowMap._cascadesEnabled) {
- Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
- } else if (shadowMap._isPointLight) {
- Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
- } else {
- shadowMapCamera.clone(lightCamera);
- }
- // Get the light direction in eye coordinates
- var lightDirection = shadowMap._lightDirectionEC;
- Matrix4.multiplyByPointAsVector(camera.viewMatrix, shadowMapCamera.directionWC, lightDirection);
- Cartesian3.normalize(lightDirection, lightDirection);
- Cartesian3.negate(lightDirection, lightDirection);
- // Get the light position in eye coordinates
- Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC);
- shadowMap._lightPositionEC.w = shadowMap._pointLightRadius;
- // Get the near and far of the scene camera
- var near;
- var far;
- if (shadowMap._fitNearFar) {
- // shadowFar can be very large, so limit to shadowMap.maximumDistance
- // Push the far plane slightly further than the near plane to avoid degenerate frustum
- near = Math.min(frameState.shadowState.nearPlane, shadowMap.maximumDistance);
- far = Math.min(frameState.shadowState.farPlane, shadowMap.maximumDistance + 1.0);
- } else {
- near = camera.frustum.near;
- far = shadowMap.maximumDistance;
- }
- shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
- camera.frustum.clone(shadowMap._sceneCamera.frustum);
- shadowMap._sceneCamera.frustum.near = near;
- shadowMap._sceneCamera.frustum.far = far;
- shadowMap._distance = far - near;
- checkVisibility(shadowMap, frameState);
- if (!shadowMap._outOfViewPrevious && shadowMap._outOfView) {
- shadowMap._needsUpdate = true;
- }
- shadowMap._outOfViewPrevious = shadowMap._outOfView;
- }
- /**
- * @private
- */
- ShadowMap.prototype.update = function(frameState) {
- updateCameras(this, frameState);
- if (this._needsUpdate) {
- updateFramebuffer(this, frameState.context);
- if (this._isPointLight) {
- computeOmnidirectional(this, frameState);
- }
- if (this._cascadesEnabled) {
- fitShadowMapToScene(this, frameState);
- if (this._numberOfCascades > 1) {
- computeCascades(this, frameState);
- }
- }
- if (!this._isPointLight) {
- // Compute the culling volume
- var shadowMapCamera = this._shadowMapCamera;
- var position = shadowMapCamera.positionWC;
- var direction = shadowMapCamera.directionWC;
- var up = shadowMapCamera.upWC;
- this._shadowMapCullingVolume = shadowMapCamera.frustum.computeCullingVolume(position, direction, up);
- if (this._passes.length === 1) {
- // Since there is only one pass, use the shadow map camera as the pass camera.
- this._passes[0].camera.clone(shadowMapCamera);
- }
- } else {
- this._shadowMapCullingVolume = CullingVolume.fromBoundingSphere(this._boundingSphere);
- }
- }
- if (this._passes.length === 1) {
- // Transforms from eye space to shadow texture space.
- // Always requires an update since the scene camera constantly changes.
- var inverseView = this._sceneCamera.inverseViewMatrix;
- Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
- }
- if (this.debugShow) {
- applyDebugSettings(this, frameState);
- }
- };
- /**
- * @private
- */
- ShadowMap.prototype.updatePass = function(context, shadowPass) {
- clearFramebuffer(this, context, shadowPass);
- };
- var scratchTexelStepSize = new Cartesian2();
- function combineUniforms(shadowMap, uniforms, isTerrain) {
- var bias = shadowMap._isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);
- var mapUniforms = {
- shadowMap_texture :function() {
- return shadowMap._shadowMapTexture;
- },
- shadowMap_textureCube : function() {
- return shadowMap._shadowMapTexture;
- },
- shadowMap_matrix : function() {
- return shadowMap._shadowMapMatrix;
- },
- shadowMap_cascadeSplits : function() {
- return shadowMap._cascadeSplits;
- },
- shadowMap_cascadeMatrices : function() {
- return shadowMap._cascadeMatrices;
- },
- shadowMap_lightDirectionEC : function() {
- return shadowMap._lightDirectionEC;
- },
- shadowMap_lightPositionEC : function() {
- return shadowMap._lightPositionEC;
- },
- shadowMap_cascadeDistances : function() {
- return shadowMap._cascadeDistances;
- },
- shadowMap_texelSizeDepthBiasAndNormalShadingSmooth : function() {
- var texelStepSize = scratchTexelStepSize;
- texelStepSize.x = 1.0 / shadowMap._textureSize.x;
- texelStepSize.y = 1.0 / shadowMap._textureSize.y;
- return Cartesian4.fromElements(texelStepSize.x, texelStepSize.y, bias.depthBias, bias.normalShadingSmooth, this.combinedUniforms1);
- },
- shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness : function() {
- return Cartesian4.fromElements(bias.normalOffsetScale, shadowMap._distance, shadowMap.maximumDistance, shadowMap._darkness, this.combinedUniforms2);
- },
- combinedUniforms1 : new Cartesian4(),
- combinedUniforms2 : new Cartesian4()
- };
- return combine(uniforms, mapUniforms, false);
- }
- function createCastDerivedCommand(shadowMap, shadowsDirty, command, context, oldShaderId, result) {
- var castShader;
- var castRenderState;
- var castUniformMap;
- if (defined(result)) {
- castShader = result.shaderProgram;
- castRenderState = result.renderState;
- castUniformMap = result.uniformMap;
- }
- result = DrawCommand.shallowClone(command, result);
- result.castShadows = true;
- result.receiveShadows = false;
- if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) {
- var shaderProgram = command.shaderProgram;
- var isTerrain = command.pass === Pass.GLOBE;
- var isOpaque = command.pass !== Pass.TRANSLUCENT;
- var isPointLight = shadowMap._isPointLight;
- var usesDepthTexture= shadowMap._usesDepthTexture;
- var keyword = ShadowMapShader.getShadowCastShaderKeyword(isPointLight, isTerrain, usesDepthTexture, isOpaque);
- castShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
- if (!defined(castShader)) {
- var vertexShaderSource = shaderProgram.vertexShaderSource;
- var fragmentShaderSource = shaderProgram.fragmentShaderSource;
- var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain);
- var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque);
- castShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
- vertexShaderSource : castVS,
- fragmentShaderSource : castFS,
- attributeLocations : shaderProgram._attributeLocations
- });
- }
- castRenderState = shadowMap._primitiveRenderState;
- if (isPointLight) {
- castRenderState = shadowMap._pointRenderState;
- } else if (isTerrain) {
- castRenderState = shadowMap._terrainRenderState;
- }
- // Modify the render state for commands that do not use back-face culling, e.g. flat textured walls
- var cullEnabled = command.renderState.cull.enabled;
- if (!cullEnabled) {
- castRenderState = clone(castRenderState, false);
- castRenderState.cull = clone(castRenderState.cull, false);
- castRenderState.cull.enabled = false;
- castRenderState = RenderState.fromCache(castRenderState);
- }
- castUniformMap = combineUniforms(shadowMap, command.uniformMap, isTerrain);
- }
- result.shaderProgram = castShader;
- result.renderState = castRenderState;
- result.uniformMap = castUniformMap;
- return result;
- }
- ShadowMap.createReceiveDerivedCommand = function(lightShadowMaps, command, shadowsDirty, context, result) {
- if (!defined(result)) {
- result = {};
- }
- var lightShadowMapsEnabled = (lightShadowMaps.length > 0);
- var shaderProgram = command.shaderProgram;
- var vertexShaderSource = shaderProgram.vertexShaderSource;
- var fragmentShaderSource = shaderProgram.fragmentShaderSource;
- var isTerrain = command.pass === Pass.GLOBE;
- var hasTerrainNormal = false;
- if (isTerrain) {
- hasTerrainNormal = command.owner.data.renderedMesh.encoding.hasVertexNormals;
- }
- if (command.receiveShadows && lightShadowMapsEnabled) {
- // Only generate a receiveCommand if there is a shadow map originating from a light source.
- var receiveShader;
- var receiveUniformMap;
- if (defined(result.receiveCommand)) {
- receiveShader = result.receiveCommand.shaderProgram;
- receiveUniformMap = result.receiveCommand.uniformMap;
- }
- result.receiveCommand = DrawCommand.shallowClone(command, result.receiveCommand);
- result.castShadows = false;
- result.receiveShadows = true;
- // If castShadows changed, recompile the receive shadows shader. The normal shading technique simulates
- // self-shadowing so it should be turned off if castShadows is false.
- var castShadowsDirty = result.receiveShaderCastShadows !== command.castShadows;
- var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id;
- if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) {
- var keyword = ShadowMapShader.getShadowReceiveShaderKeyword(lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
- receiveShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
- if (!defined(receiveShader)) {
- var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal);
- var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
- receiveShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
- vertexShaderSource : receiveVS,
- fragmentShaderSource : receiveFS,
- attributeLocations : shaderProgram._attributeLocations
- });
- }
- receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain);
- }
- result.receiveCommand.shaderProgram = receiveShader;
- result.receiveCommand.uniformMap = receiveUniformMap;
- result.receiveShaderProgramId = command.shaderProgram.id;
- result.receiveShaderCastShadows = command.castShadows;
- }
- return result;
- };
- ShadowMap.createCastDerivedCommand = function(shadowMaps, command, shadowsDirty, context, result) {
- if (!defined(result)) {
- result = {};
- }
- if (command.castShadows) {
- var castCommands = result.castCommands;
- if (!defined(castCommands)) {
- castCommands = result.castCommands = [];
- }
- var oldShaderId = result.castShaderProgramId;
- var shadowMapLength = shadowMaps.length;
- castCommands.length = shadowMapLength;
- for (var i = 0; i < shadowMapLength; ++i) {
- castCommands[i] = createCastDerivedCommand(shadowMaps[i], shadowsDirty, command, context, oldShaderId, castCommands[i]);
- }
- result.castShaderProgramId = command.shaderProgram.id;
- }
- return result;
- };
- /**
- * @private
- */
- ShadowMap.prototype.isDestroyed = function() {
- return false;
- };
- /**
- * @private
- */
- ShadowMap.prototype.destroy = function() {
- destroyFramebuffer(this);
- this._debugLightFrustum = this._debugLightFrustum && this._debugLightFrustum.destroy();
- this._debugCameraFrustum = this._debugCameraFrustum && this._debugCameraFrustum.destroy();
- this._debugShadowViewCommand = this._debugShadowViewCommand && this._debugShadowViewCommand.shaderProgram && this._debugShadowViewCommand.shaderProgram.destroy();
- for (var i = 0; i < this._numberOfCascades; ++i) {
- this._debugCascadeFrustums[i] = this._debugCascadeFrustums[i] && this._debugCascadeFrustums[i].destroy();
- }
- return destroyObject(this);
- };
- export default ShadowMap;
|