@tinypoly/gpu-renderer 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/ARCHITECTURE.md +339 -0
  2. package/LICENSE +21 -0
  3. package/README.md +284 -2
  4. package/dist/accumulator.d.ts +27 -0
  5. package/dist/accumulator.d.ts.map +1 -0
  6. package/dist/accumulator.js +57 -0
  7. package/dist/accumulator.js.map +1 -0
  8. package/dist/bvh.d.ts +26 -0
  9. package/dist/bvh.d.ts.map +1 -0
  10. package/dist/bvh.js +296 -0
  11. package/dist/bvh.js.map +1 -0
  12. package/dist/bvhBuilder.d.ts +72 -0
  13. package/dist/bvhBuilder.d.ts.map +1 -0
  14. package/dist/bvhBuilder.js +126 -0
  15. package/dist/bvhBuilder.js.map +1 -0
  16. package/dist/bvhWorker.d.ts +2 -0
  17. package/dist/bvhWorker.d.ts.map +1 -0
  18. package/dist/bvhWorker.js +8 -0
  19. package/dist/bvhWorker.js.map +1 -0
  20. package/dist/camera.d.ts +29 -0
  21. package/dist/camera.d.ts.map +1 -0
  22. package/dist/camera.js +48 -0
  23. package/dist/camera.js.map +1 -0
  24. package/dist/canvas.d.ts +9 -0
  25. package/dist/canvas.d.ts.map +1 -0
  26. package/dist/canvas.js +11 -0
  27. package/dist/canvas.js.map +1 -0
  28. package/dist/denoise.d.ts +69 -0
  29. package/dist/denoise.d.ts.map +1 -0
  30. package/dist/denoise.js +455 -0
  31. package/dist/denoise.js.map +1 -0
  32. package/dist/device.d.ts +37 -0
  33. package/dist/device.d.ts.map +1 -0
  34. package/dist/device.js +52 -0
  35. package/dist/device.js.map +1 -0
  36. package/dist/dispatchScheduler.d.ts +43 -0
  37. package/dist/dispatchScheduler.d.ts.map +1 -0
  38. package/dist/dispatchScheduler.js +49 -0
  39. package/dist/dispatchScheduler.js.map +1 -0
  40. package/dist/environment.d.ts +39 -0
  41. package/dist/environment.d.ts.map +1 -0
  42. package/dist/environment.js +168 -0
  43. package/dist/environment.js.map +1 -0
  44. package/dist/frameLayout.d.ts +4 -0
  45. package/dist/frameLayout.d.ts.map +1 -0
  46. package/dist/frameLayout.js +58 -0
  47. package/dist/frameLayout.js.map +1 -0
  48. package/dist/geometry.d.ts +75 -0
  49. package/dist/geometry.d.ts.map +1 -0
  50. package/dist/geometry.js +799 -0
  51. package/dist/geometry.js.map +1 -0
  52. package/dist/index.d.ts +27 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +19 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/lights.d.ts +64 -0
  57. package/dist/lights.d.ts.map +1 -0
  58. package/dist/lights.js +264 -0
  59. package/dist/lights.js.map +1 -0
  60. package/dist/materialLayout.d.ts +143 -0
  61. package/dist/materialLayout.d.ts.map +1 -0
  62. package/dist/materialLayout.js +297 -0
  63. package/dist/materialLayout.js.map +1 -0
  64. package/dist/math.d.ts +10 -0
  65. package/dist/math.d.ts.map +1 -0
  66. package/dist/math.js +40 -0
  67. package/dist/math.js.map +1 -0
  68. package/dist/pathTracePass.d.ts +27 -0
  69. package/dist/pathTracePass.d.ts.map +1 -0
  70. package/dist/pathTracePass.js +83 -0
  71. package/dist/pathTracePass.js.map +1 -0
  72. package/dist/planarCapture.d.ts +38 -0
  73. package/dist/planarCapture.d.ts.map +1 -0
  74. package/dist/planarCapture.js +163 -0
  75. package/dist/planarCapture.js.map +1 -0
  76. package/dist/presentPass.d.ts +31 -0
  77. package/dist/presentPass.d.ts.map +1 -0
  78. package/dist/presentPass.js +78 -0
  79. package/dist/presentPass.js.map +1 -0
  80. package/dist/renderer.d.ts +209 -0
  81. package/dist/renderer.d.ts.map +1 -0
  82. package/dist/renderer.js +847 -0
  83. package/dist/renderer.js.map +1 -0
  84. package/dist/sceneAssets.d.ts +24 -0
  85. package/dist/sceneAssets.d.ts.map +1 -0
  86. package/dist/sceneAssets.js +77 -0
  87. package/dist/sceneAssets.js.map +1 -0
  88. package/dist/sceneUpload.d.ts +61 -0
  89. package/dist/sceneUpload.d.ts.map +1 -0
  90. package/dist/sceneUpload.js +98 -0
  91. package/dist/sceneUpload.js.map +1 -0
  92. package/dist/shaderModule.d.ts +7 -0
  93. package/dist/shaderModule.d.ts.map +1 -0
  94. package/dist/shaderModule.js +19 -0
  95. package/dist/shaderModule.js.map +1 -0
  96. package/dist/shaderProgram.d.ts +124 -0
  97. package/dist/shaderProgram.d.ts.map +1 -0
  98. package/dist/shaderProgram.js +1030 -0
  99. package/dist/shaderProgram.js.map +1 -0
  100. package/dist/structLayout.d.ts +28 -0
  101. package/dist/structLayout.d.ts.map +1 -0
  102. package/dist/structLayout.js +58 -0
  103. package/dist/structLayout.js.map +1 -0
  104. package/dist/texturePack.d.ts +54 -0
  105. package/dist/texturePack.d.ts.map +1 -0
  106. package/dist/texturePack.js +86 -0
  107. package/dist/texturePack.js.map +1 -0
  108. package/dist/upstream.d.ts +7 -0
  109. package/dist/upstream.d.ts.map +1 -0
  110. package/dist/upstream.js +11 -0
  111. package/dist/upstream.js.map +1 -0
  112. package/dist/volumetric.d.ts +10 -0
  113. package/dist/volumetric.d.ts.map +1 -0
  114. package/dist/volumetric.js +34 -0
  115. package/dist/volumetric.js.map +1 -0
  116. package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
  117. package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
  118. package/dist/wgsl/bsdf.wgsl.js +619 -0
  119. package/dist/wgsl/bsdf.wgsl.js.map +1 -0
  120. package/dist/wgsl/bvh.wgsl.d.ts +3 -0
  121. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
  122. package/dist/wgsl/bvh.wgsl.js +218 -0
  123. package/dist/wgsl/bvh.wgsl.js.map +1 -0
  124. package/dist/wgsl/common.wgsl.d.ts +3 -0
  125. package/dist/wgsl/common.wgsl.d.ts.map +1 -0
  126. package/dist/wgsl/common.wgsl.js +193 -0
  127. package/dist/wgsl/common.wgsl.js.map +1 -0
  128. package/dist/wgsl/environment.wgsl.d.ts +10 -0
  129. package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
  130. package/dist/wgsl/environment.wgsl.js +145 -0
  131. package/dist/wgsl/environment.wgsl.js.map +1 -0
  132. package/dist/wgsl/lights.wgsl.d.ts +10 -0
  133. package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
  134. package/dist/wgsl/lights.wgsl.js +434 -0
  135. package/dist/wgsl/lights.wgsl.js.map +1 -0
  136. package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
  137. package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
  138. package/dist/wgsl/pathTrace.wgsl.js +1020 -0
  139. package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
  140. package/dist/wgsl/present.wgsl.d.ts +7 -0
  141. package/dist/wgsl/present.wgsl.d.ts.map +1 -0
  142. package/dist/wgsl/present.wgsl.js +154 -0
  143. package/dist/wgsl/present.wgsl.js.map +1 -0
  144. package/dist/wgsl/random.wgsl.d.ts +9 -0
  145. package/dist/wgsl/random.wgsl.d.ts.map +1 -0
  146. package/dist/wgsl/random.wgsl.js +91 -0
  147. package/dist/wgsl/random.wgsl.js.map +1 -0
  148. package/dist/wgsl/sampling.wgsl.d.ts +3 -0
  149. package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
  150. package/dist/wgsl/sampling.wgsl.js +31 -0
  151. package/dist/wgsl/sampling.wgsl.js.map +1 -0
  152. package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
  153. package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
  154. package/dist/wgsl/shaderSupport.wgsl.js +120 -0
  155. package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
  156. package/dist/wgsl/surface.wgsl.d.ts +10 -0
  157. package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
  158. package/dist/wgsl/surface.wgsl.js +794 -0
  159. package/dist/wgsl/surface.wgsl.js.map +1 -0
  160. package/dist/wgsl/texture.wgsl.d.ts +11 -0
  161. package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
  162. package/dist/wgsl/texture.wgsl.js +132 -0
  163. package/dist/wgsl/texture.wgsl.js.map +1 -0
  164. package/package.json +77 -3
  165. package/src/accumulator.ts +64 -0
  166. package/src/bvh.ts +336 -0
  167. package/src/bvhBuilder.ts +178 -0
  168. package/src/bvhWorker.ts +13 -0
  169. package/src/camera.ts +77 -0
  170. package/src/canvas.ts +13 -0
  171. package/src/denoise.ts +514 -0
  172. package/src/device.ts +81 -0
  173. package/src/dispatchScheduler.ts +71 -0
  174. package/src/environment.ts +233 -0
  175. package/src/frameLayout.ts +59 -0
  176. package/src/geometry.ts +1067 -0
  177. package/src/index.ts +64 -0
  178. package/src/lights.ts +365 -0
  179. package/src/materialLayout.ts +408 -0
  180. package/src/math.ts +48 -0
  181. package/src/pathTracePass.ts +97 -0
  182. package/src/planarCapture.ts +225 -0
  183. package/src/presentPass.ts +99 -0
  184. package/src/renderer.ts +1145 -0
  185. package/src/sceneAssets.ts +118 -0
  186. package/src/sceneUpload.ts +185 -0
  187. package/src/shaderModule.ts +19 -0
  188. package/src/shaderProgram.ts +1370 -0
  189. package/src/structLayout.ts +93 -0
  190. package/src/texturePack.ts +152 -0
  191. package/src/upstream.ts +56 -0
  192. package/src/volumetric.ts +42 -0
  193. package/src/wgsl/bsdf.wgsl.ts +618 -0
  194. package/src/wgsl/bvh.wgsl.ts +217 -0
  195. package/src/wgsl/common.wgsl.ts +193 -0
  196. package/src/wgsl/environment.wgsl.ts +144 -0
  197. package/src/wgsl/lights.wgsl.ts +433 -0
  198. package/src/wgsl/pathTrace.wgsl.ts +1019 -0
  199. package/src/wgsl/present.wgsl.ts +153 -0
  200. package/src/wgsl/random.wgsl.ts +90 -0
  201. package/src/wgsl/sampling.wgsl.ts +30 -0
  202. package/src/wgsl/shaderSupport.wgsl.ts +119 -0
  203. package/src/wgsl/surface.wgsl.ts +793 -0
  204. package/src/wgsl/texture.wgsl.ts +131 -0
