ShadowMapShader.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import defined from '../Core/defined.js';
  2. import ShaderSource from '../Renderer/ShaderSource.js';
  3. /**
  4. * @private
  5. */
  6. function ShadowMapShader() {
  7. }
  8. ShadowMapShader.getShadowCastShaderKeyword = function(isPointLight, isTerrain, usesDepthTexture, isOpaque) {
  9. return 'castShadow ' + isPointLight + ' ' + isTerrain + ' ' + usesDepthTexture + ' ' + isOpaque;
  10. };
  11. ShadowMapShader.createShadowCastVertexShader = function(vs, isPointLight, isTerrain) {
  12. var defines = vs.defines.slice(0);
  13. var sources = vs.sources.slice(0);
  14. defines.push('SHADOW_MAP');
  15. if (isTerrain) {
  16. defines.push('GENERATE_POSITION');
  17. }
  18. var positionVaryingName = ShaderSource.findPositionVarying(vs);
  19. var hasPositionVarying = defined(positionVaryingName);
  20. if (isPointLight && !hasPositionVarying) {
  21. var length = sources.length;
  22. for (var j = 0; j < length; ++j) {
  23. sources[j] = ShaderSource.replaceMain(sources[j], 'czm_shadow_cast_main');
  24. }
  25. var shadowVS =
  26. 'varying vec3 v_positionEC; \n' +
  27. 'void main() \n' +
  28. '{ \n' +
  29. ' czm_shadow_cast_main(); \n' +
  30. ' v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' +
  31. '}';
  32. sources.push(shadowVS);
  33. }
  34. return new ShaderSource({
  35. defines : defines,
  36. sources : sources
  37. });
  38. };
  39. ShadowMapShader.createShadowCastFragmentShader = function(fs, isPointLight, usesDepthTexture, opaque) {
  40. var defines = fs.defines.slice(0);
  41. var sources = fs.sources.slice(0);
  42. var positionVaryingName = ShaderSource.findPositionVarying(fs);
  43. var hasPositionVarying = defined(positionVaryingName);
  44. if (!hasPositionVarying) {
  45. positionVaryingName = 'v_positionEC';
  46. }
  47. var length = sources.length;
  48. for (var i = 0; i < length; ++i) {
  49. sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_cast_main');
  50. }
  51. var fsSource = '';
  52. if (isPointLight) {
  53. if (!hasPositionVarying) {
  54. fsSource += 'varying vec3 v_positionEC; \n';
  55. }
  56. fsSource += 'uniform vec4 shadowMap_lightPositionEC; \n';
  57. }
  58. if (opaque) {
  59. fsSource +=
  60. 'void main() \n' +
  61. '{ \n';
  62. } else {
  63. fsSource +=
  64. 'void main() \n' +
  65. '{ \n' +
  66. ' czm_shadow_cast_main(); \n' +
  67. ' if (gl_FragColor.a == 0.0) \n' +
  68. ' { \n' +
  69. ' discard; \n' +
  70. ' } \n';
  71. }
  72. if (isPointLight) {
  73. fsSource +=
  74. ' float distance = length(' + positionVaryingName + '); \n' +
  75. ' if (distance >= shadowMap_lightPositionEC.w) \n' +
  76. ' { \n' +
  77. ' discard; \n' +
  78. ' } \n' +
  79. ' distance /= shadowMap_lightPositionEC.w; // radius \n' +
  80. ' gl_FragColor = czm_packDepth(distance); \n';
  81. } else if (usesDepthTexture) {
  82. fsSource += ' gl_FragColor = vec4(1.0); \n';
  83. } else {
  84. fsSource += ' gl_FragColor = czm_packDepth(gl_FragCoord.z); \n';
  85. }
  86. fsSource += '} \n';
  87. sources.push(fsSource);
  88. return new ShaderSource({
  89. defines : defines,
  90. sources : sources
  91. });
  92. };
  93. ShadowMapShader.getShadowReceiveShaderKeyword = function(shadowMap, castShadows, isTerrain, hasTerrainNormal) {
  94. var usesDepthTexture = shadowMap._usesDepthTexture;
  95. var polygonOffsetSupported = shadowMap._polygonOffsetSupported;
  96. var isPointLight = shadowMap._isPointLight;
  97. var isSpotLight = shadowMap._isSpotLight;
  98. var hasCascades = shadowMap._numberOfCascades > 1;
  99. var debugCascadeColors = shadowMap.debugCascadeColors;
  100. var softShadows = shadowMap.softShadows;
  101. return 'receiveShadow ' + usesDepthTexture + polygonOffsetSupported + isPointLight + isSpotLight +
  102. hasCascades + debugCascadeColors + softShadows + castShadows + isTerrain + hasTerrainNormal;
  103. };
  104. ShadowMapShader.createShadowReceiveVertexShader = function(vs, isTerrain, hasTerrainNormal) {
  105. var defines = vs.defines.slice(0);
  106. var sources = vs.sources.slice(0);
  107. defines.push('SHADOW_MAP');
  108. if (isTerrain) {
  109. if (hasTerrainNormal) {
  110. defines.push('GENERATE_POSITION_AND_NORMAL');
  111. } else {
  112. defines.push('GENERATE_POSITION');
  113. }
  114. }
  115. return new ShaderSource({
  116. defines : defines,
  117. sources : sources
  118. });
  119. };
  120. ShadowMapShader.createShadowReceiveFragmentShader = function(fs, shadowMap, castShadows, isTerrain, hasTerrainNormal) {
  121. var normalVaryingName = ShaderSource.findNormalVarying(fs);
  122. var hasNormalVarying = (!isTerrain && defined(normalVaryingName)) || (isTerrain && hasTerrainNormal);
  123. var positionVaryingName = ShaderSource.findPositionVarying(fs);
  124. var hasPositionVarying = defined(positionVaryingName);
  125. var usesDepthTexture = shadowMap._usesDepthTexture;
  126. var polygonOffsetSupported = shadowMap._polygonOffsetSupported;
  127. var isPointLight = shadowMap._isPointLight;
  128. var isSpotLight = shadowMap._isSpotLight;
  129. var hasCascades = shadowMap._numberOfCascades > 1;
  130. var debugCascadeColors = shadowMap.debugCascadeColors;
  131. var softShadows = shadowMap.softShadows;
  132. var bias = isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);
  133. var defines = fs.defines.slice(0);
  134. var sources = fs.sources.slice(0);
  135. var length = sources.length;
  136. for (var i = 0; i < length; ++i) {
  137. sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_receive_main');
  138. }
  139. if (isPointLight) {
  140. defines.push('USE_CUBE_MAP_SHADOW');
  141. } else if (usesDepthTexture) {
  142. defines.push('USE_SHADOW_DEPTH_TEXTURE');
  143. }
  144. if (softShadows && !isPointLight) {
  145. defines.push('USE_SOFT_SHADOWS');
  146. }
  147. // Enable day-night shading so that the globe is dark when the light is below the horizon
  148. if (hasCascades && castShadows && isTerrain) {
  149. if (hasNormalVarying) {
  150. defines.push('ENABLE_VERTEX_LIGHTING');
  151. } else {
  152. defines.push('ENABLE_DAYNIGHT_SHADING');
  153. }
  154. }
  155. if (castShadows && bias.normalShading && hasNormalVarying) {
  156. defines.push('USE_NORMAL_SHADING');
  157. if (bias.normalShadingSmooth > 0.0) {
  158. defines.push('USE_NORMAL_SHADING_SMOOTH');
  159. }
  160. }
  161. var fsSource = '';
  162. if (isPointLight) {
  163. fsSource += 'uniform samplerCube shadowMap_textureCube; \n';
  164. } else {
  165. fsSource += 'uniform sampler2D shadowMap_texture; \n';
  166. }
  167. var returnPositionEC;
  168. if (hasPositionVarying) {
  169. returnPositionEC = ' return vec4(' + positionVaryingName + ', 1.0); \n';
  170. } else {
  171. returnPositionEC =
  172. '#ifndef LOG_DEPTH \n' +
  173. ' return czm_windowToEyeCoordinates(gl_FragCoord); \n' +
  174. '#else \n' +
  175. ' return vec4(v_logPositionEC, 1.0); \n' +
  176. '#endif \n';
  177. }
  178. fsSource +=
  179. 'uniform mat4 shadowMap_matrix; \n' +
  180. 'uniform vec3 shadowMap_lightDirectionEC; \n' +
  181. 'uniform vec4 shadowMap_lightPositionEC; \n' +
  182. 'uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness; \n' +
  183. 'uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth; \n' +
  184. '#ifdef LOG_DEPTH \n' +
  185. 'varying vec3 v_logPositionEC; \n' +
  186. '#endif \n' +
  187. 'vec4 getPositionEC() \n' +
  188. '{ \n' +
  189. returnPositionEC +
  190. '} \n' +
  191. 'vec3 getNormalEC() \n' +
  192. '{ \n' +
  193. (hasNormalVarying ?
  194. ' return normalize(' + normalVaryingName + '); \n' :
  195. ' return vec3(1.0); \n') +
  196. '} \n' +
  197. // Offset the shadow position in the direction of the normal for perpendicular and back faces
  198. 'void applyNormalOffset(inout vec4 positionEC, vec3 normalEC, float nDotL) \n' +
  199. '{ \n' +
  200. (bias.normalOffset && hasNormalVarying ?
  201. ' float normalOffset = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.x; \n' +
  202. ' float normalOffsetScale = 1.0 - nDotL; \n' +
  203. ' vec3 offset = normalOffset * normalOffsetScale * normalEC; \n' +
  204. ' positionEC.xyz += offset; \n' : '') +
  205. '} \n';
  206. fsSource +=
  207. 'void main() \n' +
  208. '{ \n' +
  209. ' czm_shadow_receive_main(); \n' +
  210. ' vec4 positionEC = getPositionEC(); \n' +
  211. ' vec3 normalEC = getNormalEC(); \n' +
  212. ' float depth = -positionEC.z; \n';
  213. fsSource +=
  214. ' czm_shadowParameters shadowParameters; \n' +
  215. ' shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy; \n' +
  216. ' shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z; \n' +
  217. ' shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w; \n' +
  218. ' shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w; \n';
  219. if (isTerrain) {
  220. // Scale depth bias based on view distance to reduce z-fighting in distant terrain
  221. fsSource += ' shadowParameters.depthBias *= max(depth * 0.01, 1.0); \n';
  222. } else if (!polygonOffsetSupported) {
  223. // If polygon offset isn't supported push the depth back based on view, however this
  224. // causes light leaking at further away views
  225. fsSource += ' shadowParameters.depthBias *= mix(1.0, 100.0, depth * 0.0015); \n';
  226. }
  227. if (isPointLight) {
  228. fsSource +=
  229. ' vec3 directionEC = positionEC.xyz - shadowMap_lightPositionEC.xyz; \n' +
  230. ' float distance = length(directionEC); \n' +
  231. ' directionEC = normalize(directionEC); \n' +
  232. ' float radius = shadowMap_lightPositionEC.w; \n' +
  233. ' // Stop early if the fragment is beyond the point light radius \n' +
  234. ' if (distance > radius) \n' +
  235. ' { \n' +
  236. ' return; \n' +
  237. ' } \n' +
  238. ' vec3 directionWC = czm_inverseViewRotation * directionEC; \n' +
  239. ' shadowParameters.depth = distance / radius; \n' +
  240. ' shadowParameters.nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +
  241. ' shadowParameters.texCoords = directionWC; \n' +
  242. ' float visibility = czm_shadowVisibility(shadowMap_textureCube, shadowParameters); \n';
  243. } else if (isSpotLight) {
  244. fsSource +=
  245. ' vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz); \n' +
  246. ' float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +
  247. ' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
  248. ' vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +
  249. ' // Spot light uses a perspective projection, so perform the perspective divide \n' +
  250. ' shadowPosition /= shadowPosition.w; \n' +
  251. ' // Stop early if the fragment is not in the shadow bounds \n' +
  252. ' if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
  253. ' { \n' +
  254. ' return; \n' +
  255. ' } \n' +
  256. ' shadowParameters.texCoords = shadowPosition.xy; \n' +
  257. ' shadowParameters.depth = shadowPosition.z; \n' +
  258. ' shadowParameters.nDotL = nDotL; \n' +
  259. ' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
  260. } else if (hasCascades) {
  261. fsSource +=
  262. ' float maxDepth = shadowMap_cascadeSplits[1].w; \n' +
  263. ' // Stop early if the eye depth exceeds the last cascade \n' +
  264. ' if (depth > maxDepth) \n' +
  265. ' { \n' +
  266. ' return; \n' +
  267. ' } \n' +
  268. ' // Get the cascade based on the eye-space depth \n' +
  269. ' vec4 weights = czm_cascadeWeights(depth); \n' +
  270. ' // Apply normal offset \n' +
  271. ' float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
  272. ' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
  273. ' // Transform position into the cascade \n' +
  274. ' vec4 shadowPosition = czm_cascadeMatrix(weights) * positionEC; \n' +
  275. ' // Get visibility \n' +
  276. ' shadowParameters.texCoords = shadowPosition.xy; \n' +
  277. ' shadowParameters.depth = shadowPosition.z; \n' +
  278. ' shadowParameters.nDotL = nDotL; \n' +
  279. ' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n' +
  280. ' // Fade out shadows that are far away \n' +
  281. ' float shadowMapMaximumDistance = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.z; \n' +
  282. ' float fade = max((depth - shadowMapMaximumDistance * 0.8) / (shadowMapMaximumDistance * 0.2), 0.0); \n' +
  283. ' visibility = mix(visibility, 1.0, fade); \n' +
  284. (debugCascadeColors ?
  285. ' // Draw cascade colors for debugging \n' +
  286. ' gl_FragColor *= czm_cascadeColor(weights); \n' : '');
  287. } else {
  288. fsSource +=
  289. ' float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
  290. ' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
  291. ' vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +
  292. ' // Stop early if the fragment is not in the shadow bounds \n' +
  293. ' if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
  294. ' { \n' +
  295. ' return; \n' +
  296. ' } \n' +
  297. ' shadowParameters.texCoords = shadowPosition.xy; \n' +
  298. ' shadowParameters.depth = shadowPosition.z; \n' +
  299. ' shadowParameters.nDotL = nDotL; \n' +
  300. ' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
  301. }
  302. fsSource +=
  303. ' gl_FragColor.rgb *= visibility; \n' +
  304. '} \n';
  305. sources.push(fsSource);
  306. return new ShaderSource({
  307. defines : defines,
  308. sources : sources
  309. });
  310. };
  311. export default ShadowMapShader;