@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.
- package/ARCHITECTURE.md +339 -0
- package/LICENSE +21 -0
- package/README.md +284 -2
- package/dist/accumulator.d.ts +27 -0
- package/dist/accumulator.d.ts.map +1 -0
- package/dist/accumulator.js +57 -0
- package/dist/accumulator.js.map +1 -0
- package/dist/bvh.d.ts +26 -0
- package/dist/bvh.d.ts.map +1 -0
- package/dist/bvh.js +296 -0
- package/dist/bvh.js.map +1 -0
- package/dist/bvhBuilder.d.ts +72 -0
- package/dist/bvhBuilder.d.ts.map +1 -0
- package/dist/bvhBuilder.js +126 -0
- package/dist/bvhBuilder.js.map +1 -0
- package/dist/bvhWorker.d.ts +2 -0
- package/dist/bvhWorker.d.ts.map +1 -0
- package/dist/bvhWorker.js +8 -0
- package/dist/bvhWorker.js.map +1 -0
- package/dist/camera.d.ts +29 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +48 -0
- package/dist/camera.js.map +1 -0
- package/dist/canvas.d.ts +9 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +11 -0
- package/dist/canvas.js.map +1 -0
- package/dist/denoise.d.ts +69 -0
- package/dist/denoise.d.ts.map +1 -0
- package/dist/denoise.js +455 -0
- package/dist/denoise.js.map +1 -0
- package/dist/device.d.ts +37 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +52 -0
- package/dist/device.js.map +1 -0
- package/dist/dispatchScheduler.d.ts +43 -0
- package/dist/dispatchScheduler.d.ts.map +1 -0
- package/dist/dispatchScheduler.js +49 -0
- package/dist/dispatchScheduler.js.map +1 -0
- package/dist/environment.d.ts +39 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +168 -0
- package/dist/environment.js.map +1 -0
- package/dist/frameLayout.d.ts +4 -0
- package/dist/frameLayout.d.ts.map +1 -0
- package/dist/frameLayout.js +58 -0
- package/dist/frameLayout.js.map +1 -0
- package/dist/geometry.d.ts +75 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +799 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lights.d.ts +64 -0
- package/dist/lights.d.ts.map +1 -0
- package/dist/lights.js +264 -0
- package/dist/lights.js.map +1 -0
- package/dist/materialLayout.d.ts +143 -0
- package/dist/materialLayout.d.ts.map +1 -0
- package/dist/materialLayout.js +297 -0
- package/dist/materialLayout.js.map +1 -0
- package/dist/math.d.ts +10 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +40 -0
- package/dist/math.js.map +1 -0
- package/dist/pathTracePass.d.ts +27 -0
- package/dist/pathTracePass.d.ts.map +1 -0
- package/dist/pathTracePass.js +83 -0
- package/dist/pathTracePass.js.map +1 -0
- package/dist/planarCapture.d.ts +38 -0
- package/dist/planarCapture.d.ts.map +1 -0
- package/dist/planarCapture.js +163 -0
- package/dist/planarCapture.js.map +1 -0
- package/dist/presentPass.d.ts +31 -0
- package/dist/presentPass.d.ts.map +1 -0
- package/dist/presentPass.js +78 -0
- package/dist/presentPass.js.map +1 -0
- package/dist/renderer.d.ts +209 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +847 -0
- package/dist/renderer.js.map +1 -0
- package/dist/sceneAssets.d.ts +24 -0
- package/dist/sceneAssets.d.ts.map +1 -0
- package/dist/sceneAssets.js +77 -0
- package/dist/sceneAssets.js.map +1 -0
- package/dist/sceneUpload.d.ts +61 -0
- package/dist/sceneUpload.d.ts.map +1 -0
- package/dist/sceneUpload.js +98 -0
- package/dist/sceneUpload.js.map +1 -0
- package/dist/shaderModule.d.ts +7 -0
- package/dist/shaderModule.d.ts.map +1 -0
- package/dist/shaderModule.js +19 -0
- package/dist/shaderModule.js.map +1 -0
- package/dist/shaderProgram.d.ts +124 -0
- package/dist/shaderProgram.d.ts.map +1 -0
- package/dist/shaderProgram.js +1030 -0
- package/dist/shaderProgram.js.map +1 -0
- package/dist/structLayout.d.ts +28 -0
- package/dist/structLayout.d.ts.map +1 -0
- package/dist/structLayout.js +58 -0
- package/dist/structLayout.js.map +1 -0
- package/dist/texturePack.d.ts +54 -0
- package/dist/texturePack.d.ts.map +1 -0
- package/dist/texturePack.js +86 -0
- package/dist/texturePack.js.map +1 -0
- package/dist/upstream.d.ts +7 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +11 -0
- package/dist/upstream.js.map +1 -0
- package/dist/volumetric.d.ts +10 -0
- package/dist/volumetric.d.ts.map +1 -0
- package/dist/volumetric.js +34 -0
- package/dist/volumetric.js.map +1 -0
- package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
- package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bsdf.wgsl.js +619 -0
- package/dist/wgsl/bsdf.wgsl.js.map +1 -0
- package/dist/wgsl/bvh.wgsl.d.ts +3 -0
- package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bvh.wgsl.js +218 -0
- package/dist/wgsl/bvh.wgsl.js.map +1 -0
- package/dist/wgsl/common.wgsl.d.ts +3 -0
- package/dist/wgsl/common.wgsl.d.ts.map +1 -0
- package/dist/wgsl/common.wgsl.js +193 -0
- package/dist/wgsl/common.wgsl.js.map +1 -0
- package/dist/wgsl/environment.wgsl.d.ts +10 -0
- package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
- package/dist/wgsl/environment.wgsl.js +145 -0
- package/dist/wgsl/environment.wgsl.js.map +1 -0
- package/dist/wgsl/lights.wgsl.d.ts +10 -0
- package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
- package/dist/wgsl/lights.wgsl.js +434 -0
- package/dist/wgsl/lights.wgsl.js.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.js +1020 -0
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
- package/dist/wgsl/present.wgsl.d.ts +7 -0
- package/dist/wgsl/present.wgsl.d.ts.map +1 -0
- package/dist/wgsl/present.wgsl.js +154 -0
- package/dist/wgsl/present.wgsl.js.map +1 -0
- package/dist/wgsl/random.wgsl.d.ts +9 -0
- package/dist/wgsl/random.wgsl.d.ts.map +1 -0
- package/dist/wgsl/random.wgsl.js +91 -0
- package/dist/wgsl/random.wgsl.js.map +1 -0
- package/dist/wgsl/sampling.wgsl.d.ts +3 -0
- package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
- package/dist/wgsl/sampling.wgsl.js +31 -0
- package/dist/wgsl/sampling.wgsl.js.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.js +120 -0
- package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
- package/dist/wgsl/surface.wgsl.d.ts +10 -0
- package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
- package/dist/wgsl/surface.wgsl.js +794 -0
- package/dist/wgsl/surface.wgsl.js.map +1 -0
- package/dist/wgsl/texture.wgsl.d.ts +11 -0
- package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
- package/dist/wgsl/texture.wgsl.js +132 -0
- package/dist/wgsl/texture.wgsl.js.map +1 -0
- package/package.json +77 -3
- package/src/accumulator.ts +64 -0
- package/src/bvh.ts +336 -0
- package/src/bvhBuilder.ts +178 -0
- package/src/bvhWorker.ts +13 -0
- package/src/camera.ts +77 -0
- package/src/canvas.ts +13 -0
- package/src/denoise.ts +514 -0
- package/src/device.ts +81 -0
- package/src/dispatchScheduler.ts +71 -0
- package/src/environment.ts +233 -0
- package/src/frameLayout.ts +59 -0
- package/src/geometry.ts +1067 -0
- package/src/index.ts +64 -0
- package/src/lights.ts +365 -0
- package/src/materialLayout.ts +408 -0
- package/src/math.ts +48 -0
- package/src/pathTracePass.ts +97 -0
- package/src/planarCapture.ts +225 -0
- package/src/presentPass.ts +99 -0
- package/src/renderer.ts +1145 -0
- package/src/sceneAssets.ts +118 -0
- package/src/sceneUpload.ts +185 -0
- package/src/shaderModule.ts +19 -0
- package/src/shaderProgram.ts +1370 -0
- package/src/structLayout.ts +93 -0
- package/src/texturePack.ts +152 -0
- package/src/upstream.ts +56 -0
- package/src/volumetric.ts +42 -0
- package/src/wgsl/bsdf.wgsl.ts +618 -0
- package/src/wgsl/bvh.wgsl.ts +217 -0
- package/src/wgsl/common.wgsl.ts +193 -0
- package/src/wgsl/environment.wgsl.ts +144 -0
- package/src/wgsl/lights.wgsl.ts +433 -0
- package/src/wgsl/pathTrace.wgsl.ts +1019 -0
- package/src/wgsl/present.wgsl.ts +153 -0
- package/src/wgsl/random.wgsl.ts +90 -0
- package/src/wgsl/sampling.wgsl.ts +30 -0
- package/src/wgsl/shaderSupport.wgsl.ts +119 -0
- package/src/wgsl/surface.wgsl.ts +793 -0
- package/src/wgsl/texture.wgsl.ts +131 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking a light and aiming a shadow ray at it. Emissive triangles and Three's analytic lights answer the same
|
|
3
|
+
* question, so the integrator asks it once: given two random numbers, where does light come from, how much of it
|
|
4
|
+
* arrives, and how likely was this direction to be found this way.
|
|
5
|
+
*
|
|
6
|
+
* The falloff, the spot cone and the rectangle's projected area follow `material.js` in the CPU renderer, so direct
|
|
7
|
+
* light from an analytic light agrees with it.
|
|
8
|
+
*/
|
|
9
|
+
export const LIGHTS_WGSL = /* wgsl */ `
|
|
10
|
+
struct LightSample {
|
|
11
|
+
// Unit vector from the shaded point towards the light.
|
|
12
|
+
direction: vec3<f32>,
|
|
13
|
+
// How far an occlusion ray may travel before it is past the light.
|
|
14
|
+
distance: f32,
|
|
15
|
+
// What the light delivers, already divided by the density it was sampled with.
|
|
16
|
+
value: vec3<f32>,
|
|
17
|
+
// Solid angle density of this direction, for the power heuristic. Zero when no bounce ray could find this light,
|
|
18
|
+
// which is every light that occupies no geometry, and then the light strategy keeps the whole contribution.
|
|
19
|
+
misPdf: f32,
|
|
20
|
+
valid: bool,
|
|
21
|
+
occlude: bool,
|
|
22
|
+
kind: u32,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
fn noLight() -> LightSample {
|
|
26
|
+
var sample: LightSample;
|
|
27
|
+
sample.valid = false;
|
|
28
|
+
|
|
29
|
+
return sample;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The light whose cumulative probability range contains u. */
|
|
33
|
+
fn lightByProbability(u: f32) -> Light {
|
|
34
|
+
var low = 0u;
|
|
35
|
+
var high = frame.lightCount - 1u;
|
|
36
|
+
|
|
37
|
+
loop {
|
|
38
|
+
if (low >= high) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let middle = (low + high) / 2u;
|
|
43
|
+
|
|
44
|
+
if (lights[middle].cdf > u) {
|
|
45
|
+
high = middle;
|
|
46
|
+
} else {
|
|
47
|
+
low = middle + 1u;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return lights[low];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The area density with which direct sampling would have picked a point on this triangle, or zero if it never would.
|
|
56
|
+
* Only the leading entries are emissive triangles, and they are in triangle order, so one binary search answers it.
|
|
57
|
+
*/
|
|
58
|
+
fn lightPdfOfTriangle(triangle: u32) -> f32 {
|
|
59
|
+
var low = 0u;
|
|
60
|
+
var high = frame.areaLightCount;
|
|
61
|
+
|
|
62
|
+
loop {
|
|
63
|
+
if (low >= high) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let middle = (low + high) / 2u;
|
|
68
|
+
let candidate = lights[middle].triangle;
|
|
69
|
+
|
|
70
|
+
if (candidate == triangle) {
|
|
71
|
+
return lights[middle].pdf;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (candidate < triangle) {
|
|
75
|
+
low = middle + 1u;
|
|
76
|
+
} else {
|
|
77
|
+
high = middle;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return 0.0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The power heuristic: how much of a contribution the strategy that produced it keeps, against the other one. */
|
|
85
|
+
fn misWeight(pdf: f32, other: f32) -> f32 {
|
|
86
|
+
let a = pdf * pdf;
|
|
87
|
+
let b = other * other;
|
|
88
|
+
|
|
89
|
+
return select(a / (a + b), 0.0, a + b <= 0.0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Spreads a direction over a cone, which is how a light of some apparent size softens its own shadow. The settings give
|
|
94
|
+
* the angular diameter; a rasterizer would blur the shadow map by the same amount.
|
|
95
|
+
*/
|
|
96
|
+
fn spreadDirection(direction: vec3<f32>, u: vec2<f32>) -> vec3<f32> {
|
|
97
|
+
if (frame.shadowSpread <= 0.0) {
|
|
98
|
+
return direction;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let basis = orthonormalBasis(direction);
|
|
102
|
+
let radius = sqrt(u.x) * frame.shadowSpread;
|
|
103
|
+
let angle = 2.0 * PI * u.y;
|
|
104
|
+
|
|
105
|
+
return normalize(direction + basis[0] * (radius * cos(angle)) + basis[1] * (radius * sin(angle)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Three's getDistanceAttenuation: the inverse power law, cut off smoothly at the light's range. */
|
|
109
|
+
fn distanceFalloff(light: Light, distance: f32) -> f32 {
|
|
110
|
+
var falloff = 1.0 / max(pow(distance, light.decay), 0.01);
|
|
111
|
+
|
|
112
|
+
if (light.cutoff > 0.0) {
|
|
113
|
+
let edge = clamp(1.0 - pow(distance / light.cutoff, 4.0), 0.0, 1.0);
|
|
114
|
+
|
|
115
|
+
falloff = falloff * edge * edge;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return falloff;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A point uniformly distributed over a rectangular light, and the axis it emits along. */
|
|
122
|
+
fn rectanglePoint(light: Light, u: vec2<f32>) -> vec3<f32> {
|
|
123
|
+
return light.position + light.tangent * (u.x * 2.0 - 1.0) + light.bitangent * (u.y * 2.0 - 1.0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Where the light that pick selects arrives from, and how much of it. u places the point on a surface light or spreads a
|
|
128
|
+
* point light's shadow, so the same two dimensions serve every kind.
|
|
129
|
+
*/
|
|
130
|
+
fn sampleLight(position: vec3<f32>, pick: f32, u: vec2<f32>) -> LightSample {
|
|
131
|
+
if (frame.lightCount == 0u) {
|
|
132
|
+
return noLight();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return sampleLightEntry(lightByProbability(pick), position, u);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Three's spotLightMap: inside the projection's frustum the texture tints the light; outside it leaves it alone. */
|
|
139
|
+
fn spotLightTint(light: Light, position: vec3<f32>) -> vec3<f32> {
|
|
140
|
+
if ((light.flags & LIGHT_HAS_MAP) == 0u) {
|
|
141
|
+
return vec3<f32>(1.0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let at = light.triangle;
|
|
145
|
+
let map = textures[at];
|
|
146
|
+
let matrix = mat4x4<f32>(extraVec4(at + 1u), extraVec4(at + 5u), extraVec4(at + 9u), extraVec4(at + 13u));
|
|
147
|
+
let coordinate = matrix * vec4<f32>(position, 1.0);
|
|
148
|
+
|
|
149
|
+
if (coordinate.w <= 0.0) {
|
|
150
|
+
return vec3<f32>(1.0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let projected = coordinate.xyz / coordinate.w;
|
|
154
|
+
|
|
155
|
+
if (any(projected <= vec3<f32>(0.0)) || any(projected >= vec3<f32>(1.0))) {
|
|
156
|
+
return vec3<f32>(1.0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return sampleTextureRaw(map, projected.xy).rgb;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn sampleLightEntry(light: Light, position: vec3<f32>, u: vec2<f32>) -> LightSample {
|
|
163
|
+
var sample: LightSample;
|
|
164
|
+
sample.valid = true;
|
|
165
|
+
sample.kind = light.kind;
|
|
166
|
+
// Emissive geometry is always tested: a panel that lit the room through its own wall would not be a path tracer.
|
|
167
|
+
sample.occlude = light.kind == LIGHT_AREA
|
|
168
|
+
|| ((light.flags & LIGHT_CASTS_SHADOW) != 0u && frame.shadowsEnabled != 0u);
|
|
169
|
+
sample.misPdf = 0.0;
|
|
170
|
+
|
|
171
|
+
if (light.kind == LIGHT_ENVIRONMENT) {
|
|
172
|
+
let drawn = sampleEnvironmentDirection(u);
|
|
173
|
+
|
|
174
|
+
if (drawn.pdf <= 0.0) {
|
|
175
|
+
return noLight();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let density = light.pdf * drawn.pdf;
|
|
179
|
+
|
|
180
|
+
sample.direction = drawn.direction;
|
|
181
|
+
sample.distance = 1e30;
|
|
182
|
+
sample.value = environmentRadiance(drawn.direction) / density;
|
|
183
|
+
// A bounce ray that escapes finds the same light, so the two strategies share it.
|
|
184
|
+
sample.misPdf = density;
|
|
185
|
+
|
|
186
|
+
return sample;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (light.kind == LIGHT_DIRECTIONAL) {
|
|
190
|
+
sample.direction = spreadDirection(light.direction, u);
|
|
191
|
+
sample.distance = 1e30;
|
|
192
|
+
// The colour already carries the intensity, which for a directional light is the irradiance it delivers.
|
|
193
|
+
sample.value = light.color / light.pdf;
|
|
194
|
+
|
|
195
|
+
return sample;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (light.kind == LIGHT_AREA || light.kind == LIGHT_RECT) {
|
|
199
|
+
let area = light.kind == LIGHT_AREA;
|
|
200
|
+
let bary = triangleBarycentric(u);
|
|
201
|
+
let point = select(rectanglePoint(light, u), trianglePointAt(light.triangle, bary), area);
|
|
202
|
+
let axis = select(normalize(cross(light.tangent, light.bitangent)), triangleNormal(light.triangle), area);
|
|
203
|
+
let offset = point - position;
|
|
204
|
+
let distanceSquared = dot(offset, offset);
|
|
205
|
+
let distance = sqrt(distanceSquared);
|
|
206
|
+
|
|
207
|
+
if (distance <= RAY_EPSILON) {
|
|
208
|
+
return noLight();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
sample.direction = offset / distance;
|
|
212
|
+
// An emissive triangle radiates from both sides in this milestone; a rectangular light only from the side it faces.
|
|
213
|
+
let cosine = dot(axis, sample.direction);
|
|
214
|
+
let cosLight = select(max(cosine, 0.0), abs(cosine), area);
|
|
215
|
+
|
|
216
|
+
if (cosLight <= 1e-6) {
|
|
217
|
+
return noLight();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Stop just short of the light so its own surface is not reported as the blocker.
|
|
221
|
+
sample.distance = distance * (1.0 - 1e-3);
|
|
222
|
+
let density = light.pdf * distanceSquared / cosLight;
|
|
223
|
+
// The emissive map is read at the very point that was sampled, so this agrees with what a bounce ray would find.
|
|
224
|
+
let emission = select(light.color, emissionAt(light.triangle, bary), area);
|
|
225
|
+
|
|
226
|
+
sample.value = emission / density;
|
|
227
|
+
// Only a triangle of the scene's geometry can also be found by a bounce ray, so only it needs the heuristic.
|
|
228
|
+
sample.misPdf = select(0.0, density, area);
|
|
229
|
+
|
|
230
|
+
return sample;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// A point or a spot light: both fall off with distance and are sampled at their centre.
|
|
234
|
+
let offset = light.position - position;
|
|
235
|
+
let distance = length(offset);
|
|
236
|
+
|
|
237
|
+
if (distance <= RAY_EPSILON) {
|
|
238
|
+
return noLight();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let towards = offset / distance;
|
|
242
|
+
var power = distanceFalloff(light, distance);
|
|
243
|
+
|
|
244
|
+
if (light.kind == LIGHT_SPOT) {
|
|
245
|
+
let cosine = dot(light.direction, towards);
|
|
246
|
+
|
|
247
|
+
if (light.innerCos <= light.outerCos) {
|
|
248
|
+
power = power * select(0.0, 1.0, cosine >= light.outerCos);
|
|
249
|
+
} else {
|
|
250
|
+
let t = clamp((cosine - light.outerCos) / (light.innerCos - light.outerCos), 0.0, 1.0);
|
|
251
|
+
|
|
252
|
+
power = power * t * t * (3.0 - 2.0 * t);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (power <= 0.0) {
|
|
256
|
+
return noLight();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
sample.direction = spreadDirection(towards, u);
|
|
261
|
+
sample.distance = distance;
|
|
262
|
+
sample.value = light.color * (power / light.pdf);
|
|
263
|
+
|
|
264
|
+
if (light.kind == LIGHT_SPOT) {
|
|
265
|
+
sample.value = sample.value * spotLightTint(light, position);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return sample;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Whether a material stops every shadow ray that meets it, wherever: opaque, no cutout, no transmission, no clipping
|
|
273
|
+
* or wireframe, no shader that might discard, and normal blending. Only then can the first triangle found decide.
|
|
274
|
+
*/
|
|
275
|
+
fn blocksAll(m: u32) -> bool {
|
|
276
|
+
let flags = materials[m].flags;
|
|
277
|
+
let blending = materials[m].blending;
|
|
278
|
+
|
|
279
|
+
return (flags & (MATERIAL_TRANSPARENT | MATERIAL_ALPHA_HASH | MATERIAL_WIREFRAME)) == 0u
|
|
280
|
+
&& materials[m].alphaTest <= 0.0
|
|
281
|
+
&& materials[m].transmission <= 0.0
|
|
282
|
+
&& materials[m].clipCount == 0u
|
|
283
|
+
&& materials[m].program == NO_PROGRAM
|
|
284
|
+
&& materials[m].kind != KIND_SHADOW
|
|
285
|
+
&& (blending == BLEND_NORMAL || blending == BLEND_NONE);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** How many surfaces a shadow ray may pass through before it gives up and calls the light blocked. */
|
|
289
|
+
const MAX_SHADOW_SKIPS: u32 = 8u;
|
|
290
|
+
|
|
291
|
+
/** No material to skip: every blocker counts. */
|
|
292
|
+
const NO_MATERIAL: u32 = 0xffffffffu;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* What a shadow ray keeps of a thin transmissive wall it meets: the share Three lets through, tinted by the base colour,
|
|
296
|
+
* less what the interface reflects. It is the expectation of the straight crossing in sampleDielectric, so a light
|
|
297
|
+
* seen through a window is found the same by both estimators. A volume bends the ray it lets through, so it blocks.
|
|
298
|
+
*/
|
|
299
|
+
fn thinCrossing(triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
300
|
+
let m = materialIndexOf(triangle);
|
|
301
|
+
|
|
302
|
+
if (materials[m].transmission <= 0.0) {
|
|
303
|
+
return vec3<f32>(0.0);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let metalness = materials[m].metalness * sampleMap(materials[m].metalnessMap, triangle, bary).b;
|
|
307
|
+
let transmission = transmissionAt(m, triangle, bary, metalness);
|
|
308
|
+
let thickness = materials[m].thickness * sampleMap(materials[m].thicknessMap, triangle, bary).g;
|
|
309
|
+
|
|
310
|
+
if (transmission <= 0.0 || thickness > 0.0) {
|
|
311
|
+
return vec3<f32>(0.0);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let albedo = materials[m].color.rgb * sampleMap(materials[m].map, triangle, bary).rgb;
|
|
315
|
+
let reflectance = fresnelDielectric(abs(dot(authoredNormal(triangle, bary), direction)), 1.0 / max(materials[m].ior, 1.0));
|
|
316
|
+
|
|
317
|
+
return albedo * (transmission * (1.0 - reflectance));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The colour a blended surface filters what is behind it by: its base colour, with the map and vertex colours. */
|
|
321
|
+
fn blendColor(triangle: u32, bary: vec2<f32>) -> vec3<f32> {
|
|
322
|
+
let m = materialIndexOf(triangle);
|
|
323
|
+
var color = materials[m].color.rgb * sampleMap(materials[m].map, triangle, bary).rgb;
|
|
324
|
+
|
|
325
|
+
if ((materials[m].flags & MATERIAL_VERTEX_COLORS) != 0u) {
|
|
326
|
+
color = color * triangleColor(triangle, bary).rgb;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return color;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* How much of the light this sample came from reaches the shaded point. A surface that only partly covers its triangle
|
|
334
|
+
* blocks the ray only that often, which is what makes a cutout leaf cast a cutout shadow; a thin transmissive wall
|
|
335
|
+
* lets its own share through, tinted.
|
|
336
|
+
*/
|
|
337
|
+
fn lightTransmittance(
|
|
338
|
+
position: vec3<f32>, chosen: LightSample, rng: ptr<function, PathSampler>, skipMaterial: u32) -> vec3<f32> {
|
|
339
|
+
if (!chosen.occlude) {
|
|
340
|
+
return vec3<f32>(1.0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Most blockers are plainly opaque, and for those any triangle on the way settles it: the search stops at the first
|
|
344
|
+
// one found instead of looking for the closest. Anything that lets light through goes the long way below.
|
|
345
|
+
let first = traceAny(position, chosen.direction, chosen.distance);
|
|
346
|
+
|
|
347
|
+
if (first == NO_HIT) {
|
|
348
|
+
return vec3<f32>(1.0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let firstMaterial = materialIndexOf(first);
|
|
352
|
+
|
|
353
|
+
if (firstMaterial != skipMaterial && blocksAll(firstMaterial)) {
|
|
354
|
+
return vec3<f32>(0.0);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
var origin = position;
|
|
358
|
+
var remaining = chosen.distance;
|
|
359
|
+
var through = vec3<f32>(1.0);
|
|
360
|
+
|
|
361
|
+
for (var skip = 0u; skip < MAX_SHADOW_SKIPS; skip = skip + 1u) {
|
|
362
|
+
let blocker = traceScene(origin, chosen.direction, remaining);
|
|
363
|
+
|
|
364
|
+
if (blocker.triangle == NO_HIT) {
|
|
365
|
+
return through;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let bary = vec2<f32>(blocker.u, blocker.v);
|
|
369
|
+
let m = materialIndexOf(blocker.triangle);
|
|
370
|
+
let blending = materials[m].blending;
|
|
371
|
+
|
|
372
|
+
// A shadow catcher catches shadows rather than casting them, and a surface that adds light blocks none.
|
|
373
|
+
if (m != skipMaterial && materials[m].kind != KIND_SHADOW && blending != BLEND_ADDITIVE) {
|
|
374
|
+
let coverage = coverageAt(blocker.triangle, bary, chosen.direction);
|
|
375
|
+
|
|
376
|
+
if (blending == BLEND_MULTIPLY) {
|
|
377
|
+
through = through * mix(vec3<f32>(1.0), blendColor(blocker.triangle, bary), coverage);
|
|
378
|
+
} else if (blending == BLEND_SUBTRACTIVE) {
|
|
379
|
+
through = through * (vec3<f32>(1.0) - blendColor(blocker.triangle, bary) * coverage);
|
|
380
|
+
} else if (coverage >= 1.0 || sample2D(rng).x < coverage) {
|
|
381
|
+
through = through * thinCrossing(blocker.triangle, bary, chosen.direction);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (all(through <= vec3<f32>(0.0))) {
|
|
385
|
+
return vec3<f32>(0.0);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The ray carries on from just past the surface it saw through.
|
|
390
|
+
origin = origin + chosen.direction * (blocker.distance + RAY_EPSILON);
|
|
391
|
+
remaining = remaining - blocker.distance - RAY_EPSILON;
|
|
392
|
+
|
|
393
|
+
if (remaining <= RAY_EPSILON) {
|
|
394
|
+
return through;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return vec3<f32>(0.0);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* How shadowed a point is, as Three's ShadowMaterial reads it: for every light that casts a shadow, whether something
|
|
403
|
+
* stands between it and the point, the darkest of them winning as in the CPU renderer. A rectangular light casts no
|
|
404
|
+
* shadow in Three, so it takes no part.
|
|
405
|
+
*/
|
|
406
|
+
fn shadowMaskAt(position: vec3<f32>, rng: ptr<function, PathSampler>) -> f32 {
|
|
407
|
+
if (frame.shadowsEnabled == 0u) {
|
|
408
|
+
return 0.0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
var shadow = 0.0;
|
|
412
|
+
|
|
413
|
+
for (var i = frame.areaLightCount; i < frame.lightCount; i = i + 1u) {
|
|
414
|
+
let light = lights[i];
|
|
415
|
+
|
|
416
|
+
if (light.kind == LIGHT_ENVIRONMENT || light.kind == LIGHT_RECT || (light.flags & LIGHT_CASTS_SHADOW) == 0u) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let chosen = sampleLightEntry(light, position, sample2D(rng));
|
|
421
|
+
|
|
422
|
+
if (!chosen.valid) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let visible = luminanceOf(lightTransmittance(position, chosen, rng, NO_MATERIAL));
|
|
427
|
+
|
|
428
|
+
shadow = max(shadow, 1.0 - clamp(visible, 0.0, 1.0));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return shadow;
|
|
432
|
+
}
|
|
433
|
+
`;
|