@@ -0,0 +1,619 @@
1
+ /**
2
+ * Material evaluation and the sampling the CPU renderer has no need for. Its `material.js` only ever asks what a surface
3
+ * reflects towards a light it already knows about; a path tracer also has to ask the opposite question, which direction
4
+ * to continue in, and with what density it chose it.
5
+ *
6
+ * The lobes evaluate as Three r185's direct lighting does (`lights_physical_pars_fragment`, `BRDF_BlinnPhong`, the toon
7
+ * ramp): GGX with its multiple scattering term, anisotropy, sheen and its energy compensation, a clearcoat on its own
8
+ * normal, and an iridescent film blended into the Fresnel. They are sampled with the standard methods for each: cosine
9
+ * for the diffuse lobe, the visible normal distribution (Heitz 2018) for GGX, anisotropic or not, and the Blinn-Phong
10
+ * lobe for Phong, mixed by how much each lobe reflects.
11
+ */
12
+ export const BSDF_WGSL = /* wgsl */ `
13
+ /** How a surface answers a direction: what it reflects, and how likely sampling was to have chosen it. */
14
+ struct BsdfEval {
15
+ // The bidirectional distribution times the cosine at the surface, which is what a contribution is multiplied by.
16
+ value: vec3<f32>,
17
+ // Solid angle density of this direction under this renderer's own sampling, for the power heuristic.
18
+ pdf: f32,
19
+ };
20
+
21
+ struct BsdfSample {
22
+ direction: vec3<f32>,
23
+ // The distribution over its own density: what the path's throughput is multiplied by.
24
+ weight: vec3<f32>,
25
+ pdf: f32,
26
+ valid: bool,
27
+ // The diffuse lobe chose the direction, rather than a specular one: which of the denoiser's two signals it feeds.
28
+ diffuse: bool,
29
+ };
30
+
31
+ fn luminanceOf(color: vec3<f32>) -> f32 {
32
+ return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
33
+ }
34
+
35
+ fn max3(color: vec3<f32>) -> f32 {
36
+ return max(color.r, max(color.g, color.b));
37
+ }
38
+
39
+ /** Schlick's approximation, as in Three: the reflectance always reaches one at a grazing angle. */
40
+ fn fresnelSchlick(f0: vec3<f32>, cosine: f32) -> vec3<f32> {
41
+ return schlick3(f0, 1.0, cosine);
42
+ }
43
+
44
+ /** Reflectance at normal incidence: the dielectric's own, turned towards the base colour as the surface turns metal. */
45
+ fn surfaceF0(surface: Surface) -> vec3<f32> {
46
+ return mix(surface.specularColor, surface.albedo, surface.metalness);
47
+ }
48
+
49
+ /** GGX, with alpha the square of the roughness, as Three parameterises it. */
50
+ fn ggxDistribution(alpha2: f32, nh: f32) -> f32 {
51
+ let denominator = nh * nh * (alpha2 - 1.0) + 1.0;
52
+
53
+ return alpha2 / (PI * denominator * denominator);
54
+ }
55
+
56
+ /** Smith's height correlated visibility, which already carries the 1/(4 nl nv) the lobe would otherwise need. */
57
+ fn ggxVisibility(alpha2: f32, nl: f32, nv: f32) -> f32 {
58
+ return 0.5 / (nl * sqrt(nv * nv * (1.0 - alpha2) + alpha2) + nv * sqrt(nl * nl * (1.0 - alpha2) + alpha2) + 1e-7);
59
+ }
60
+
61
+ /** The separable masking term, which is the one the visible normal sampling is built on. */
62
+ fn ggxMasking(alpha2: f32, nv: f32) -> f32 {
63
+ return 2.0 * nv / (nv + sqrt(alpha2 + (1.0 - alpha2) * nv * nv));
64
+ }
65
+
66
+ /** Karis' analytic fit of the GGX directional albedo (Three's DFGApprox): the scale and bias applied to F0 and F90. */
67
+ fn dfgApprox(roughness: f32, nv: f32) -> vec2<f32> {
68
+ let r = roughness * vec4<f32>(-1.0, -0.0275, -0.572, 0.022) + vec4<f32>(1.0, 0.0425, 1.04, -0.04);
69
+ let a004 = min(r.x * r.x, exp2(-9.28 * nv)) * r.x + r.y;
70
+
71
+ return vec2<f32>(-1.04, 1.04) * a004 + r.zw;
72
+ }
73
+
74
+ /** Three's IBLSheenBRDF: the Charlie sheen lobe integrated over the hemisphere. */
75
+ fn sheenAlbedo(cosine: f32, roughness: f32) -> f32 {
76
+ let r2 = roughness * roughness;
77
+ let inverse = 1.0 / (roughness + 0.1);
78
+ let a = -1.9362 + 1.0678 * roughness + 0.4573 * r2 - 0.8469 * inverse;
79
+ let b = -0.6014 + 0.5538 * roughness - 0.4670 * r2 - 0.1255 * inverse;
80
+
81
+ return clamp(exp(a * clamp(cosine, 0.0, 1.0) + b), 0.0, 1.0);
82
+ }
83
+
84
+ /** Three's D_Charlie times V_Neubelt. */
85
+ fn sheenLobe(roughness: f32, nh: f32, nv: f32, nl: f32) -> f32 {
86
+ let inverse = 1.0 / (roughness * roughness);
87
+ let sin2h = max(1.0 - nh * nh, 0.0078125);
88
+ let distribution = (2.0 + inverse) * pow(sin2h, inverse * 0.5) / (2.0 * PI);
89
+
90
+ return distribution * clamp(1.0 / (4.0 * (nl + nv - nl * nv)), 0.0, 1.0);
91
+ }
92
+
93
+ /** The frame the specular lobe is defined in: Three's anisotropy direction when there is one, any frame otherwise. */
94
+ fn specularBasis(surface: Surface) -> mat3x3<f32> {
95
+ let normal = surface.normal;
96
+
97
+ if (surface.anisotropy > 0.0) {
98
+ let t = surface.anisotropyT - normal * dot(normal, surface.anisotropyT);
99
+ let tangent = normalize(t);
100
+
101
+ if (all(tangent == tangent) && dot(t, t) > 1e-12) {
102
+ return mat3x3<f32>(tangent, cross(normal, tangent), normal);
103
+ }
104
+ }
105
+
106
+ return orthonormalBasis(normal);
107
+ }
108
+
109
+ /** The GGX roughness along the frame's two axes: Three's alphaT along the anisotropy, alpha across it. */
110
+ fn specularAlphas(surface: Surface) -> vec2<f32> {
111
+ let alpha = surface.roughness * surface.roughness;
112
+
113
+ if (surface.anisotropy > 0.0) {
114
+ return vec2<f32>(max(surface.alphaT, 1e-4), max(alpha, 1e-4));
115
+ }
116
+
117
+ return vec2<f32>(max(alpha, 1e-4));
118
+ }
119
+
120
+ /** Anisotropic GGX in the lobe's own frame; with equal alphas it is exactly the isotropic distribution. */
121
+ fn ggxAnisotropic(h: vec3<f32>, alphas: vec2<f32>) -> f32 {
122
+ let x = h.x / alphas.x;
123
+ let y = h.y / alphas.y;
124
+ let d = x * x + y * y + h.z * h.z;
125
+
126
+ return 1.0 / (PI * alphas.x * alphas.y * d * d);
127
+ }
128
+
129
+ /** Smith's masking of one direction, in the lobe's frame. */
130
+ fn ggxMaskingAnisotropic(v: vec3<f32>, alphas: vec2<f32>) -> f32 {
131
+ let projected = alphas.x * alphas.x * v.x * v.x + alphas.y * alphas.y * v.y * v.y;
132
+
133
+ return 2.0 * v.z / (v.z + sqrt(v.z * v.z + projected));
134
+ }
135
+
136
+ /** Three's V_GGX_SmithCorrelated_Anisotropic, in the lobe's frame. */
137
+ fn ggxVisibilityAnisotropic(l: vec3<f32>, v: vec3<f32>, alphas: vec2<f32>) -> f32 {
138
+ let gv = l.z * length(vec3<f32>(alphas.x * v.x, alphas.y * v.y, v.z));
139
+ let gl = v.z * length(vec3<f32>(alphas.x * l.x, alphas.y * l.y, l.z));
140
+
141
+ return 0.5 / max(gv + gl, 1e-7);
142
+ }
143
+
144
+ /**
145
+ * A half vector drawn from the distribution of normals the view direction can actually see (Heitz 2018), which is what
146
+ * keeps the specular lobe from wasting samples on microfacets facing away. The view is in the lobe's frame.
147
+ */
148
+ fn sampleVisibleNormal(view: vec3<f32>, alphas: vec2<f32>, u: vec2<f32>) -> vec3<f32> {
149
+ let stretched = normalize(vec3<f32>(alphas.x * view.x, alphas.y * view.y, view.z));
150
+ let lengthSquared = stretched.x * stretched.x + stretched.y * stretched.y;
151
+ let t1 = select(
152
+ vec3<f32>(1.0, 0.0, 0.0),
153
+ vec3<f32>(-stretched.y, stretched.x, 0.0) * inverseSqrt(max(lengthSquared, 1e-20)),
154
+ lengthSquared > 1e-20);
155
+ let t2 = cross(stretched, t1);
156
+ let radius = sqrt(u.x);
157
+ let angle = 2.0 * PI * u.y;
158
+ let px = radius * cos(angle);
159
+ let raw = radius * sin(angle);
160
+ let lift = 0.5 * (1.0 + stretched.z);
161
+ let py = (1.0 - lift) * sqrt(max(0.0, 1.0 - px * px)) + lift * raw;
162
+ let normal = t1 * px + t2 * py + stretched * sqrt(max(0.0, 1.0 - px * px - py * py));
163
+
164
+ return normalize(vec3<f32>(alphas.x * normal.x, alphas.y * normal.y, max(normal.z, 1e-6)));
165
+ }
166
+
167
+ /** What the clearcoat takes back from everything under it, seen from this view: Three's (1 - clearcoat * Fcc). */
168
+ fn coatTransmittance(surface: Surface, view: vec3<f32>) -> f32 {
169
+ if (surface.clearcoat <= 0.0) {
170
+ return 1.0;
171
+ }
172
+
173
+ let nv = clamp(dot(surface.clearcoatNormal, view), 0.0, 1.0);
174
+
175
+ return 1.0 - surface.clearcoat * schlick3(vec3<f32>(0.04), 1.0, nv).x;
176
+ }
177
+
178
+ /** How often each lobe is chosen, as (diffuse, specular, clearcoat): by how much of the reflection each accounts for. */
179
+ fn lobeWeights(surface: Surface, view: vec3<f32>) -> vec3<f32> {
180
+ let nv = max(dot(surface.normal, view), 1e-4);
181
+
182
+ if (surface.kind == KIND_PHONG) {
183
+ let diffuse = luminanceOf(surface.albedo) + 0.02;
184
+ let specular = luminanceOf(surface.specularColor) * surface.specularStrength * (surface.shininess * 0.125 + 1.0) + 0.02;
185
+
186
+ return vec3<f32>(diffuse, specular, 0.0) / (diffuse + specular);
187
+ }
188
+
189
+ if ((surface.flags & MATERIAL_PBR) == 0u) {
190
+ return vec3<f32>(1.0, 0.0, 0.0);
191
+ }
192
+
193
+ let base = coatTransmittance(surface, view);
194
+ // The floors keep every lobe reachable: the diffuse one also carries the sheen and the multiple scattering term.
195
+ let diffuse = (luminanceOf(surface.albedo * (1.0 - surface.metalness)) + max3(surface.sheenColor)) * base + 0.02;
196
+ let specular = luminanceOf(schlick3(surface.specularColor, surface.specularF90, nv)
197
+ * (1.0 - surface.metalness) + surface.albedo * surface.metalness) * base + 0.05;
198
+ let coat = select(0.0, (1.0 - base) + 0.02, surface.clearcoat > 0.0);
199
+ let total = diffuse + specular + coat;
200
+
201
+ return vec3<f32>(diffuse, specular, coat) / total;
202
+ }
203
+
204
+ /** Three's physical direct lighting for one light direction, times the cosine. */
205
+ fn physicalValue(surface: Surface, view: vec3<f32>, light: vec3<f32>) -> vec3<f32> {
206
+ let normal = surface.normal;
207
+ let nl = dot(normal, light);
208
+ let nv = max(dot(normal, view), 1e-4);
209
+ var value = vec3<f32>(0.0);
210
+
211
+ if (nl > 0.0) {
212
+ let basis = specularBasis(surface);
213
+ let alphas = specularAlphas(surface);
214
+ let half = normalize(light + view);
215
+ let vh = clamp(dot(view, half), 0.0, 1.0);
216
+ let f0 = surfaceF0(surface);
217
+ var fresnel = schlick3(f0, surface.specularF90, vh);
218
+
219
+ if (surface.iridescence > 0.0) {
220
+ fresnel = mix(fresnel, surface.iridescenceFresnel, surface.iridescence);
221
+ }
222
+
223
+ let localLight = vec3<f32>(dot(light, basis[0]), dot(light, basis[1]), nl);
224
+ let localView = vec3<f32>(dot(view, basis[0]), dot(view, basis[1]), nv);
225
+ let localHalf = vec3<f32>(dot(half, basis[0]), dot(half, basis[1]), max(dot(half, normal), 0.0));
226
+ let single = fresnel * (ggxVisibilityAnisotropic(localLight, localView, alphas) * ggxAnisotropic(localHalf, alphas));
227
+
228
+ // Three's BRDF_GGX_Multiscatter: the energy single scattering loses at high roughness, returned.
229
+ let dfgV = dfgApprox(surface.roughness, nv);
230
+ let dfgL = dfgApprox(surface.roughness, nl);
231
+ let essV = f0 * dfgV.x + surface.specularF90 * dfgV.y;
232
+ let essL = f0 * dfgL.x + surface.specularF90 * dfgL.y;
233
+ let emsV = 1.0 - (dfgV.x + dfgV.y);
234
+ let emsL = 1.0 - (dfgL.x + dfgL.y);
235
+ let average = f0 + (vec3<f32>(1.0) - f0) * 0.047619;
236
+ let multiple = essV * essL * average / (vec3<f32>(1.0) - emsV * emsL * average + 1e-7) * (emsV * emsL);
237
+
238
+ var diffuse = surface.albedo * (1.0 - surface.metalness) / PI;
239
+ var specular = single + multiple;
240
+
241
+ // Three's sheen: its lobe sits on top and takes back the energy it reflects from the layers below.
242
+ if (any(surface.sheenColor > vec3<f32>(0.0))) {
243
+ let energy = 1.0 - max3(surface.sheenColor)
244
+ * max(sheenAlbedo(nv, surface.sheenRoughness), sheenAlbedo(nl, surface.sheenRoughness));
245
+
246
+ diffuse = diffuse * energy;
247
+ specular = specular * energy
248
+ + surface.sheenColor * sheenLobe(surface.sheenRoughness, clamp(dot(normal, half), 0.0, 1.0), nv, nl);
249
+ }
250
+
251
+ value = (diffuse + specular) * nl;
252
+ }
253
+
254
+ if (surface.clearcoat > 0.0) {
255
+ let coatNormal = surface.clearcoatNormal;
256
+ value = value * coatTransmittance(surface, view);
257
+
258
+ let nlCoat = dot(coatNormal, light);
259
+ let nvCoat = max(dot(coatNormal, view), 1e-4);
260
+
261
+ if (nlCoat > 0.0) {
262
+ let half = normalize(light + view);
263
+ let alpha = surface.clearcoatRoughness * surface.clearcoatRoughness;
264
+ let alpha2 = alpha * alpha;
265
+ let fresnel = schlick3(vec3<f32>(0.04), 1.0, clamp(dot(view, half), 0.0, 1.0)).x;
266
+ let lobe = fresnel * ggxVisibility(alpha2, nlCoat, nvCoat) * ggxDistribution(alpha2, max(dot(coatNormal, half), 0.0));
267
+
268
+ value = value + vec3<f32>(surface.clearcoat * lobe * nlCoat);
269
+ }
270
+ }
271
+
272
+ return value;
273
+ }
274
+
275
+ /** Three's D_BlinnPhong times its implicit visibility of a quarter: the Phong highlight's distribution. */
276
+ fn blinnPhong(shininess: f32, nh: f32) -> f32 {
277
+ return 0.25 * (shininess * 0.5 + 1.0) / PI * pow(max(nh, 0.0), shininess);
278
+ }
279
+
280
+ /** Density of the Blinn-Phong lobe's sampling, per solid angle of the light direction. */
281
+ fn blinnPhongPdf(shininess: f32, nh: f32, vh: f32) -> f32 {
282
+ return (shininess + 1.0) / (2.0 * PI) * pow(max(nh, 0.0), shininess) / max(4.0 * vh, 1e-6);
283
+ }
284
+
285
+ /** Density with which the GGX lobe (VNDF) would have produced this light direction, in the lobe's frame. */
286
+ fn ggxPdf(localLight: vec3<f32>, localView: vec3<f32>, alphas: vec2<f32>) -> f32 {
287
+ let half = normalize(localLight + localView);
288
+
289
+ if (half.z <= 0.0 || localView.z <= 0.0) {
290
+ return 0.0;
291
+ }
292
+
293
+ return ggxMaskingAnisotropic(localView, alphas) * ggxAnisotropic(half, alphas) / (4.0 * localView.z);
294
+ }
295
+
296
+ /** The mixture density of every lobe this surface samples with. */
297
+ fn bsdfPdf(surface: Surface, view: vec3<f32>, light: vec3<f32>) -> f32 {
298
+ let normal = surface.normal;
299
+ let nl = dot(normal, light);
300
+ let weights = lobeWeights(surface, view);
301
+ var pdf = weights.x * max(nl, 0.0) / PI;
302
+
303
+ if (surface.kind == KIND_PHONG) {
304
+ let half = normalize(light + view);
305
+
306
+ return pdf + weights.y * blinnPhongPdf(surface.shininess, dot(normal, half), max(dot(view, half), 0.0));
307
+ }
308
+
309
+ if ((surface.flags & MATERIAL_PBR) == 0u) {
310
+ return pdf;
311
+ }
312
+
313
+ let basis = specularBasis(surface);
314
+ let alphas = specularAlphas(surface);
315
+
316
+ pdf = pdf + weights.y * ggxPdf(
317
+ vec3<f32>(dot(light, basis[0]), dot(light, basis[1]), nl),
318
+ vec3<f32>(dot(view, basis[0]), dot(view, basis[1]), dot(view, normal)),
319
+ alphas);
320
+
321
+ if (weights.z > 0.0) {
322
+ let coatBasis = orthonormalBasis(surface.clearcoatNormal);
323
+ let alpha = max(surface.clearcoatRoughness * surface.clearcoatRoughness, 1e-4);
324
+
325
+ pdf = pdf + weights.z * ggxPdf(
326
+ vec3<f32>(dot(light, coatBasis[0]), dot(light, coatBasis[1]), dot(light, surface.clearcoatNormal)),
327
+ vec3<f32>(dot(view, coatBasis[0]), dot(view, coatBasis[1]), dot(view, surface.clearcoatNormal)),
328
+ vec2<f32>(alpha));
329
+ }
330
+
331
+ return pdf;
332
+ }
333
+
334
+ /**
335
+ * What the surface sends towards the view direction, and the density of that direction. A Lambert material keeps
336
+ * Three's Lambert term alone, which is all its own shading has; a toon material reads its ramp in place of the cosine.
337
+ */
338
+ fn evaluateBsdf(surface: Surface, view: vec3<f32>, light: vec3<f32>) -> BsdfEval {
339
+ let normal = surface.normal;
340
+ var result: BsdfEval;
341
+ result.value = vec3<f32>(0.0);
342
+ result.pdf = 0.0;
343
+
344
+ let nl = dot(normal, light);
345
+ let nv = dot(normal, view);
346
+
347
+ if (nv <= 0.0) {
348
+ return result;
349
+ }
350
+
351
+ if (surface.kind == KIND_TOON) {
352
+ // Three's toon lighting reads its ramp across the whole sphere of directions, the side facing away included.
353
+ result.value = surface.albedo / PI * toonRamp(surface.gradientMap, nl);
354
+ result.pdf = max(nl, 0.0) / PI;
355
+
356
+ return result;
357
+ }
358
+
359
+ // A clearcoat may face a light its base turns away from, so only a surface without one stops here.
360
+ if (nl <= 0.0 && surface.clearcoat <= 0.0) {
361
+ return result;
362
+ }
363
+
364
+ if ((surface.flags & MATERIAL_PBR) != 0u) {
365
+ result.value = physicalValue(surface, view, light);
366
+ } else if (surface.kind == KIND_PHONG) {
367
+ let half = normalize(light + view);
368
+ let fresnel = schlick3(surface.specularColor, 1.0, clamp(dot(view, half), 0.0, 1.0));
369
+ let specular = fresnel * blinnPhong(surface.shininess, dot(normal, half)) * surface.specularStrength;
370
+
371
+ result.value = (surface.albedo / PI + specular) * max(nl, 0.0);
372
+ } else {
373
+ result.value = surface.albedo / PI * max(nl, 0.0);
374
+ }
375
+
376
+ result.pdf = bsdfPdf(surface, view, light);
377
+
378
+ return result;
379
+ }
380
+
381
+ /**
382
+ * Reflectance of a smooth dielectric interface, from both polarisations rather than Schlick's fit: refraction needs the
383
+ * exact cut-off past which nothing gets through. eta is the index the ray leaves over the index it enters.
384
+ */
385
+ fn fresnelDielectric(cosine: f32, eta: f32) -> f32 {
386
+ let c = clamp(cosine, 0.0, 1.0);
387
+ let sin2 = eta * eta * (1.0 - c * c);
388
+
389
+ if (sin2 >= 1.0) {
390
+ return 1.0;
391
+ }
392
+
393
+ let t = sqrt(1.0 - sin2);
394
+ let s = (eta * c - t) / (eta * c + t);
395
+ let p = (c - eta * t) / (c + eta * t);
396
+
397
+ return 0.5 * (s * s + p * p);
398
+ }
399
+
400
+ /** Where a transmissive surface sends the path: off it, or through it. */
401
+ struct DielectricSample {
402
+ direction: vec3<f32>,
403
+ weight: vec3<f32>,
404
+ // The path went through the surface instead of reflecting off it.
405
+ crossed: bool,
406
+ // It went through without bending, so the density that found the next hit is still the one before this surface.
407
+ straight: bool,
408
+ valid: bool,
409
+ };
410
+
411
+ /**
412
+ * The transmissive share of a physical material. Reflection and refraction are chosen in proportion to the Fresnel
413
+ * reflectance on a microfacet drawn from the visible normals, which leaves only the masking of the outgoing direction
414
+ * in the weight. Light is tinted by the base colour once, on the way in, which is where Three applies it.
415
+ *
416
+ * A thin wall (zero thickness, Three's default) bends nothing: what is not reflected carries straight on, exactly as a
417
+ * shadow ray crossing it does in thinCrossing, so the two estimators agree on a window. ior is the index this path
418
+ * sees, which dispersion makes depend on the colour the path carries.
419
+ */
420
+ fn sampleDielectric(surface: Surface, view: vec3<f32>, ior: f32, lobe: f32, u: vec2<f32>) -> DielectricSample {
421
+ let normal = surface.normal;
422
+ var result: DielectricSample;
423
+ result.valid = false;
424
+ result.crossed = false;
425
+ result.straight = false;
426
+ result.weight = vec3<f32>(0.0);
427
+
428
+ let nv = dot(normal, view);
429
+
430
+ if (nv <= 0.0) {
431
+ return result;
432
+ }
433
+
434
+ let alpha = max(surface.roughness * surface.roughness, 1e-4);
435
+ let alpha2 = alpha * alpha;
436
+ let basis = orthonormalBasis(normal);
437
+ let localView = vec3<f32>(dot(view, basis[0]), dot(view, basis[1]), nv);
438
+ let localHalf = sampleVisibleNormal(localView, vec2<f32>(alpha), u);
439
+ let half = basis[0] * localHalf.x + basis[1] * localHalf.y + normal * localHalf.z;
440
+
441
+ if (surface.thickness <= 0.0) {
442
+ let reflectance = fresnelDielectric(abs(dot(surface.facing, view)), 1.0 / ior);
443
+
444
+ if (lobe >= reflectance) {
445
+ result.direction = -view;
446
+ result.weight = surface.albedo;
447
+ result.crossed = true;
448
+ result.straight = true;
449
+ result.valid = true;
450
+
451
+ return result;
452
+ }
453
+
454
+ result.direction = reflect(-view, half);
455
+ } else {
456
+ let eta = select(ior, 1.0 / ior, surface.front);
457
+ let refracted = refract(-view, half, eta);
458
+ // Past the critical angle refract() returns nothing, and everything reflects.
459
+ let reflectance = select(fresnelDielectric(dot(view, half), eta), 1.0, all(refracted == vec3<f32>(0.0)));
460
+
461
+ if (lobe >= reflectance) {
462
+ result.direction = normalize(refracted);
463
+
464
+ let no = -dot(normal, result.direction);
465
+
466
+ if (no <= 0.0) {
467
+ return result;
468
+ }
469
+
470
+ result.weight = vec3<f32>(ggxMasking(alpha2, no)) * select(vec3<f32>(1.0), surface.albedo, surface.front);
471
+ result.crossed = true;
472
+ result.valid = true;
473
+
474
+ return result;
475
+ }
476
+
477
+ result.direction = reflect(-view, half);
478
+ }
479
+
480
+ let nl = dot(normal, result.direction);
481
+
482
+ if (nl <= 0.0) {
483
+ return result;
484
+ }
485
+
486
+ result.weight = vec3<f32>(ggxMasking(alpha2, nl));
487
+ result.valid = true;
488
+
489
+ return result;
490
+ }
491
+
492
+ /** A half vector around the normal distributed as cos^s, which is the Blinn-Phong lobe's own shape. */
493
+ fn sampleBlinnPhongHalf(normal: vec3<f32>, shininess: f32, u: vec2<f32>) -> vec3<f32> {
494
+ let cosine = pow(u.x, 1.0 / (shininess + 1.0));
495
+ let sine = sqrt(max(0.0, 1.0 - cosine * cosine));
496
+ let angle = 2.0 * PI * u.y;
497
+
498
+ return normalize(orthonormalBasis(normal) * vec3<f32>(sine * cos(angle), sine * sin(angle), cosine));
499
+ }
500
+
501
+ /** Which way the path carries on, and what its throughput is multiplied by. */
502
+ fn sampleBsdf(surface: Surface, view: vec3<f32>, lobe: f32, u: vec2<f32>) -> BsdfSample {
503
+ let normal = surface.normal;
504
+ var result: BsdfSample;
505
+ result.valid = false;
506
+ result.weight = vec3<f32>(0.0);
507
+ result.pdf = 0.0;
508
+ result.diffuse = true;
509
+
510
+ if (dot(normal, view) <= 0.0) {
511
+ return result;
512
+ }
513
+
514
+ // Lambert, and the bounce of a toon surface: Three lights a toon surface's indirect light with a plain Lambert term.
515
+ if ((surface.flags & MATERIAL_PBR) == 0u && surface.kind != KIND_PHONG) {
516
+ result.direction = cosineDirection(normal, u);
517
+ // The cosine weight cancels the Lambert cosine exactly, which leaves the albedo alone.
518
+ result.weight = surface.albedo;
519
+ result.pdf = max(dot(normal, result.direction), 0.0) / PI;
520
+ result.valid = result.pdf > 0.0;
521
+
522
+ return result;
523
+ }
524
+
525
+ let weights = lobeWeights(surface, view);
526
+
527
+ result.diffuse = lobe < weights.x;
528
+
529
+ if (lobe < weights.x) {
530
+ result.direction = cosineDirection(normal, u);
531
+ } else if (surface.kind == KIND_PHONG) {
532
+ result.direction = reflect(-view, sampleBlinnPhongHalf(normal, surface.shininess, u));
533
+ } else if (lobe < weights.x + weights.y) {
534
+ let basis = specularBasis(surface);
535
+ // The lobe is drawn in its own frame, so the view direction goes into that frame and the result comes back out.
536
+ let localView = vec3<f32>(dot(view, basis[0]), dot(view, basis[1]), dot(view, normal));
537
+ let localHalf = sampleVisibleNormal(localView, specularAlphas(surface), u);
538
+ let half = basis * localHalf;
539
+
540
+ result.direction = reflect(-view, half);
541
+ } else {
542
+ let coatNormal = surface.clearcoatNormal;
543
+ let basis = orthonormalBasis(coatNormal);
544
+ let alpha = max(surface.clearcoatRoughness * surface.clearcoatRoughness, 1e-4);
545
+ let localView = vec3<f32>(dot(view, basis[0]), dot(view, basis[1]), dot(view, coatNormal));
546
+ let half = basis * sampleVisibleNormal(localView, vec2<f32>(alpha), u);
547
+
548
+ result.direction = reflect(-view, half);
549
+ }
550
+
551
+ let evaluated = evaluateBsdf(surface, view, result.direction);
552
+
553
+ if (evaluated.pdf <= 0.0 || all(evaluated.value <= vec3<f32>(0.0))) {
554
+ return result;
555
+ }
556
+
557
+ result.pdf = evaluated.pdf;
558
+ result.weight = evaluated.value / evaluated.pdf;
559
+ result.valid = true;
560
+
561
+ return result;
562
+ }
563
+
564
+ /**
565
+ * Only the diffuse part of evaluateBsdf, times the cosine: what of the light from this direction the denoiser counts
566
+ * as diffuse. The rest of the value is specular.
567
+ */
568
+ fn diffuseValue(surface: Surface, view: vec3<f32>, light: vec3<f32>) -> vec3<f32> {
569
+ let normal = surface.normal;
570
+ let nl = dot(normal, light);
571
+ let nv = max(dot(normal, view), 1e-4);
572
+
573
+ if (surface.kind == KIND_TOON) {
574
+ return surface.albedo / PI * toonRamp(surface.gradientMap, nl);
575
+ }
576
+
577
+ if (nl <= 0.0) {
578
+ return vec3<f32>(0.0);
579
+ }
580
+
581
+ if ((surface.flags & MATERIAL_PBR) == 0u) {
582
+ return surface.albedo / PI * nl;
583
+ }
584
+
585
+ var diffuse = surface.albedo * (1.0 - surface.metalness) / PI;
586
+
587
+ if (any(surface.sheenColor > vec3<f32>(0.0))) {
588
+ diffuse = diffuse * (1.0 - max3(surface.sheenColor)
589
+ * max(sheenAlbedo(nv, surface.sheenRoughness), sheenAlbedo(nl, surface.sheenRoughness)));
590
+ }
591
+
592
+ return diffuse * nl * coatTransmittance(surface, view);
593
+ }
594
+
595
+ /** How much of the light a surface's specular lobes reflect in all, seen from this view: what that signal is divided by. */
596
+ fn specularAlbedo(surface: Surface, view: vec3<f32>) -> vec3<f32> {
597
+ let nv = clamp(dot(surface.normal, view), 0.0, 1.0);
598
+
599
+ if (surface.kind == KIND_PHONG) {
600
+ return surface.specularColor * surface.specularStrength;
601
+ }
602
+
603
+ if ((surface.flags & MATERIAL_PBR) == 0u) {
604
+ return vec3<f32>(0.0);
605
+ }
606
+
607
+ let dfg = dfgApprox(surface.roughness, nv);
608
+ var albedo = surfaceF0(surface) * dfg.x + vec3<f32>(surface.specularF90 * dfg.y);
609
+
610
+ if (surface.clearcoat > 0.0) {
611
+ let coatDfg = dfgApprox(surface.clearcoatRoughness, clamp(dot(surface.clearcoatNormal, view), 0.0, 1.0));
612
+ albedo = albedo * coatTransmittance(surface, view) + vec3<f32>(surface.clearcoat * (0.04 * coatDfg.x + coatDfg.y));
613
+ }
614
+
615
+ // What crosses a transmissive surface is the specular signal too, and it is not filtered by any colour of the surface.
616
+ return mix(albedo, vec3<f32>(1.0), clamp(surface.transmission, 0.0, 1.0));
617
+ }
618
+ `;
619
+ //# sourceMappingURL=bsdf.wgsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsdf.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/bsdf.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8lBnC,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Triangle intersection and the iterative traversal, with the fixed stack the builder caps its depth to. */
2
+ export declare const BVH_WGSL = "\nfn triangleVertex(triangle: u32, corner: u32) -> vec3<f32> {\n let base = triangle * 9u + corner * 3u;\n\n return vec3<f32>(positions[base], positions[base + 1u], positions[base + 2u]);\n}\n\n/** Moller-Trumbore. Returns the distance in x and the barycentric coordinates in yz, or a negative distance. */\nfn intersectTriangle(triangle: u32, origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {\n let miss = vec3<f32>(-1.0, 0.0, 0.0);\n let a = triangleVertex(triangle, 0u);\n let edge1 = triangleVertex(triangle, 1u) - a;\n let edge2 = triangleVertex(triangle, 2u) - a;\n let p = cross(direction, edge2);\n let determinant = dot(edge1, p);\n\n if (abs(determinant) < 1e-12) {\n return miss;\n }\n\n let inverse = 1.0 / determinant;\n let offset = origin - a;\n let u = dot(offset, p) * inverse;\n\n if (u < 0.0 || u > 1.0) {\n return miss;\n }\n\n let q = cross(offset, edge1);\n let v = dot(direction, q) * inverse;\n\n if (v < 0.0 || u + v > 1.0) {\n return miss;\n }\n\n return vec3<f32>(dot(edge2, q) * inverse, u, v);\n}\n\n/** Where a ray enters a node's box, or 1e30 when it misses it or only meets it past maxDistance. */\nfn enterBox(node: BvhNode, origin: vec3<f32>, inverse: vec3<f32>, maxDistance: f32) -> f32 {\n let first = (node.low - origin) * inverse;\n let second = (node.high - origin) * inverse;\n let near = min(first, second);\n let far = max(first, second);\n let enter = max(max(near.x, near.y), max(near.z, 0.0));\n let exit = min(min(far.x, far.y), min(far.z, maxDistance));\n\n return select(1e30, enter, enter <= exit);\n}\n\n/** The triangles of a leaf, against the closest hit so far. */\nfn intersectLeaf(node: BvhNode, origin: vec3<f32>, direction: vec3<f32>, hit: ptr<function, Hit>) {\n let start = u32(-1 - node.link);\n\n for (var i = 0u; i < u32(node.count); i = i + 1u) {\n let triangle = orderedTriangle(start + i);\n let result = intersectTriangle(triangle, origin, direction);\n\n if (result.x > RAY_EPSILON && result.x < (*hit).distance) {\n (*hit).distance = result.x;\n (*hit).triangle = triangle;\n (*hit).u = result.y;\n (*hit).v = result.z;\n }\n }\n}\n\n/**\n * The closest hit along a ray. Both children of a node are tested together and the nearer one is visited first, so\n * the closest hit shrinks the search early and whole subtrees behind it are skipped; each node remembers where the ray\n * entered it, and one whose entry is already past the closest hit is dropped without being opened.\n */\nfn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit {\n var hit: Hit;\n hit.distance = maxDistance;\n hit.triangle = NO_HIT;\n hit.u = 0.0;\n hit.v = 0.0;\n\n if (frame.triangleCount == 0u) {\n return hit;\n }\n\n // A huge finite reciprocal instead of an infinite one: an axis aligned slab would otherwise produce 0 * inf.\n let signs = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));\n let inverse = signs / max(abs(direction), vec3<f32>(1e-20));\n let rootEnter = enterBox(nodes[0], origin, inverse, hit.distance);\n\n if (rootEnter >= 1e30) {\n return hit;\n }\n\n var stack: array<u32, 32>;\n var enters: array<f32, 32>;\n var depth = 1u;\n stack[0] = 0u;\n enters[0] = rootEnter;\n\n loop {\n if (depth == 0u) {\n break;\n }\n\n depth = depth - 1u;\n\n // Something closer was found since this node was pushed.\n if (enters[depth] > hit.distance) {\n continue;\n }\n\n let node = nodes[stack[depth]];\n\n if (node.link < 0) {\n intersectLeaf(node, origin, direction, &hit);\n continue;\n }\n\n let leftIndex = u32(node.link);\n let rightIndex = u32(node.count);\n let leftEnter = enterBox(nodes[leftIndex], origin, inverse, hit.distance);\n let rightEnter = enterBox(nodes[rightIndex], origin, inverse, hit.distance);\n let leftHit = leftEnter < 1e30;\n let rightHit = rightEnter < 1e30;\n\n // The builder caps its depth so both children always fit; the guard keeps a malformed tree inside the stack.\n if (depth + 2u > 32u) {\n continue;\n }\n\n if (leftHit && rightHit) {\n // The farther first, so the nearer comes off the stack next.\n let leftNearer = leftEnter <= rightEnter;\n stack[depth] = select(leftIndex, rightIndex, leftNearer);\n enters[depth] = select(leftEnter, rightEnter, leftNearer);\n stack[depth + 1u] = select(rightIndex, leftIndex, leftNearer);\n enters[depth + 1u] = select(rightEnter, leftEnter, leftNearer);\n depth = depth + 2u;\n } else if (leftHit) {\n stack[depth] = leftIndex;\n enters[depth] = leftEnter;\n depth = depth + 1u;\n } else if (rightHit) {\n stack[depth] = rightIndex;\n enters[depth] = rightEnter;\n depth = depth + 1u;\n }\n }\n\n return hit;\n}\n\n/**\n * Whether anything at all lies along a ray before maxDistance, stopping at the first triangle it finds rather than the\n * closest: what a shadow ray asks when every surface it could meet blocks it. Returns that triangle, or NO_HIT.\n */\nfn traceAny(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> u32 {\n if (frame.triangleCount == 0u) {\n return NO_HIT;\n }\n\n let signs = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));\n let inverse = signs / max(abs(direction), vec3<f32>(1e-20));\n\n if (enterBox(nodes[0], origin, inverse, maxDistance) >= 1e30) {\n return NO_HIT;\n }\n\n var stack: array<u32, 32>;\n var depth = 1u;\n stack[0] = 0u;\n\n loop {\n if (depth == 0u) {\n break;\n }\n\n depth = depth - 1u;\n\n let node = nodes[stack[depth]];\n\n if (node.link < 0) {\n let start = u32(-1 - node.link);\n\n for (var i = 0u; i < u32(node.count); i = i + 1u) {\n let triangle = orderedTriangle(start + i);\n let result = intersectTriangle(triangle, origin, direction);\n\n if (result.x > RAY_EPSILON && result.x < maxDistance) {\n return triangle;\n }\n }\n\n continue;\n }\n\n let leftIndex = u32(node.link);\n let rightIndex = u32(node.count);\n\n if (depth + 2u > 32u) {\n continue;\n }\n\n if (enterBox(nodes[leftIndex], origin, inverse, maxDistance) < 1e30) {\n stack[depth] = leftIndex;\n depth = depth + 1u;\n }\n\n if (enterBox(nodes[rightIndex], origin, inverse, maxDistance) < 1e30) {\n stack[depth] = rightIndex;\n depth = depth + 1u;\n }\n }\n\n return NO_HIT;\n}\n";
3
+ //# sourceMappingURL=bvh.wgsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bvh.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/bvh.wgsl.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,eAAO,MAAM,QAAQ,y4MAuNpB,CAAC"}