AtmosphereCommon.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. //This file is automatically rebuilt by the Cesium build process.
  2. export default "uniform vec3 u_radiiAndDynamicAtmosphereColor;\n\
  3. \n\
  4. uniform float u_atmosphereLightIntensity;\n\
  5. uniform float u_atmosphereRayleighScaleHeight;\n\
  6. uniform float u_atmosphereMieScaleHeight;\n\
  7. uniform float u_atmosphereMieAnisotropy;\n\
  8. uniform vec3 u_atmosphereRayleighCoefficient;\n\
  9. uniform vec3 u_atmosphereMieCoefficient;\n\
  10. \n\
  11. const float ATMOSPHERE_THICKNESS = 111e3; // The thickness of the atmosphere in meters.\n\
  12. const int PRIMARY_STEPS_MAX = 16; // Maximum number of times the ray from the camera to the world position (primary ray) is sampled.\n\
  13. const int LIGHT_STEPS_MAX = 4; // Maximum number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray.\n\
  14. \n\
  15. /**\n\
  16. * Rational approximation to tanh(x)\n\
  17. */\n\
  18. float approximateTanh(float x) {\n\
  19. float x2 = x * x;\n\
  20. return max(-1.0, min(+1.0, x * (27.0 + x2) / (27.0 + 9.0 * x2)));\n\
  21. }\n\
  22. \n\
  23. /**\n\
  24. * This function computes the colors contributed by Rayliegh and Mie scattering on a given ray, as well as\n\
  25. * the transmittance value for the ray.\n\
  26. *\n\
  27. * @param {czm_ray} primaryRay The ray from the camera to the position.\n\
  28. * @param {float} primaryRayLength The length of the primary ray.\n\
  29. * @param {vec3} lightDirection The direction of the light to calculate the scattering from.\n\
  30. * @param {vec3} rayleighColor The variable the Rayleigh scattering will be written to.\n\
  31. * @param {vec3} mieColor The variable the Mie scattering will be written to.\n\
  32. * @param {float} opacity The variable the transmittance will be written to.\n\
  33. * @glslFunction\n\
  34. */\n\
  35. void computeScattering(\n\
  36. czm_ray primaryRay,\n\
  37. float primaryRayLength,\n\
  38. vec3 lightDirection,\n\
  39. float atmosphereInnerRadius,\n\
  40. out vec3 rayleighColor,\n\
  41. out vec3 mieColor,\n\
  42. out float opacity\n\
  43. ) {\n\
  44. \n\
  45. // Initialize the default scattering amounts to 0.\n\
  46. rayleighColor = vec3(0.0);\n\
  47. mieColor = vec3(0.0);\n\
  48. opacity = 0.0;\n\
  49. \n\
  50. float atmosphereOuterRadius = atmosphereInnerRadius + ATMOSPHERE_THICKNESS;\n\
  51. \n\
  52. vec3 origin = vec3(0.0);\n\
  53. \n\
  54. // Calculate intersection from the camera to the outer ring of the atmosphere.\n\
  55. czm_raySegment primaryRayAtmosphereIntersect = czm_raySphereIntersectionInterval(primaryRay, origin, atmosphereOuterRadius);\n\
  56. \n\
  57. // Return empty colors if no intersection with the atmosphere geometry.\n\
  58. if (primaryRayAtmosphereIntersect == czm_emptyRaySegment) {\n\
  59. return;\n\
  60. }\n\
  61. \n\
  62. // To deal with smaller values of PRIMARY_STEPS (e.g. 4)\n\
  63. // we implement a split strategy: sky or horizon.\n\
  64. // For performance reasons, instead of a if/else branch\n\
  65. // a soft choice is implemented through a weight 0.0 <= w_stop_gt_lprl <= 1.0\n\
  66. float x = 1e-7 * primaryRayAtmosphereIntersect.stop / length(primaryRayLength);\n\
  67. // Value close to 0.0: close to the horizon\n\
  68. // Value close to 1.0: above in the sky\n\
  69. float w_stop_gt_lprl = 0.5 * (1.0 + approximateTanh(x));\n\
  70. \n\
  71. // The ray should start from the first intersection with the outer atmopshere, or from the camera position, if it is inside the atmosphere.\n\
  72. float start_0 = primaryRayAtmosphereIntersect.start;\n\
  73. primaryRayAtmosphereIntersect.start = max(primaryRayAtmosphereIntersect.start, 0.0);\n\
  74. // The ray should end at the exit from the atmosphere or at the distance to the vertex, whichever is smaller.\n\
  75. primaryRayAtmosphereIntersect.stop = min(primaryRayAtmosphereIntersect.stop, length(primaryRayLength));\n\
  76. \n\
  77. // For the number of ray steps, distinguish inside or outside atmosphere (outer space)\n\
  78. // (1) from outer space we have to use more ray steps to get a realistic rendering\n\
  79. // (2) within atmosphere we need fewer steps for faster rendering\n\
  80. float x_o_a = start_0 - ATMOSPHERE_THICKNESS; // ATMOSPHERE_THICKNESS used as an ad-hoc constant, no precise meaning here, only the order of magnitude matters\n\
  81. float w_inside_atmosphere = 1.0 - 0.5 * (1.0 + approximateTanh(x_o_a));\n\
  82. int PRIMARY_STEPS = PRIMARY_STEPS_MAX - int(w_inside_atmosphere * 12.0); // Number of times the ray from the camera to the world position (primary ray) is sampled.\n\
  83. int LIGHT_STEPS = LIGHT_STEPS_MAX - int(w_inside_atmosphere * 2.0); // Number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray.\n\
  84. \n\
  85. // Setup for sampling positions along the ray - starting from the intersection with the outer ring of the atmosphere.\n\
  86. float rayPositionLength = primaryRayAtmosphereIntersect.start;\n\
  87. // (1) Outside the atmosphere: constant rayStepLength\n\
  88. // (2) Inside atmosphere: variable rayStepLength to compensate the rough rendering of the smaller number of ray steps\n\
  89. float totalRayLength = primaryRayAtmosphereIntersect.stop - rayPositionLength;\n\
  90. float rayStepLengthIncrease = w_inside_atmosphere * ((1.0 - w_stop_gt_lprl) * totalRayLength / (float(PRIMARY_STEPS * (PRIMARY_STEPS + 1)) / 2.0));\n\
  91. float rayStepLength = max(1.0 - w_inside_atmosphere, w_stop_gt_lprl) * totalRayLength / max(7.0 * w_inside_atmosphere, float(PRIMARY_STEPS));\n\
  92. \n\
  93. vec3 rayleighAccumulation = vec3(0.0);\n\
  94. vec3 mieAccumulation = vec3(0.0);\n\
  95. vec2 opticalDepth = vec2(0.0);\n\
  96. vec2 heightScale = vec2(u_atmosphereRayleighScaleHeight, u_atmosphereMieScaleHeight);\n\
  97. \n\
  98. // Sample positions on the primary ray.\n\
  99. for (int i = 0; i < PRIMARY_STEPS_MAX; ++i) {\n\
  100. \n\
  101. // The loop should be: for (int i = 0; i < PRIMARY_STEPS; ++i) {...} but WebGL1 cannot\n\
  102. // loop with non-constant condition, so it has to break early instead\n\
  103. if (i >= PRIMARY_STEPS) {\n\
  104. break;\n\
  105. }\n\
  106. \n\
  107. // Calculate sample position along viewpoint ray.\n\
  108. vec3 samplePosition = primaryRay.origin + primaryRay.direction * (rayPositionLength + rayStepLength);\n\
  109. \n\
  110. // Calculate height of sample position above ellipsoid.\n\
  111. float sampleHeight = length(samplePosition) - atmosphereInnerRadius;\n\
  112. \n\
  113. // Calculate and accumulate density of particles at the sample position.\n\
  114. vec2 sampleDensity = exp(-sampleHeight / heightScale) * rayStepLength;\n\
  115. opticalDepth += sampleDensity;\n\
  116. \n\
  117. // Generate ray from the sample position segment to the light source, up to the outer ring of the atmosphere.\n\
  118. czm_ray lightRay = czm_ray(samplePosition, lightDirection);\n\
  119. czm_raySegment lightRayAtmosphereIntersect = czm_raySphereIntersectionInterval(lightRay, origin, atmosphereOuterRadius);\n\
  120. \n\
  121. float lightStepLength = lightRayAtmosphereIntersect.stop / float(LIGHT_STEPS);\n\
  122. float lightPositionLength = 0.0;\n\
  123. \n\
  124. vec2 lightOpticalDepth = vec2(0.0);\n\
  125. \n\
  126. // Sample positions along the light ray, to accumulate incidence of light on the latest sample segment.\n\
  127. for (int j = 0; j < LIGHT_STEPS_MAX; ++j) {\n\
  128. \n\
  129. // The loop should be: for (int j = 0; i < LIGHT_STEPS; ++j) {...} but WebGL1 cannot\n\
  130. // loop with non-constant condition, so it has to break early instead\n\
  131. if (j >= LIGHT_STEPS) {\n\
  132. break;\n\
  133. }\n\
  134. \n\
  135. // Calculate sample position along light ray.\n\
  136. vec3 lightPosition = samplePosition + lightDirection * (lightPositionLength + lightStepLength * 0.5);\n\
  137. \n\
  138. // Calculate height of the light sample position above ellipsoid.\n\
  139. float lightHeight = length(lightPosition) - atmosphereInnerRadius;\n\
  140. \n\
  141. // Calculate density of photons at the light sample position.\n\
  142. lightOpticalDepth += exp(-lightHeight / heightScale) * lightStepLength;\n\
  143. \n\
  144. // Increment distance on light ray.\n\
  145. lightPositionLength += lightStepLength;\n\
  146. }\n\
  147. \n\
  148. // Compute attenuation via the primary ray and the light ray.\n\
  149. vec3 attenuation = exp(-((u_atmosphereMieCoefficient * (opticalDepth.y + lightOpticalDepth.y)) + (u_atmosphereRayleighCoefficient * (opticalDepth.x + lightOpticalDepth.x))));\n\
  150. \n\
  151. // Accumulate the scattering.\n\
  152. rayleighAccumulation += sampleDensity.x * attenuation;\n\
  153. mieAccumulation += sampleDensity.y * attenuation;\n\
  154. \n\
  155. // Increment distance on primary ray.\n\
  156. rayPositionLength += (rayStepLength += rayStepLengthIncrease);\n\
  157. }\n\
  158. \n\
  159. // Compute the scattering amount.\n\
  160. rayleighColor = u_atmosphereRayleighCoefficient * rayleighAccumulation;\n\
  161. mieColor = u_atmosphereMieCoefficient * mieAccumulation;\n\
  162. \n\
  163. // Compute the transmittance i.e. how much light is passing through the atmosphere.\n\
  164. opacity = length(exp(-((u_atmosphereMieCoefficient * opticalDepth.y) + (u_atmosphereRayleighCoefficient * opticalDepth.x))));\n\
  165. }\n\
  166. \n\
  167. vec4 computeAtmosphereColor(\n\
  168. vec3 positionWC,\n\
  169. vec3 lightDirection,\n\
  170. vec3 rayleighColor,\n\
  171. vec3 mieColor,\n\
  172. float opacity\n\
  173. ) {\n\
  174. // Setup the primary ray: from the camera position to the vertex position.\n\
  175. vec3 cameraToPositionWC = positionWC - czm_viewerPositionWC;\n\
  176. vec3 cameraToPositionWCDirection = normalize(cameraToPositionWC);\n\
  177. \n\
  178. float cosAngle = dot(cameraToPositionWCDirection, lightDirection);\n\
  179. float cosAngleSq = cosAngle * cosAngle;\n\
  180. \n\
  181. float G = u_atmosphereMieAnisotropy;\n\
  182. float GSq = G * G;\n\
  183. \n\
  184. // The Rayleigh phase function.\n\
  185. float rayleighPhase = 3.0 / (50.2654824574) * (1.0 + cosAngleSq);\n\
  186. // The Mie phase function.\n\
  187. float miePhase = 3.0 / (25.1327412287) * ((1.0 - GSq) * (cosAngleSq + 1.0)) / (pow(1.0 + GSq - 2.0 * cosAngle * G, 1.5) * (2.0 + GSq));\n\
  188. \n\
  189. // The final color is generated by combining the effects of the Rayleigh and Mie scattering.\n\
  190. vec3 rayleigh = rayleighPhase * rayleighColor;\n\
  191. vec3 mie = miePhase * mieColor;\n\
  192. \n\
  193. vec3 color = (rayleigh + mie) * u_atmosphereLightIntensity;\n\
  194. \n\
  195. return vec4(color, opacity);\n\
  196. }\n\
  197. ";