rayzee 11.0.0 → 11.1.0
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/dist/{ArchiveImporter-BRQvLggb.js → ArchiveImporter-Hn35o6Z5.js} +1525 -680
- package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
- package/dist/{NeuralRetouch-BCXylThH.js → NeuralRetouch-BzNpiVHZ.js} +1 -1
- package/dist/{NeuralSuperRes-sZAeS142.js → NeuralSuperRes-Bk7bHg2B.js} +1 -1
- package/dist/{PathTracer-CQPW06Mm.js → PathTracer-72XufKAr.js} +79 -74
- package/dist/{PhysicalSky-BvqCw_dy.js → PhysicalSky-BgjdV88k.js} +1 -1
- package/dist/{RayzeeRenderer-COpwotpU.js → RayzeeRenderer-1QRO97eq.js} +3 -3
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/{archiveFormats-BZw7TiDp.js → archiveFormats-tnAoUL1j.js} +28 -9
- package/dist/rayzee-core.es.js +4 -4
- package/dist/rayzee.es.js +11 -11
- package/dist/rayzee.umd.js +43 -43
- package/dist/{sceneCachePolicy-BSSsKY98.js → sceneCachePolicy-C67jbci-.js} +1 -1
- package/package.json +1 -1
- package/src/Processor/ArchiveCache.js +19 -6
- package/src/Processor/ArchiveImporter.js +9 -3
- package/src/Processor/ArchiveReader.js +22 -4
- package/src/Processor/PBRT/PBRTMaterials.js +132 -8
- package/src/Processor/PBRT/PBRTParser.js +20 -3
- package/src/Processor/PBRT/PBRTScattering.js +93 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +513 -49
- package/src/Processor/PBRT/PBRTTextureBake.js +32 -7
- package/src/Processor/SceneMetadata.js +30 -5
- package/src/RayzeeRenderer.js +3 -2
- package/src/Stages/PathTracer.js +3 -5
- package/src/TSL/Bidirectional.js +6 -2
- package/src/TSL/ConnectKernel.js +31 -11
- package/src/TSL/LightSplatKernel.js +22 -7
- package/src/TSL/MergeKernel.js +13 -7
- package/src/TSL/ShadeKernel.js +51 -30
- package/src/integrators/BidirectionalIntegrator.js +6 -1
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +0 -934
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* an image texture's `scale` above 1 or `invert`, and a `mix` material whose amount is a texture.
|
|
4
4
|
*
|
|
5
5
|
* A layer is an image as pbrt evaluates it: linear values, top row first, `channels` 1 (a float texture reads
|
|
6
|
-
* the
|
|
7
|
-
* (st = scale · uv + delta, image row from the top = 1 − t).
|
|
6
|
+
* an image's alpha where it has one, else the mean of its colour) or 3, already scaled and inverted, and the uv
|
|
7
|
+
* mapping pbrt applies before the lookup (st = scale · uv + delta, image row from the top = 1 − t).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const MAX_BAKE_SIZE = 4096;
|
|
@@ -28,6 +28,10 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
|
|
|
28
28
|
const stride = pixels.channels ?? 4;
|
|
29
29
|
const data = new Float32Array( width * height * channels );
|
|
30
30
|
const topDown = pixels.topDown !== false;
|
|
31
|
+
// pbrt's encoding applies to every channel of an 8-bit image, alpha included.
|
|
32
|
+
const read = ( i ) => ( decode ? decode( pixels.data[ i ] / 255 ) : pixels.data[ i ] );
|
|
33
|
+
const fromAlpha = channels === 1 && stride === 4 && hasAlpha( pixels );
|
|
34
|
+
const mean = channels === 1 && stride >= 3 && ! fromAlpha;
|
|
31
35
|
|
|
32
36
|
for ( let y = 0; y < height; y ++ ) {
|
|
33
37
|
|
|
@@ -37,8 +41,10 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
|
|
|
37
41
|
const src = ( row * width + x ) * stride, dst = ( y * width + x ) * channels;
|
|
38
42
|
for ( let c = 0; c < channels; c ++ ) {
|
|
39
43
|
|
|
40
|
-
const raw =
|
|
41
|
-
|
|
44
|
+
const raw = fromAlpha ? read( src + 3 )
|
|
45
|
+
: mean ? ( read( src ) + read( src + 1 ) + read( src + 2 ) ) / 3
|
|
46
|
+
: read( src + Math.min( c, stride - 1 ) );
|
|
47
|
+
const v = scale * raw;
|
|
42
48
|
data[ dst + c ] = invert ? Math.max( 0, 1 - v ) : v;
|
|
43
49
|
|
|
44
50
|
}
|
|
@@ -51,6 +57,16 @@ export function makeLayer( pixels, { channels, decode = null, scale = 1, invert
|
|
|
51
57
|
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
/** Whether an RGBA image's alpha is anything but opaque — pbrt reads it as an RGB image when it is not. */
|
|
61
|
+
export function hasAlpha( pixels ) {
|
|
62
|
+
|
|
63
|
+
const { data } = pixels;
|
|
64
|
+
const one = pixels.float ? 1 : 255;
|
|
65
|
+
for ( let i = 3; i < data.length; i += 4 ) if ( data[ i ] !== one ) return true;
|
|
66
|
+
return false;
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
/** The layer's mean value (a float layer's only channel; a spectrum layer's average of three). */
|
|
55
71
|
export function layerMean( layer ) {
|
|
56
72
|
|
|
@@ -91,9 +107,12 @@ function sample( layer, s, t, out ) {
|
|
|
91
107
|
* (bottom row first), clamped to [0, 1] as pbrt clamps an albedo. Null when every term is a constant.
|
|
92
108
|
* @param {Array<{ rgb?: number[], layer?: object, tint?: number[] }>} terms
|
|
93
109
|
* @param {(values: number[][]) => number[]} combine - linear RGB from each term's linear RGB
|
|
110
|
+
* @param {object} [options]
|
|
111
|
+
* @param {(values: number[][]) => number} [options.alphaOf] - the alpha channel, linear; opaque without it
|
|
112
|
+
* @param {boolean} [options.linear=false] - linear bytes, for a data map
|
|
94
113
|
* @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number } } | null}
|
|
95
114
|
*/
|
|
96
|
-
export function bake( terms, combine ) {
|
|
115
|
+
export function bake( terms, combine, { alphaOf = null, linear = false } = {} ) {
|
|
97
116
|
|
|
98
117
|
let driver = null;
|
|
99
118
|
for ( const term of terms ) if ( term.layer && ( ! driver || term.layer.width * term.layer.height > driver.width * driver.height ) ) driver = term.layer;
|
|
@@ -134,8 +153,14 @@ export function bake( terms, combine ) {
|
|
|
134
153
|
|
|
135
154
|
const rgb = combine( values );
|
|
136
155
|
const o = ( j * width + i ) * 4;
|
|
137
|
-
for ( let c = 0; c < 3; c ++ )
|
|
138
|
-
|
|
156
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
157
|
+
|
|
158
|
+
const v = Math.min( 1, Math.max( 0, rgb[ c ] ) );
|
|
159
|
+
out[ o + c ] = Math.round( 255 * ( linear ? v : linearToSRGB( v ) ) );
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
out[ o + 3 ] = alphaOf ? Math.round( 255 * Math.min( 1, Math.max( 0, alphaOf( values ) ) ) ) : 255;
|
|
139
164
|
|
|
140
165
|
}
|
|
141
166
|
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* "sourceFile": "https://.../brown_photostudio_02_1k.hdr",
|
|
15
15
|
* "rotation": 0, // degrees around Y, matches the environmentRotation setting
|
|
16
16
|
* "intensity": 1 // drives both environmentIntensity and backgroundIntensity
|
|
17
|
+
* },
|
|
18
|
+
* "render": { // what the file asks the renderer for; reported, not applied
|
|
19
|
+
* "maxBounces": 15, "samples": 1024, "width": 1920, "height": 1080
|
|
17
20
|
* }
|
|
18
21
|
* }
|
|
19
22
|
*
|
|
@@ -56,10 +59,11 @@ function pickPayload( container ) {
|
|
|
56
59
|
const obj = asObject( container );
|
|
57
60
|
if ( ! obj ) return null;
|
|
58
61
|
|
|
62
|
+
const has = ( o ) => o && ( o.environment !== undefined || o.render !== undefined );
|
|
59
63
|
const scoped = asObject( obj.rayzee );
|
|
60
|
-
if (
|
|
64
|
+
if ( has( scoped ) ) return scoped;
|
|
61
65
|
|
|
62
|
-
return obj
|
|
66
|
+
return has( obj ) ? obj : null;
|
|
63
67
|
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -84,10 +88,29 @@ function normalizeEnvironment( raw ) {
|
|
|
84
88
|
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
const RENDER_KEYS = [ 'maxBounces', 'samples', 'width', 'height' ];
|
|
92
|
+
|
|
93
|
+
function normalizeRender( raw ) {
|
|
94
|
+
|
|
95
|
+
const render = asObject( raw );
|
|
96
|
+
if ( ! render ) return null;
|
|
97
|
+
|
|
98
|
+
const normalized = {};
|
|
99
|
+
for ( const key of RENDER_KEYS ) {
|
|
100
|
+
|
|
101
|
+
const n = asNumber( render[ key ] );
|
|
102
|
+
if ( n !== undefined && n > 0 ) normalized[ key ] = Math.round( n );
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Object.keys( normalized ).length ? normalized : null;
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
/**
|
|
88
111
|
* Reads scene metadata out of one `extras`-shaped container.
|
|
89
112
|
* @param {Object|string|null} container
|
|
90
|
-
* @returns {{ environment?: { sourceFile: string, rotation?: number, intensity?: number } }|null}
|
|
113
|
+
* @returns {{ environment?: { sourceFile: string, rotation?: number, intensity?: number }, render?: { maxBounces?: number, samples?: number, width?: number, height?: number } }|null}
|
|
91
114
|
*/
|
|
92
115
|
export function parseSceneMetadata( container ) {
|
|
93
116
|
|
|
@@ -95,14 +118,16 @@ export function parseSceneMetadata( container ) {
|
|
|
95
118
|
if ( ! payload ) return null;
|
|
96
119
|
|
|
97
120
|
const environment = normalizeEnvironment( payload.environment );
|
|
98
|
-
|
|
121
|
+
const render = normalizeRender( payload.render );
|
|
122
|
+
if ( ! environment && ! render ) return null;
|
|
123
|
+
return { ...( environment ? { environment } : {} ), ...( render ? { render } : {} ) };
|
|
99
124
|
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
128
|
* Reads scene metadata out of a GLTFLoader parse result.
|
|
104
129
|
* @param {Object} gltf - The object GLTFLoader resolves with
|
|
105
|
-
* @returns {
|
|
130
|
+
* @returns {ReturnType<typeof parseSceneMetadata>}
|
|
106
131
|
*/
|
|
107
132
|
export function extractSceneMetadata( gltf ) {
|
|
108
133
|
|
package/src/RayzeeRenderer.js
CHANGED
|
@@ -1216,8 +1216,9 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1216
1216
|
/**
|
|
1217
1217
|
* Scene-level authoring metadata carried by the current model file (glTF `extras`),
|
|
1218
1218
|
* or null when the file has none. See {@link module:Processor/SceneMetadata}. A pbrt scene
|
|
1219
|
-
* without an infinite light carries `environment: { enabled: false }`.
|
|
1220
|
-
*
|
|
1219
|
+
* without an infinite light carries `environment: { enabled: false }`. `render` is what the file asks
|
|
1220
|
+
* the renderer for (pbrt's maxdepth, pixelsamples and film resolution); nothing applies it.
|
|
1221
|
+
* @type {{ environment?: { sourceFile?: string, rotation?: number, intensity?: number, enabled?: boolean }, render?: { maxBounces?: number, samples?: number, width?: number, height?: number } }|null}
|
|
1221
1222
|
*/
|
|
1222
1223
|
get sceneMetadata() {
|
|
1223
1224
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -1443,12 +1443,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1443
1443
|
|
|
1444
1444
|
}
|
|
1445
1445
|
|
|
1446
|
-
/** The material layers the scene uses
|
|
1446
|
+
/** The material layers the scene uses. */
|
|
1447
1447
|
_layersToCompile() {
|
|
1448
1448
|
|
|
1449
|
-
|
|
1450
|
-
if ( this._integrator ) layers.diffuseTransmission = false;
|
|
1451
|
-
return layers;
|
|
1449
|
+
return this.materialData.materialLayers();
|
|
1452
1450
|
|
|
1453
1451
|
}
|
|
1454
1452
|
|
|
@@ -2083,7 +2081,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
2083
2081
|
|
|
2084
2082
|
this._integrator?.registerKernels( {
|
|
2085
2083
|
km: this._kernelManager, qm, pb, counters, bounceCountsBuf, wfCurrentBounce, copyReadB, copyWriteA, maxRays, w, own,
|
|
2086
|
-
freshLight, freshTri, freshBvh, freshMat, freshEnvTex, freshEnvCDF,
|
|
2084
|
+
freshLight, freshTri, freshBvh, freshMat, freshEnvTex, freshEnvCDF, materialLayers: this._compiledLayers,
|
|
2087
2085
|
} );
|
|
2088
2086
|
|
|
2089
2087
|
const fwFn = buildFinalWriteKernel( {
|
package/src/TSL/Bidirectional.js
CHANGED
|
@@ -156,8 +156,6 @@ export function resolveSurfaceMaterial( materialIndex, uv, N, materialBuffer ) {
|
|
|
156
156
|
material.normalMapIndex.assign( int( - 1 ) );
|
|
157
157
|
material.bumpMapIndex.assign( int( - 1 ) );
|
|
158
158
|
material.displacementMapIndex.assign( int( - 1 ) );
|
|
159
|
-
// Shade leaves diffuse transmission out in this integrator (its strategies do not cross surfaces yet).
|
|
160
|
-
material.diffuseTransmission.assign( 0.0 );
|
|
161
159
|
|
|
162
160
|
const samples = MaterialSamples.wrap( sampleAllMaterialTextures( material, uv, N, vec4( 0.0 ) ) ).toVar();
|
|
163
161
|
const rawRough = samples.roughness.toVar();
|
|
@@ -187,6 +185,8 @@ export function resolveSurfaceMaterial( materialIndex, uv, N, materialBuffer ) {
|
|
|
187
185
|
material.iridescenceThicknessRange.assign( vec2( material.iridescenceThicknessRange.x, ext.iridescenceThickness ) );
|
|
188
186
|
material.specularIntensity.assign( ext.specularIntensity );
|
|
189
187
|
material.specularColor.assign( ext.specularColor );
|
|
188
|
+
material.diffuseTransmission.assign( ext.diffuseTransmission );
|
|
189
|
+
material.diffuseTransmissionColor.assign( ext.diffuseTransmissionColor );
|
|
190
190
|
|
|
191
191
|
If( rawRough.lessThan( MIN_ROUGHNESS ).and( material.anisotropy.equal( 0.0 ) ).and( material.clearcoat.equal( 0.0 ) )
|
|
192
192
|
.and( material.transmission.equal( 0.0 ) ).and( material.subsurface.equal( 0.0 ) ), () => {
|
|
@@ -199,6 +199,10 @@ export function resolveSurfaceMaterial( materialIndex, uv, N, materialBuffer ) {
|
|
|
199
199
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
// The normal of the frame with `toward` on top: a surface that passes light through diffusely is seen from either side.
|
|
203
|
+
export const facingSide = ( N, toward, material ) => select(
|
|
204
|
+
material.diffuseTransmission.greaterThan( 0.0 ).and( dot( N, toward ).lessThan( 0.0 ) ), N.negate(), N );
|
|
205
|
+
|
|
202
206
|
// The light end's cosine with the shading-normal correction (Veach 5.3.2): |V·N| |d·Ng| / |V·Ng|.
|
|
203
207
|
export const lightEndCosine = ( V, N, facetN, toOther ) =>
|
|
204
208
|
abs( dot( V, N ) ).mul( abs( dot( toOther, facetN ) ) ).div( max( abs( dot( V, facetN ) ), 1e-4 ) );
|
package/src/TSL/ConnectKernel.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
Fn, float, vec2, vec4, int, uint, If, instanceIndex, atomicLoad, Return, dot, sqrt, min, max, length,
|
|
7
|
+
Fn, float, vec2, vec4, int, uint, If, instanceIndex, atomicLoad, Return, dot, sqrt, min, max, length, select, abs,
|
|
8
8
|
} from 'three/tsl';
|
|
9
9
|
|
|
10
10
|
import { getRandomSample1D, pcgHash, SAMPLER_DIMS_PER_BOUNCE } from './Random.js';
|
|
@@ -15,7 +15,7 @@ import { traverseBVHShadow } from './BVHTraversal.js';
|
|
|
15
15
|
import { offsetRayOrigin, SHADOW_END, luminance } from './Common.js';
|
|
16
16
|
import { regularizePathContribution } from './PathTracerCore.js';
|
|
17
17
|
import {
|
|
18
|
-
resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, STRATEGY, DIM_CONNECT_PATH, DIM_CONNECT_VERTEX,
|
|
18
|
+
resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, facingSide, STRATEGY, DIM_CONNECT_PATH, DIM_CONNECT_VERTEX,
|
|
19
19
|
} from './Bidirectional.js';
|
|
20
20
|
import { COUNTER } from '../Processor/QueueManager.js';
|
|
21
21
|
import {
|
|
@@ -35,6 +35,8 @@ export function buildConnectKernel( params ) {
|
|
|
35
35
|
maxBounceCount, globalIlluminationIntensity, fireflyThreshold,
|
|
36
36
|
// Vertex merging's η² at a point (Bidirectional.js mergeVmAt), or null without it.
|
|
37
37
|
mergeVm = null,
|
|
38
|
+
// Some material passes light through diffusely: a connection may then reach either end from behind.
|
|
39
|
+
diffuseTransmission = true,
|
|
38
40
|
} = params;
|
|
39
41
|
|
|
40
42
|
return Fn( () => {
|
|
@@ -89,7 +91,15 @@ export function buildConnectKernel( params ) {
|
|
|
89
91
|
const cosCameraFacet = dot( dir, cam.facetN ).toVar();
|
|
90
92
|
const cosLightFacet = dot( dir, light.facetN ).negate().toVar();
|
|
91
93
|
// The camera end tests as NEE does; at the light end the camera vertex is the viewer, which only the facet bounds.
|
|
92
|
-
|
|
94
|
+
// Through a surface that transmits diffusely, either end may also be reached from behind (checked below).
|
|
95
|
+
const inFront = cosCamera.greaterThan( 0.0 ).and( cosCameraFacet.greaterThan( 0.0 ) ).and( cosLightFacet.greaterThan( 0.0 ) );
|
|
96
|
+
const cameraBehind = cosCamera.lessThan( 0.0 ).and( cosCameraFacet.lessThan( 0.0 ) );
|
|
97
|
+
const lightBehind = cosLightFacet.lessThan( 0.0 );
|
|
98
|
+
const possible = diffuseTransmission
|
|
99
|
+
? cameraBehind.or( cosCamera.greaterThan( 0.0 ).and( cosCameraFacet.greaterThan( 0.0 ) ) )
|
|
100
|
+
.and( lightBehind.or( cosLightFacet.greaterThan( 0.0 ) ) )
|
|
101
|
+
: inFront;
|
|
102
|
+
If( possible.not(), () => {
|
|
93
103
|
|
|
94
104
|
Return();
|
|
95
105
|
|
|
@@ -98,19 +108,28 @@ export function buildConnectKernel( params ) {
|
|
|
98
108
|
const cameraMaterial = resolveSurfaceMaterial( cam.materialIndex, cam.uv, cam.N, materialBuffer );
|
|
99
109
|
const lightMaterial = resolveSurfaceMaterial( light.materialIndex, light.uv, light.N, materialBuffer );
|
|
100
110
|
const toCamera = dir.negate();
|
|
111
|
+
if ( diffuseTransmission ) If( cameraBehind.and( cameraMaterial.diffuseTransmission.lessThanEqual( 0.0 ) )
|
|
112
|
+
.or( lightBehind.and( lightMaterial.diffuseTransmission.lessThanEqual( 0.0 ) ) ), () => {
|
|
101
113
|
|
|
114
|
+
Return();
|
|
115
|
+
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
// Each end's BSDF and densities read in the frame with their own first direction on top.
|
|
119
|
+
const camN = ( toward ) => ( diffuseTransmission ? facingSide( cam.N, toward, cameraMaterial ) : cam.N );
|
|
120
|
+
const lightN = ( toward ) => ( diffuseTransmission ? facingSide( light.N, toward, lightMaterial ) : light.N );
|
|
102
121
|
const fCamera = evaluateMaterialResponse( cam.V, dir, cam.N, cameraMaterial ).toVar();
|
|
103
|
-
const fLight = evaluateMaterialResponse( toCamera, light.V,
|
|
104
|
-
const geometry = cosCamera.mul( lightEndCosine( light.V, light.N, light.facetN, toCamera ) ).div( dist2 );
|
|
122
|
+
const fLight = evaluateMaterialResponse( toCamera, light.V, lightN( toCamera ), lightMaterial ).toVar();
|
|
123
|
+
const geometry = ( diffuseTransmission ? abs( cosCamera ) : cosCamera ).mul( lightEndCosine( light.V, light.N, light.facetN, toCamera ) ).div( dist2 );
|
|
105
124
|
|
|
106
125
|
const cameraForward = calculateMaterialPDF( cam.V, dir, cam.N, cameraMaterial );
|
|
107
|
-
const cameraReverse = calculateMaterialPDF( dir, cam.V,
|
|
126
|
+
const cameraReverse = calculateMaterialPDF( dir, cam.V, camN( dir ), cameraMaterial );
|
|
108
127
|
const lightForward = calculateMaterialPDF( light.V, toCamera, light.N, lightMaterial );
|
|
109
|
-
const lightReverse = calculateMaterialPDF( toCamera, light.V,
|
|
128
|
+
const lightReverse = calculateMaterialPDF( toCamera, light.V, lightN( toCamera ), lightMaterial );
|
|
110
129
|
|
|
111
130
|
// Georgiev (40)-(41): each side's sum, from the other side's density of reaching it.
|
|
112
|
-
const wLight = misPartial( cameraForward.mul( cosLightFacet ).div( dist2 ), light, lightReverse );
|
|
113
|
-
const wCamera = misPartial( lightForward.mul( cosCameraFacet ).div( dist2 ), cam, cameraReverse );
|
|
131
|
+
const wLight = misPartial( cameraForward.mul( diffuseTransmission ? abs( cosLightFacet ) : cosLightFacet ).div( dist2 ), light, lightReverse );
|
|
132
|
+
const wCamera = misPartial( lightForward.mul( diffuseTransmission ? abs( cosCameraFacet ) : cosCameraFacet ).div( dist2 ), cam, cameraReverse );
|
|
114
133
|
|
|
115
134
|
const scattering = cameraVertices.add( c ).add( int( 1 ) );
|
|
116
135
|
// Alone, a path of k scattering vertices is reached by k − 1 connections.
|
|
@@ -123,8 +142,9 @@ export function buildConnectKernel( params ) {
|
|
|
123
142
|
|
|
124
143
|
If( luminance( contribution ).greaterThan( 0.0 ), () => {
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
const
|
|
145
|
+
// Each end of the shadow ray leaves from the side the connection takes.
|
|
146
|
+
const origin = offsetRayOrigin( cam.position, diffuseTransmission ? select( cameraBehind, cam.facetN.negate(), cam.facetN ) : cam.facetN ).toVar();
|
|
147
|
+
const segment = offsetRayOrigin( light.position, diffuseTransmission ? select( lightBehind, light.facetN.negate(), light.facetN ) : light.facetN ).sub( origin ).toVar();
|
|
128
148
|
const segmentLength = length( segment ).toVar();
|
|
129
149
|
const visibility = traceShadowRayRefractiveOpaque(
|
|
130
150
|
origin, segment.div( segmentLength ), segmentLength.mul( SHADOW_END ),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
Fn, float, vec3, vec4, int, uint, If, instanceIndex, atomicAdd, atomicLoad, atomicStore, Return,
|
|
8
|
-
dot, sqrt, max, min, floor, length, localId, workgroupId,
|
|
8
|
+
dot, sqrt, max, min, floor, length, localId, workgroupId, select, abs,
|
|
9
9
|
} from 'three/tsl';
|
|
10
10
|
|
|
11
11
|
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
@@ -15,7 +15,7 @@ import { traverseBVHShadowCameraCulled } from './BVHTraversal.js';
|
|
|
15
15
|
import { offsetRayOrigin, SHADOW_END, sanitizeRGB } from './Common.js';
|
|
16
16
|
import { pcgHash } from './Random.js';
|
|
17
17
|
import { regularizePathContribution } from './PathTracerCore.js';
|
|
18
|
-
import { resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, STRATEGY, SPLAT_SCALE, SPLAT_MAX } from './Bidirectional.js';
|
|
18
|
+
import { resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, facingSide, STRATEGY, SPLAT_SCALE, SPLAT_MAX } from './Bidirectional.js';
|
|
19
19
|
import {
|
|
20
20
|
cachedVertex, readVertexRecord, readLightPathLength, readRayRadiance, writeRayRadiance,
|
|
21
21
|
} from '../Processor/PackedRayBuffer.js';
|
|
@@ -33,6 +33,8 @@ export function buildLightSplatKernel( params ) {
|
|
|
33
33
|
renderWidth, renderHeight,
|
|
34
34
|
globalIlluminationIntensity, fireflyThreshold, accumFrame, frame,
|
|
35
35
|
mergeVm = null,
|
|
36
|
+
// Some material passes light through diffusely: the camera may then see a light vertex from behind its surface.
|
|
37
|
+
diffuseTransmission = true,
|
|
36
38
|
} = params;
|
|
37
39
|
|
|
38
40
|
return Fn( () => {
|
|
@@ -60,8 +62,13 @@ export function buildLightSplatKernel( params ) {
|
|
|
60
62
|
const dir = toCamera.div( sqrt( dist2 ) ).toVar();
|
|
61
63
|
const cosFacet = dot( dir, v.facetN ).toVar();
|
|
62
64
|
const cosCamera = dot( dir, cameraForward ).negate().toVar();
|
|
63
|
-
// The camera is this vertex's viewer: like a camera path, only the facet decides which side it is on.
|
|
64
|
-
|
|
65
|
+
// The camera is this vertex's viewer: like a camera path, only the facet decides which side it is on. Behind the
|
|
66
|
+
// surface (only through diffuse transmission, checked below) the cull flag is the far side's, bit 1.
|
|
67
|
+
const behind = cosFacet.lessThan( 0.0 );
|
|
68
|
+
const culled = diffuseTransmission
|
|
69
|
+
? select( behind, v.extra.bitAnd( uint( 2 ) ), v.extra.bitAnd( uint( 1 ) ) ).notEqual( uint( 0 ) )
|
|
70
|
+
: v.extra.notEqual( uint( 0 ) );
|
|
71
|
+
If( ( diffuseTransmission ? cosFacet.equal( 0.0 ) : cosFacet.lessThanEqual( 0.0 ) ).or( cosCamera.lessThanEqual( 1e-6 ) ).or( culled ), () => {
|
|
65
72
|
|
|
66
73
|
Return();
|
|
67
74
|
|
|
@@ -83,14 +90,22 @@ export function buildLightSplatKernel( params ) {
|
|
|
83
90
|
} );
|
|
84
91
|
|
|
85
92
|
const material = resolveSurfaceMaterial( v.materialIndex, v.uv, v.N, materialBuffer );
|
|
86
|
-
|
|
93
|
+
if ( diffuseTransmission ) If( behind.and( material.diffuseTransmission.lessThanEqual( 0.0 ) ), () => {
|
|
94
|
+
|
|
95
|
+
Return();
|
|
96
|
+
|
|
97
|
+
} );
|
|
98
|
+
|
|
99
|
+
// Read in the frame with the camera on top.
|
|
100
|
+
const viewN = diffuseTransmission ? facingSide( v.N, dir, material ) : v.N;
|
|
101
|
+
const f = evaluateMaterialResponse( dir, v.V, viewN, material ).toVar();
|
|
87
102
|
|
|
88
103
|
// The pinhole's solid-angle density for a uniform point in a pixel: 1 / (A_pixel cos³θ).
|
|
89
104
|
const cameraPdfW = float( 1.0 ).div( pixelArea.mul( cosCamera ).mul( cosCamera ).mul( cosCamera ) );
|
|
90
105
|
const toSurface = cameraPdfW.div( dist2 ).toVar();
|
|
91
106
|
|
|
92
107
|
// Georgiev (46); the camera side has no sum of its own.
|
|
93
|
-
const wLight = misPartial( toSurface.mul( cosFacet ).div( float( lightPaths ) ), v, calculateMaterialPDF( dir, v.V,
|
|
108
|
+
const wLight = misPartial( toSurface.mul( diffuseTransmission ? abs( cosFacet ) : cosFacet ).div( float( lightPaths ) ), v, calculateMaterialPDF( dir, v.V, viewN, material ) );
|
|
94
109
|
const bounces = c.add( uint( 1 ) );
|
|
95
110
|
const gi = float( 1.0 ).toVar();
|
|
96
111
|
If( bounces.greaterThan( uint( 1 ) ), () => {
|
|
@@ -105,7 +120,7 @@ export function buildLightSplatKernel( params ) {
|
|
|
105
120
|
float( c ), fireflyThreshold, int( accumFrame ),
|
|
106
121
|
).toVar();
|
|
107
122
|
|
|
108
|
-
const origin = offsetRayOrigin( v.position, v.facetN ).toVar();
|
|
123
|
+
const origin = offsetRayOrigin( v.position, diffuseTransmission ? select( behind, v.facetN.negate(), v.facetN ) : v.facetN ).toVar();
|
|
109
124
|
const segment = cameraPosition.sub( origin ).toVar();
|
|
110
125
|
const segmentLength = length( segment ).toVar();
|
|
111
126
|
// This segment stands in for the primary ray, which sees through the faces it culls.
|
package/src/TSL/MergeKernel.js
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
Fn, float, vec3, vec4, int, uint, ivec3, If, Loop, instanceIndex, atomicLoad, atomicStore, atomicFunc, Return, dot, floor, max, min, select,
|
|
15
|
-
length, log2, exp2, Break,
|
|
15
|
+
length, log2, exp2, Break, abs,
|
|
16
16
|
} from 'three/tsl';
|
|
17
17
|
|
|
18
18
|
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
19
19
|
import { calculateMaterialPDF } from './LightsSampling.js';
|
|
20
20
|
import { luminance } from './Common.js';
|
|
21
21
|
import { regularizePathContribution } from './PathTracerCore.js';
|
|
22
|
-
import { resolveSurfaceMaterial, misMergePartial, misWeight, strategyWeight, mergeRadiusAt, mergeEta, STRATEGY } from './Bidirectional.js';
|
|
22
|
+
import { resolveSurfaceMaterial, misMergePartial, misWeight, strategyWeight, mergeRadiusAt, mergeEta, facingSide, STRATEGY } from './Bidirectional.js';
|
|
23
23
|
import { COUNTER } from '../Processor/QueueManager.js';
|
|
24
24
|
import {
|
|
25
25
|
readRayRadiance, writeRayRadiance, pendingVertex, cachedVertex, readVertexTag, readVertexRecord, readVertexPosition,
|
|
@@ -105,6 +105,8 @@ export function buildMergeKernel( params ) {
|
|
|
105
105
|
const {
|
|
106
106
|
rayBufferRW, hitBufferRO, activeIndicesRO, counters, head, materialBuffer, bidirectional: bdpt,
|
|
107
107
|
maxBounceCount, globalIlluminationIntensity, fireflyThreshold, accumFrame,
|
|
108
|
+
// Some material passes light through diffusely: light arriving from behind its surface merges too.
|
|
109
|
+
diffuseTransmission = true,
|
|
108
110
|
} = params;
|
|
109
111
|
const { passTag, slotsPerPath, strategyView, hashMask } = bdpt;
|
|
110
112
|
|
|
@@ -173,19 +175,23 @@ export function buildMergeKernel( params ) {
|
|
|
173
175
|
const light = readVertexRecord( hitBufferRO, at );
|
|
174
176
|
const cosShading = dot( light.V, cam.N ).toVar();
|
|
175
177
|
const cosFacet = dot( light.V, cam.facetN ).toVar();
|
|
176
|
-
// Light arriving on this side
|
|
177
|
-
// part of the sphere past the corner, which
|
|
178
|
-
|
|
178
|
+
// Light arriving on this side, or from behind a surface that passes it through diffusely. A corner's
|
|
179
|
+
// other wall stays in: its light vertices stand in for the part of the sphere past the corner, which
|
|
180
|
+
// the density divides by too.
|
|
181
|
+
const onThisSide = cosShading.greaterThan( 0.0 ).and( cosFacet.greaterThan( 0.0 ) );
|
|
182
|
+
const throughSurface = material.diffuseTransmission.greaterThan( 0.0 ).and( cosShading.lessThan( 0.0 ) ).and( cosFacet.lessThan( 0.0 ) );
|
|
183
|
+
If( diffuseTransmission ? onThisSide.or( throughSurface ) : onThisSide, () => {
|
|
179
184
|
|
|
180
185
|
const f = evaluateMaterialResponse( cam.V, light.V, cam.N, material );
|
|
181
186
|
const wLight = misMergePartial( light, calculateMaterialPDF( cam.V, light.V, cam.N, material ), vc );
|
|
182
|
-
const
|
|
187
|
+
const reverseN = diffuseTransmission ? facingSide( cam.N, light.V, material ) : cam.N;
|
|
188
|
+
const wCamera = misMergePartial( cam, calculateMaterialPDF( light.V, cam.V, reverseN, material ), vc );
|
|
183
189
|
const scattering = cameraDepth.add( l );
|
|
184
190
|
// Alone, a path of k scattering vertices is reached by k merges.
|
|
185
191
|
const weight = strategyWeight( strategyView, STRATEGY.MERGE, misWeight( wLight, wCamera ), float( 1.0 ).div( float( scattering ) ) );
|
|
186
192
|
const gi = select( scattering.greaterThan( int( 1 ) ), globalIlluminationIntensity, float( 1.0 ) );
|
|
187
193
|
// The photon's flux is per unit area; the BSDF wants it per projected solid angle (Veach 5.3.2).
|
|
188
|
-
const correction = cosShading.div( max( cosFacet, 1e-4 ) );
|
|
194
|
+
const correction = diffuseTransmission ? abs( cosShading ).div( max( abs( cosFacet ), 1e-4 ) ) : cosShading.div( max( cosFacet, 1e-4 ) );
|
|
189
195
|
gathered.addAssign( regularizePathContribution(
|
|
190
196
|
cam.throughput.mul( f ).mul( light.throughput ).mul( weight.mul( correction ).div( eta ).mul( gi ) ),
|
|
191
197
|
float( scattering.sub( int( 1 ) ) ), fireflyThreshold, int( accumFrame ),
|