CloudCollectionFS.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //This file is automatically rebuilt by the Cesium build process.
  2. export default "uniform sampler2D u_noiseTexture;\n\
  3. uniform vec3 u_noiseTextureDimensions;\n\
  4. uniform float u_noiseDetail;\n\
  5. varying vec2 v_offset;\n\
  6. varying vec3 v_maximumSize;\n\
  7. varying vec4 v_color;\n\
  8. varying float v_slice;\n\
  9. varying float v_brightness;\n\
  10. \n\
  11. float wrap(float value, float rangeLength) {\n\
  12. if(value < 0.0) {\n\
  13. float absValue = abs(value);\n\
  14. float modValue = mod(absValue, rangeLength);\n\
  15. return mod(rangeLength - modValue, rangeLength);\n\
  16. }\n\
  17. return mod(value, rangeLength);\n\
  18. }\n\
  19. \n\
  20. vec3 wrapVec(vec3 value, float rangeLength) {\n\
  21. return vec3(wrap(value.x, rangeLength),\n\
  22. wrap(value.y, rangeLength),\n\
  23. wrap(value.z, rangeLength));\n\
  24. }\n\
  25. \n\
  26. float textureSliceWidth = u_noiseTextureDimensions.x;\n\
  27. float noiseTextureRows = u_noiseTextureDimensions.y;\n\
  28. float inverseNoiseTextureRows = u_noiseTextureDimensions.z;\n\
  29. \n\
  30. float textureSliceWidthSquared = textureSliceWidth * textureSliceWidth;\n\
  31. vec2 inverseNoiseTextureDimensions = vec2(noiseTextureRows / textureSliceWidthSquared,\n\
  32. inverseNoiseTextureRows / textureSliceWidth);\n\
  33. \n\
  34. vec2 voxelToUV(vec3 voxelIndex) {\n\
  35. vec3 wrappedIndex = wrapVec(voxelIndex, textureSliceWidth);\n\
  36. float column = mod(wrappedIndex.z, textureSliceWidth * inverseNoiseTextureRows);\n\
  37. float row = floor(wrappedIndex.z / textureSliceWidth * noiseTextureRows);\n\
  38. \n\
  39. float xPixelCoord = wrappedIndex.x + column * textureSliceWidth;\n\
  40. float yPixelCoord = wrappedIndex.y + row * textureSliceWidth;\n\
  41. return vec2(xPixelCoord, yPixelCoord) * inverseNoiseTextureDimensions;\n\
  42. }\n\
  43. \n\
  44. // Interpolate a voxel with its neighbor (along the positive X-axis)\n\
  45. vec4 lerpSamplesX(vec3 voxelIndex, float x) {\n\
  46. vec2 uv0 = voxelToUV(voxelIndex);\n\
  47. vec2 uv1 = voxelToUV(voxelIndex + vec3(1.0, 0.0, 0.0));\n\
  48. vec4 sample0 = texture2D(u_noiseTexture, uv0);\n\
  49. vec4 sample1 = texture2D(u_noiseTexture, uv1);\n\
  50. return mix(sample0, sample1, x);\n\
  51. }\n\
  52. \n\
  53. vec4 sampleNoiseTexture(vec3 position) {\n\
  54. vec3 recenteredPos = position + vec3(textureSliceWidth / 2.0);\n\
  55. vec3 lerpValue = fract(recenteredPos);\n\
  56. vec3 voxelIndex = floor(recenteredPos);\n\
  57. \n\
  58. vec4 xLerp00 = lerpSamplesX(voxelIndex, lerpValue.x);\n\
  59. vec4 xLerp01 = lerpSamplesX(voxelIndex + vec3(0.0, 0.0, 1.0), lerpValue.x);\n\
  60. vec4 xLerp10 = lerpSamplesX(voxelIndex + vec3(0.0, 1.0, 0.0), lerpValue.x);\n\
  61. vec4 xLerp11 = lerpSamplesX(voxelIndex + vec3(0.0, 1.0, 1.0), lerpValue.x);\n\
  62. \n\
  63. vec4 yLerp0 = mix(xLerp00, xLerp10, lerpValue.y);\n\
  64. vec4 yLerp1 = mix(xLerp01, xLerp11, lerpValue.y);\n\
  65. return mix(yLerp0, yLerp1, lerpValue.z);\n\
  66. }\n\
  67. \n\
  68. // Intersection with a unit sphere with radius 0.5 at center (0, 0, 0).\n\
  69. bool intersectSphere(vec3 origin, vec3 dir, float slice,\n\
  70. out vec3 point, out vec3 normal) {\n\
  71. float A = dot(dir, dir);\n\
  72. float B = dot(origin, dir);\n\
  73. float C = dot(origin, origin) - 0.25;\n\
  74. float discriminant = (B * B) - (A * C);\n\
  75. if(discriminant < 0.0) {\n\
  76. return false;\n\
  77. }\n\
  78. float root = sqrt(discriminant);\n\
  79. float t = (-B - root) / A;\n\
  80. if(t < 0.0) {\n\
  81. t = (-B + root) / A;\n\
  82. }\n\
  83. point = origin + t * dir;\n\
  84. \n\
  85. if(slice >= 0.0) {\n\
  86. point.z = (slice / 2.0) - 0.5;\n\
  87. if(length(point) > 0.5) {\n\
  88. return false;\n\
  89. }\n\
  90. }\n\
  91. \n\
  92. normal = normalize(point);\n\
  93. point -= czm_epsilon2 * normal;\n\
  94. return true;\n\
  95. }\n\
  96. \n\
  97. // Transforms the ray origin and direction into unit sphere space,\n\
  98. // then transforms the result back into the ellipsoid's space.\n\
  99. bool intersectEllipsoid(vec3 origin, vec3 dir, vec3 center, vec3 scale, float slice,\n\
  100. out vec3 point, out vec3 normal) {\n\
  101. if(scale.x <= 0.01 || scale.y < 0.01 || scale.z < 0.01) {\n\
  102. return false;\n\
  103. }\n\
  104. \n\
  105. vec3 o = (origin - center) / scale;\n\
  106. vec3 d = dir / scale;\n\
  107. vec3 p, n;\n\
  108. bool intersected = intersectSphere(o, d, slice, p, n);\n\
  109. if(intersected) {\n\
  110. point = (p * scale) + center;\n\
  111. normal = n;\n\
  112. }\n\
  113. return intersected;\n\
  114. }\n\
  115. \n\
  116. // Assume that if phase shift is being called for octave i,\n\
  117. // the frequency is of i - 1. This saves us from doing extra\n\
  118. // division / multiplication operations.\n\
  119. vec2 phaseShift2D(vec2 p, vec2 freq) {\n\
  120. return (czm_pi / 2.0) * sin(freq.yx * p.yx);\n\
  121. }\n\
  122. \n\
  123. vec2 phaseShift3D(vec3 p, vec2 freq) {\n\
  124. return phaseShift2D(p.xy, freq) + czm_pi * vec2(sin(freq.x * p.z));\n\
  125. }\n\
  126. \n\
  127. // The cloud texture function derived from Gardner's 1985 paper,\n\
  128. // \"Visual Simulation of Clouds.\"\n\
  129. // https://www.cs.drexel.edu/~david/Classes/Papers/p297-gardner.pdf\n\
  130. const float T0 = 0.6; // contrast of the texture pattern\n\
  131. const float k = 0.1; // computed to produce a maximum value of 1\n\
  132. const float C0 = 0.8; // coefficient\n\
  133. const float FX0 = 0.6; // frequency X\n\
  134. const float FY0 = 0.6; // frequency Y\n\
  135. const int octaves = 5;\n\
  136. \n\
  137. float T(vec3 point) {\n\
  138. vec2 sum = vec2(0.0);\n\
  139. float Ci = C0;\n\
  140. vec2 FXY = vec2(FX0, FY0);\n\
  141. vec2 PXY = vec2(0.0);\n\
  142. for(int i = 1; i <= octaves; i++) {\n\
  143. PXY = phaseShift3D(point, FXY);\n\
  144. Ci *= 0.707;\n\
  145. FXY *= 2.0;\n\
  146. vec2 sinTerm = sin(FXY * point.xy + PXY);\n\
  147. sum += Ci * sinTerm + vec2(T0);\n\
  148. }\n\
  149. return k * sum.x * sum.y;\n\
  150. }\n\
  151. \n\
  152. const float a = 0.5; // fraction of surface reflection due to ambient or scattered light,\n\
  153. const float t = 0.4; // fraction of texture shading\n\
  154. const float s = 0.25; // fraction of specular reflection\n\
  155. \n\
  156. float I(float Id, float Is, float It) {\n\
  157. return (1.0 - a) * ((1.0 - t) * ((1.0 - s) * Id + s * Is) + t * It) + a;\n\
  158. }\n\
  159. \n\
  160. const vec3 lightDir = normalize(vec3(0.2, -1.0, 0.7));\n\
  161. \n\
  162. vec4 drawCloud(vec3 rayOrigin, vec3 rayDir, vec3 cloudCenter, vec3 cloudScale, float cloudSlice,\n\
  163. float brightness) {\n\
  164. vec3 cloudPoint, cloudNormal;\n\
  165. if(!intersectEllipsoid(rayOrigin, rayDir, cloudCenter, cloudScale, cloudSlice,\n\
  166. cloudPoint, cloudNormal)) {\n\
  167. return vec4(0.0);\n\
  168. }\n\
  169. \n\
  170. float Id = clamp(dot(cloudNormal, -lightDir), 0.0, 1.0); // diffuse reflection\n\
  171. float Is = max(pow(dot(-lightDir, -rayDir), 2.0), 0.0); // specular reflection\n\
  172. float It = T(cloudPoint); // texture function\n\
  173. float intensity = I(Id, Is, It);\n\
  174. vec3 color = vec3(intensity * clamp(brightness, 0.1, 1.0));\n\
  175. \n\
  176. vec4 noise = sampleNoiseTexture(u_noiseDetail * cloudPoint);\n\
  177. float W = noise.x;\n\
  178. float W2 = noise.y;\n\
  179. float W3 = noise.z;\n\
  180. \n\
  181. // The dot product between the cloud's normal and the ray's direction is greatest\n\
  182. // in the center of the ellipsoid's surface. It decreases towards the edge.\n\
  183. // Thus, it is used to blur the areas leading to the edges of the ellipsoid,\n\
  184. // so that no harsh lines appear.\n\
  185. \n\
  186. // The first (and biggest) layer of worley noise is then subtracted from this.\n\
  187. // The final result is scaled up so that the base cloud is not too translucent.\n\
  188. float ndDot = clamp(dot(cloudNormal, -rayDir), 0.0, 1.0);\n\
  189. float TR = pow(ndDot, 3.0) - W; // translucency\n\
  190. TR *= 1.3;\n\
  191. \n\
  192. // Subtracting the second and third layers of worley noise is more complicated.\n\
  193. // If these layers of noise were simply subtracted from the current translucency,\n\
  194. // the shape derived from the first layer of noise would be completely deleted.\n\
  195. // The erosion of this noise should thus be constricted to the edges of the cloud.\n\
  196. // However, because the edges of the ellipsoid were already blurred away, mapping\n\
  197. // the noise to (1.0 - ndDot) will have no impact on most of the cloud's appearance.\n\
  198. // The value of (0.5 - ndDot) provides the best compromise.\n\
  199. float minusDot = 0.5 - ndDot;\n\
  200. \n\
  201. // Even with the previous calculation, subtracting the second layer of wnoise\n\
  202. // erode too much of the cloud. The addition of it, however, will detailed\n\
  203. // volume to the cloud. As long as the noise is only added and not subtracted,\n\
  204. // the results are aesthetically pleasing.\n\
  205. \n\
  206. // The minusDot product is mapped in a way that it is larger at the edges of\n\
  207. // the ellipsoid, so a subtraction and min operation are used instead of\n\
  208. // an addition and max one.\n\
  209. TR -= min(minusDot * W2, 0.0);\n\
  210. \n\
  211. // The third level of worley noise is subtracted from the result, with some\n\
  212. // modifications. First, a scalar is added to minusDot so that the noise\n\
  213. // starts affecting the shape farther away from the center of the ellipsoid's\n\
  214. // surface. Then, it is scaled down so its impact is not too intense.\n\
  215. TR -= 0.8 * (minusDot + 0.25) * W3;\n\
  216. \n\
  217. // The texture function's shading does not correlate with the shape of the cloud\n\
  218. // produced by the layers of noise, so an extra shading scalar is calculated.\n\
  219. // The darkest areas of the cloud are assigned to be where the noise erodes\n\
  220. // the cloud the most. This is then interpolated based on the translucency\n\
  221. // and the diffuse shading term of that point in the cloud.\n\
  222. float shading = mix(1.0 - 0.8 * W * W, 1.0, Id * TR);\n\
  223. \n\
  224. // To avoid values that are too dark, this scalar is increased by a small amount\n\
  225. // and clamped so it never goes to zero.\n\
  226. shading = clamp(shading + 0.2, 0.3, 1.0);\n\
  227. \n\
  228. // Finally, the contrast of the cloud's color is increased.\n\
  229. vec3 finalColor = mix(vec3(0.5), shading * color, 1.15);\n\
  230. return vec4(finalColor, clamp(TR, 0.0, 1.0)) * v_color;\n\
  231. }\n\
  232. \n\
  233. void main() {\n\
  234. #ifdef DEBUG_BILLBOARDS\n\
  235. gl_FragColor = vec4(0.0, 0.5, 0.5, 1.0);\n\
  236. #endif\n\
  237. // To avoid calculations with high values,\n\
  238. // we raycast from an arbitrarily smaller space.\n\
  239. vec2 coordinate = v_maximumSize.xy * v_offset;\n\
  240. \n\
  241. vec3 ellipsoidScale = 0.82 * v_maximumSize;\n\
  242. vec3 ellipsoidCenter = vec3(0.0);\n\
  243. \n\
  244. float zOffset = max(ellipsoidScale.z - 10.0, 0.0);\n\
  245. vec3 eye = vec3(0, 0, -10.0 - zOffset);\n\
  246. vec3 rayDir = normalize(vec3(coordinate, 1.0) - eye);\n\
  247. vec3 rayOrigin = eye;\n\
  248. #ifdef DEBUG_ELLIPSOIDS\n\
  249. vec3 point, normal;\n\
  250. if(intersectEllipsoid(rayOrigin, rayDir, ellipsoidCenter, ellipsoidScale, v_slice,\n\
  251. point, normal)) {\n\
  252. gl_FragColor = v_brightness * v_color;\n\
  253. }\n\
  254. #else\n\
  255. #ifndef DEBUG_BILLBOARDS\n\
  256. vec4 cloud = drawCloud(rayOrigin, rayDir,\n\
  257. ellipsoidCenter, ellipsoidScale, v_slice, v_brightness);\n\
  258. if(cloud.w < 0.01) {\n\
  259. discard;\n\
  260. }\n\
  261. gl_FragColor = cloud;\n\
  262. #endif\n\
  263. #endif\n\
  264. }\n\
  265. ";