rayzee 8.5.0 → 8.6.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/README.md +60 -4
- package/dist/{DLSSNeural-BNSJ8V3z.js → DLSSNeural-BhBCBgKf.js} +5 -5
- package/dist/{DLSSSuperRes-BoEW89Cl.js → DLSSSuperRes-BCA7F_k5.js} +5 -5
- package/dist/ToneMapGPU-BzcLheoR.js +533 -0
- package/dist/rayzee.es.js +27198 -9047
- package/dist/rayzee.umd.js +255 -187
- package/package.json +1 -1
- package/src/AssetConfig.js +15 -2
- package/src/Color/BakedViews.js +134 -0
- package/src/Color/BuiltinViews.js +261 -0
- package/src/Color/ColorManagement.js +1130 -0
- package/src/Color/ColorSpaces.js +432 -0
- package/src/Color/Displays.js +19 -0
- package/src/Color/InputColorSpaces.js +243 -0
- package/src/Color/LutBake.js +371 -0
- package/src/Color/OcioRuntime.js +338 -0
- package/src/Color/OcioViews.js +419 -0
- package/src/Color/ViewTransforms.js +323 -0
- package/src/Color/WorkingMatrix.js +86 -0
- package/src/EngineDefaults.js +55 -3
- package/src/EngineIssues.js +3 -0
- package/src/Passes/AIUpscaler.js +16 -7
- package/src/PathTracerApp.js +227 -7
- package/src/Processor/AssetLoader.js +1 -2
- package/src/Processor/EmissiveTriangleBuilder.js +34 -2
- package/src/Processor/GeometryExtractor.js +151 -138
- package/src/Processor/LightSerializer.js +8 -3
- package/src/Processor/MaterialPacking.js +168 -0
- package/src/Processor/PackedRayBuffer.js +6 -2
- package/src/Processor/ProceduralSky.js +4 -0
- package/src/Processor/SceneProcessor.js +25 -1
- package/src/Processor/SimpleSky.js +8 -0
- package/src/Processor/TextureCreator.js +137 -91
- package/src/Processor/TextureReadback.js +92 -0
- package/src/Processor/ToneMapCPU.js +80 -165
- package/src/Processor/ToneMapGPU.js +122 -151
- package/src/RenderSettings.js +1 -0
- package/src/Stages/Compositor.js +11 -2
- package/src/Stages/PathTracer.js +2 -0
- package/src/Stages/PathTracerStage.js +1 -1
- package/src/TSL/Common.js +15 -0
- package/src/TSL/Debugger.js +2 -3
- package/src/TSL/EmissiveSampling.js +6 -8
- package/src/TSL/ExtendKernel.js +9 -0
- package/src/TSL/Fresnel.js +23 -1
- package/src/TSL/HitFacet.js +102 -0
- package/src/TSL/LightsDirect.js +12 -33
- package/src/TSL/LightsSampling.js +15 -7
- package/src/TSL/MaterialEvaluation.js +26 -19
- package/src/TSL/MaterialProperties.js +74 -37
- package/src/TSL/MaterialTransmission.js +2 -3
- package/src/TSL/ShadeKernel.js +34 -21
- package/src/TSL/ShadowTerminator.js +125 -0
- package/src/TSL/Struct.js +9 -0
- package/src/TSL/Subsurface.js +2 -3
- package/src/index.js +29 -1
- package/src/managers/EnvironmentManager.js +20 -0
- package/src/managers/MaterialDataManager.js +118 -208
- package/src/managers/UniformManager.js +1 -0
- package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
package/src/Stages/Compositor.js
CHANGED
|
@@ -66,8 +66,17 @@ export class Compositor extends RenderStage {
|
|
|
66
66
|
*/
|
|
67
67
|
_resolveSourceTexture( context ) {
|
|
68
68
|
|
|
69
|
-
return context.getTexture( 'bloom:output' )
|
|
70
|
-
|
|
69
|
+
return context.getTexture( 'bloom:output' ) || this.resolveLightTexture( context );
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The newest scene-referred image the pipeline holds — denoised when a denoiser has run — without
|
|
75
|
+
* bloom, which is a look added on top rather than light in the scene.
|
|
76
|
+
*/
|
|
77
|
+
resolveLightTexture( context ) {
|
|
78
|
+
|
|
79
|
+
return context.getTexture( 'oidn:output' )
|
|
71
80
|
|| context.getTexture( 'edgeFiltering:output' )
|
|
72
81
|
|| context.getTexture( 'bilateralFiltering:output' )
|
|
73
82
|
|| context.getTexture( 'asvgf:output' )
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -1393,6 +1393,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1393
1393
|
activeIndicesRO: qm.getActiveReadRO(),
|
|
1394
1394
|
counters,
|
|
1395
1395
|
maxRayCount: this._wfMaxRayCount,
|
|
1396
|
+
shadowTerminatorOffset: this.shadowTerminatorOffset,
|
|
1396
1397
|
} );
|
|
1397
1398
|
this._kernelManager.register( 'extend',
|
|
1398
1399
|
extFn().compute(
|
|
@@ -1491,6 +1492,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1491
1492
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1492
1493
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1493
1494
|
fireflyThreshold: this.fireflyThreshold,
|
|
1495
|
+
shadowTerminatorOffset: this.shadowTerminatorOffset,
|
|
1494
1496
|
// RNG axis only (keys STBN via frame & 63).
|
|
1495
1497
|
frame: this.seedFrame,
|
|
1496
1498
|
accumFrame: this.frame,
|
|
@@ -480,7 +480,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
480
480
|
this.setTriangleData( this.sdfs.triangleData, this.sdfs.triangleCount );
|
|
481
481
|
this.setBVHData( this.sdfs.bvhData );
|
|
482
482
|
this.setInstanceTable( this.sdfs.instanceTable );
|
|
483
|
-
this.materialData.setMaterialData( this.sdfs.materialData );
|
|
483
|
+
this.materialData.setMaterialData( this.sdfs.materialData, this.sdfs.materials?.map( m => m.sources ) );
|
|
484
484
|
|
|
485
485
|
// Material texture arrays
|
|
486
486
|
this.materialData.loadTexturesFromSdfs();
|
package/src/TSL/Common.js
CHANGED
|
@@ -8,6 +8,21 @@ import {
|
|
|
8
8
|
ShadowMaterial,
|
|
9
9
|
} from './Struct.js';
|
|
10
10
|
|
|
11
|
+
// Spawn point for a ray leaving a surface, Cycles' classic ray_offset: within 1 unit of the origin,
|
|
12
|
+
// 1e-5 along n; beyond, 32 float ULPs per axis toward n. A fixed 1 mm let rays slip out of
|
|
13
|
+
// sub-millimetre grooves — a 14 cm camera read up to 14 % bright in its crevices against Cycles.
|
|
14
|
+
export const offsetRayOrigin = /*@__PURE__*/ wgslFn( `
|
|
15
|
+
fn offsetRayOrigin( p: vec3f, n: vec3f ) -> vec3f {
|
|
16
|
+
let step = select( vec3<i32>( 32 ), vec3<i32>( -32 ), ( p < vec3f( 0.0 ) ) != ( n < vec3f( 0.0 ) ) );
|
|
17
|
+
let far = bitcast<vec3f>( bitcast<vec3<i32>>( p ) + step );
|
|
18
|
+
return select( far, p + n * 1e-5, abs( p ) < vec3f( 1.0 ) );
|
|
19
|
+
}
|
|
20
|
+
` );
|
|
21
|
+
|
|
22
|
+
// Visibility rays stop this fraction short of the sampled light point: unit-free, and ≫ float
|
|
23
|
+
// error, so an emitter coplanar with its fixture is never occluded by it.
|
|
24
|
+
export const SHADOW_END = 1.0 - 1e-4;
|
|
25
|
+
|
|
11
26
|
export const PI = 3.14159;
|
|
12
27
|
export const PI_INV = 1.0 / PI;
|
|
13
28
|
export const TWO_PI = 2.0 * PI;
|
package/src/TSL/Debugger.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import { Ray, HitInfo, RayTracingMaterial, MaterialSamples } from './Struct.js';
|
|
24
24
|
import { traverseBVHDebug as traverseBVH } from './BVHTraversal.js';
|
|
25
25
|
import { sampleEnvironment } from './Environment.js';
|
|
26
|
-
import { REC709_LUMINANCE_COEFFICIENTS, getMaterial } from './Common.js';
|
|
26
|
+
import { REC709_LUMINANCE_COEFFICIENTS, getMaterial, offsetRayOrigin } from './Common.js';
|
|
27
27
|
import { sampleAllMaterialTextures } from './TextureSampling.js';
|
|
28
28
|
import { pcgHash, wang_hash, RandomValue } from './Random.js';
|
|
29
29
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
@@ -336,8 +336,7 @@ export const TraceDebugMode = Fn( ( [
|
|
|
336
336
|
const xi = vec2( xi_r1, xi_r2 );
|
|
337
337
|
const bounceDir = cosineWeightedSample( { N: normalA, xi } ).toVar();
|
|
338
338
|
|
|
339
|
-
|
|
340
|
-
const bounceOrigin = hitInfo.hitPoint.add( normalA.mul( 0.001 ) );
|
|
339
|
+
const bounceOrigin = offsetRayOrigin( hitInfo.hitPoint, normalA );
|
|
341
340
|
const bounceRay = Ray( { origin: bounceOrigin, direction: bounceDir } );
|
|
342
341
|
|
|
343
342
|
const bounceHit = HitInfo.wrap( traverseBVH(
|
|
@@ -30,7 +30,7 @@ import { struct } from './patches.js';
|
|
|
30
30
|
import {
|
|
31
31
|
MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT,
|
|
32
32
|
computeDotProductsAniso, instanceRows, instanceNormalToWorld, instancePointToWorld,
|
|
33
|
-
unpackTriangleNormal, TRI_STRIDE
|
|
33
|
+
unpackTriangleNormal, TRI_STRIDE, SHADOW_END
|
|
34
34
|
} from './Common.js';
|
|
35
35
|
import { TRI_MATERIAL_MASK } from '../EngineDefaults.js';
|
|
36
36
|
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
@@ -591,13 +591,11 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
591
591
|
|
|
592
592
|
If( NoL.greaterThan( 0.0 ).and( dot( emissiveSample.direction, geomNormal ).greaterThan( 0.0 ) ), () => {
|
|
593
593
|
|
|
594
|
-
//
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
600
|
-
const visibility = traceShadowRayFn( rayOrigin, emissiveSample.direction, shadowDist );
|
|
594
|
+
// Aimed at the sampled point and stopped a relative hair short, as for area lights.
|
|
595
|
+
const rayOrigin = hitPoint.add( calculateRayOffsetFn( hitPoint, geomNormal, emissiveSample.direction ) );
|
|
596
|
+
const toSample = emissiveSample.position.sub( rayOrigin ).toVar();
|
|
597
|
+
const shadowDist = length( toSample ).toVar();
|
|
598
|
+
const visibility = traceShadowRayFn( rayOrigin, toSample.div( shadowDist ), shadowDist.mul( SHADOW_END ) );
|
|
601
599
|
|
|
602
600
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
603
601
|
|
package/src/TSL/ExtendKernel.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
writeHitPacked,
|
|
18
18
|
} from '../Processor/PackedRayBuffer.js';
|
|
19
19
|
import { COUNTER, RAY_FLAG } from '../Processor/QueueManager.js';
|
|
20
|
+
import { hitFacet, packHitFacet } from './HitFacet.js';
|
|
20
21
|
|
|
21
22
|
const WG_SIZE = 256;
|
|
22
23
|
|
|
@@ -29,6 +30,7 @@ export function buildExtendKernel( params ) {
|
|
|
29
30
|
activeIndicesRO,
|
|
30
31
|
counters,
|
|
31
32
|
maxRayCount,
|
|
33
|
+
shadowTerminatorOffset,
|
|
32
34
|
} = params;
|
|
33
35
|
|
|
34
36
|
const computeFn = Fn( () => {
|
|
@@ -64,6 +66,12 @@ export function buildExtendKernel( params ) {
|
|
|
64
66
|
ray, bvhBuffer, triangleBuffer, insideMedium,
|
|
65
67
|
) ).toVar();
|
|
66
68
|
|
|
69
|
+
const facet = hitFacet( {
|
|
70
|
+
triangleBuffer, bvhBuffer, triIdx: hitInfo.triangleIndex, instanceLeaf: hitInfo.instanceLeaf,
|
|
71
|
+
hitPoint: hitInfo.hitPoint, smoothNormal: hitInfo.normal, viewDir: direction.negate(),
|
|
72
|
+
didHit: hitInfo.didHit, liftEnabled: shadowTerminatorOffset.greaterThan( 0.0 ),
|
|
73
|
+
} );
|
|
74
|
+
|
|
67
75
|
writeHitPacked(
|
|
68
76
|
hitBufferRW, rayID,
|
|
69
77
|
hitInfo.dst,
|
|
@@ -73,6 +81,7 @@ export function buildExtendKernel( params ) {
|
|
|
73
81
|
uint( hitInfo.materialIndex ),
|
|
74
82
|
// Biased by one: leaf 0 is real, so 0 has to mean "no instance".
|
|
75
83
|
uint( hitInfo.instanceLeaf.add( int( 1 ) ) ),
|
|
84
|
+
packHitFacet( facet.faceN, facet.liftScale ),
|
|
76
85
|
);
|
|
77
86
|
|
|
78
87
|
} );
|
package/src/TSL/Fresnel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, float, vec3, max, clamp, sqrt } from 'three/tsl';
|
|
1
|
+
import { Fn, float, vec3, max, clamp, sqrt, select } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
const EPSILON = 1e-6;
|
|
4
4
|
|
|
@@ -58,3 +58,25 @@ export const iorToFresnel0 = Fn( ( [ transmittedIor, incidentIor ] ) => {
|
|
|
58
58
|
} );
|
|
59
59
|
|
|
60
60
|
export const dielectricF0 = ( ior ) => vec3( iorToFresnel0( ior, float( 1.0 ) ) );
|
|
61
|
+
|
|
62
|
+
// Unpolarised reflectance of a smooth dielectric interface, eta = n_transmitted / n_incident.
|
|
63
|
+
// Returns 1 under total internal reflection. Schlick runs 16-23 % low at 45-65° for eta 1.5.
|
|
64
|
+
export const fresnelDielectric = Fn( ( [ cosI, eta ] ) => {
|
|
65
|
+
|
|
66
|
+
const c = clamp( cosI, 0.0, 1.0 );
|
|
67
|
+
const g2 = eta.mul( eta ).sub( 1.0 ).add( c.mul( c ) );
|
|
68
|
+
const g = sqrt( max( g2, 0.0 ) );
|
|
69
|
+
const A = g.sub( c ).div( max( g.add( c ), EPSILON ) );
|
|
70
|
+
const B = c.mul( g.add( c ) ).sub( 1.0 ).div( max( c.mul( g.sub( c ) ).add( 1.0 ), EPSILON ) );
|
|
71
|
+
return select( g2.greaterThan( 0.0 ), clamp( A.mul( A ).mul( 0.5 ).mul( B.mul( B ).add( 1.0 ) ), 0.0, 1.0 ), float( 1.0 ) );
|
|
72
|
+
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
// The exact curve rescaled to run 0 head-on to 1 at grazing, so F = mix( f0, f90, weight ) keeps
|
|
76
|
+
// KHR_materials_specular's f0/f90 controls. With f0 = F0(eta) and f90 = 1 it is fresnelDielectric.
|
|
77
|
+
export const dielectricFresnelWeight = Fn( ( [ cosI, eta ] ) => {
|
|
78
|
+
|
|
79
|
+
const F0 = iorToFresnel0( eta, float( 1.0 ) );
|
|
80
|
+
return clamp( fresnelDielectric( cosI, eta ).sub( F0 ).div( max( float( 1.0 ).sub( F0 ), EPSILON ) ), 0.0, 1.0 );
|
|
81
|
+
|
|
82
|
+
} );
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hit triangle's facet normal, packed into the hit record's spare lane by Extend.
|
|
3
|
+
*
|
|
4
|
+
* The hit record's `normal` is the interpolated vertex normal. Rays spawned from a surface must be
|
|
5
|
+
* offset along the facet itself — Cycles' ray_offset uses Ng — or a vertex normal bent away from its
|
|
6
|
+
* facet (foliage cards whose normals all point up) moves the origin within the card's own plane,
|
|
7
|
+
* and the continued ray hits the same card again. The lane also carries the shadow terminator lift.
|
|
8
|
+
*
|
|
9
|
+
* Layout: facet normal as an 11:11 octahedral pair (about 0.1°), then the lift's half float
|
|
10
|
+
* truncated to its top 10 bits (sign, exponent, 4 mantissa bits).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
vec2, vec3, float, int, uint, max, abs, dot, cross, normalize, clamp, round, select, If, uintBitsToFloat,
|
|
15
|
+
packHalf2x16, unpackHalf2x16,
|
|
16
|
+
} from 'three/tsl';
|
|
17
|
+
import {
|
|
18
|
+
getDatafromStorageBuffer, unpackTriangleNormal, TRI_STRIDE, instanceRows, instanceFaceNormalToWorld,
|
|
19
|
+
} from './Common.js';
|
|
20
|
+
import { shadowTerminatorLift } from './ShadowTerminator.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @returns {{ faceN: Node, liftScale: Node }} the facet normal on the viewer's side (the
|
|
24
|
+
* interpolated one for a degenerate triangle), and the terminator lift (0 unless `liftEnabled`)
|
|
25
|
+
*/
|
|
26
|
+
export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hitPoint, smoothNormal, viewDir, didHit, liftEnabled } ) {
|
|
27
|
+
|
|
28
|
+
const faceN = vec3( 0.0, 0.0, 1.0 ).toVar();
|
|
29
|
+
const liftScale = float( 0.0 ).toVar();
|
|
30
|
+
|
|
31
|
+
If( didHit, () => {
|
|
32
|
+
|
|
33
|
+
const recA = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).toVar();
|
|
34
|
+
const recB = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).toVar();
|
|
35
|
+
const recC = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).toVar();
|
|
36
|
+
const V0 = uintBitsToFloat( recA.xyz ).toVar();
|
|
37
|
+
const V1 = uintBitsToFloat( recB.xyz ).toVar();
|
|
38
|
+
const V2 = uintBitsToFloat( recC.xyz ).toVar();
|
|
39
|
+
|
|
40
|
+
// Shared geometry is stored in object space.
|
|
41
|
+
const faceLocal = cross( V1.sub( V0 ), V2.sub( V0 ) ).toVar();
|
|
42
|
+
const face = faceLocal.toVar();
|
|
43
|
+
const instanced = instanceLeaf.greaterThanEqual( int( 0 ) );
|
|
44
|
+
If( instanced, () => {
|
|
45
|
+
|
|
46
|
+
face.assign( instanceFaceNormalToWorld( instanceRows( bvhBuffer, instanceLeaf ), faceLocal ) );
|
|
47
|
+
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
const n = select( dot( face, face ).greaterThan( 0.0 ), normalize( face ), smoothNormal ).toVar();
|
|
51
|
+
faceN.assign( select( dot( n, viewDir ).lessThan( 0.0 ), n.negate(), n ) );
|
|
52
|
+
|
|
53
|
+
If( liftEnabled, () => {
|
|
54
|
+
|
|
55
|
+
liftScale.assign( shadowTerminatorLift( {
|
|
56
|
+
V0, V1, V2,
|
|
57
|
+
N0: unpackTriangleNormal( recA.w ), N1: unpackTriangleNormal( recB.w ), N2: unpackTriangleNormal( recC.w ),
|
|
58
|
+
hitPoint, instanced, bvhBuffer, instanceLeaf, faceN,
|
|
59
|
+
} ) );
|
|
60
|
+
|
|
61
|
+
} );
|
|
62
|
+
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
return { faceN, liftScale };
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Per component: TSL's select() on a bvec is not component-wise.
|
|
70
|
+
const signNotZero = v => vec2( select( v.x.greaterThanEqual( 0.0 ), 1.0, - 1.0 ), select( v.y.greaterThanEqual( 0.0 ), 1.0, - 1.0 ) );
|
|
71
|
+
|
|
72
|
+
export function packHitFacet( faceN, liftScale ) {
|
|
73
|
+
|
|
74
|
+
const p = faceN.xy.div( max( abs( faceN.x ).add( abs( faceN.y ) ).add( abs( faceN.z ) ), 1e-20 ) ).toVar();
|
|
75
|
+
If( faceN.z.lessThan( 0.0 ), () => {
|
|
76
|
+
|
|
77
|
+
p.assign( vec2( 1.0 ).sub( abs( p.yx ) ).mul( signNotZero( p ) ) );
|
|
78
|
+
|
|
79
|
+
} );
|
|
80
|
+
const q = round( clamp( p, - 1.0, 1.0 ).mul( 0.5 ).add( 0.5 ).mul( 2047.0 ) );
|
|
81
|
+
const h = packHalf2x16( vec2( liftScale, 0.0 ) ).bitAnd( uint( 0xffff ) ).shiftRight( uint( 6 ) );
|
|
82
|
+
return uint( q.x ).bitOr( uint( q.y ).shiftLeft( uint( 11 ) ) ).bitOr( h.shiftLeft( uint( 22 ) ) );
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @returns {{ faceN: Node, liftScale: Node }} */
|
|
87
|
+
export function unpackHitFacet( bits ) {
|
|
88
|
+
|
|
89
|
+
const e = vec2(
|
|
90
|
+
float( bits.bitAnd( uint( 0x7ff ) ) ),
|
|
91
|
+
float( bits.shiftRight( uint( 11 ) ).bitAnd( uint( 0x7ff ) ) ),
|
|
92
|
+
).div( 2047.0 ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
93
|
+
const z = float( 1.0 ).sub( abs( e.x ) ).sub( abs( e.y ) ).toVar();
|
|
94
|
+
If( z.lessThan( 0.0 ), () => {
|
|
95
|
+
|
|
96
|
+
e.assign( vec2( 1.0 ).sub( abs( e.yx ) ).mul( signNotZero( e ) ) );
|
|
97
|
+
|
|
98
|
+
} );
|
|
99
|
+
const liftScale = unpackHalf2x16( bits.shiftRight( uint( 22 ) ).shiftLeft( uint( 6 ) ) ).x;
|
|
100
|
+
return { faceN: normalize( vec3( e, z ) ), liftScale };
|
|
101
|
+
|
|
102
|
+
}
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -28,9 +28,9 @@ import {
|
|
|
28
28
|
import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
|
|
29
29
|
import {
|
|
30
30
|
REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, getDatafromStorageBuffer, instanceRows,
|
|
31
|
-
instanceFaceNormalToWorld, TRI_STRIDE, getAlphaShadowsUniform, shadowFlagsSettle
|
|
31
|
+
instanceFaceNormalToWorld, TRI_STRIDE, getAlphaShadowsUniform, shadowFlagsSettle, offsetRayOrigin,
|
|
32
32
|
} from './Common.js';
|
|
33
|
-
import {
|
|
33
|
+
import { fresnelDielectric } from './Fresnel.js';
|
|
34
34
|
import { calculateBeerLawAbsorption } from './MaterialTransmission.js';
|
|
35
35
|
import { getTransformedUV, sampleBucket } from './TextureSampling.js';
|
|
36
36
|
|
|
@@ -212,11 +212,8 @@ export const traceShadowRay = Fn( ( [
|
|
|
212
212
|
|
|
213
213
|
} );
|
|
214
214
|
|
|
215
|
-
//
|
|
216
|
-
const fresnel =
|
|
217
|
-
abs( dot( dir, N ) ),
|
|
218
|
-
iorToFresnel0( shadowMaterial.ior, float( 1.0 ) ),
|
|
219
|
-
);
|
|
215
|
+
// Shadow rays go straight through, so both faces see the outside-in angle and eta.
|
|
216
|
+
const fresnel = fresnelDielectric( abs( dot( dir, N ) ), max( shadowMaterial.ior, 1.0 ) );
|
|
220
217
|
|
|
221
218
|
const matTransmittance = float( 1.0 ).sub( fresnel ).mul( shadowMaterial.transmission );
|
|
222
219
|
transmittance.mulAssign( matTransmittance );
|
|
@@ -230,8 +227,9 @@ export const traceShadowRay = Fn( ( [
|
|
|
230
227
|
} );
|
|
231
228
|
|
|
232
229
|
// Continue ray past transmissive surface
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
const passEps = max( float( 1e-5 ), length( shadowHit.hitPoint ).mul( 1e-6 ) );
|
|
231
|
+
rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( passEps ) ) );
|
|
232
|
+
remainingDist.subAssign( shadowHit.dst.add( passEps ) );
|
|
235
233
|
|
|
236
234
|
} ).ElseIf( shadowMaterial.transparent, () => {
|
|
237
235
|
|
|
@@ -246,8 +244,9 @@ export const traceShadowRay = Fn( ( [
|
|
|
246
244
|
} );
|
|
247
245
|
|
|
248
246
|
// Continue ray past transparent surface
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
const passEps = max( float( 1e-5 ), length( shadowHit.hitPoint ).mul( 1e-6 ) );
|
|
248
|
+
rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( passEps ) ) );
|
|
249
|
+
remainingDist.subAssign( shadowHit.dst.add( passEps ) );
|
|
251
250
|
|
|
252
251
|
} ).Else( () => {
|
|
253
252
|
|
|
@@ -267,28 +266,8 @@ export const traceShadowRay = Fn( ( [
|
|
|
267
266
|
// RAY OFFSET CALCULATION
|
|
268
267
|
// ================================================================================
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// Base epsilon scaled by scene size; adjusted by material properties below.
|
|
273
|
-
const materialEpsilon = max( float( 1e-4 ), length( hitPoint ).mul( 1e-6 ) ).toVar();
|
|
274
|
-
|
|
275
|
-
If( material.transmission.greaterThan( 0.0 ), () => {
|
|
276
|
-
|
|
277
|
-
// Transmissive materials need larger offsets
|
|
278
|
-
materialEpsilon.mulAssign( 2.0 );
|
|
279
|
-
|
|
280
|
-
} );
|
|
281
|
-
|
|
282
|
-
If( material.roughness.lessThan( 0.1 ), () => {
|
|
283
|
-
|
|
284
|
-
// Smooth materials are more sensitive to precision issues
|
|
285
|
-
materialEpsilon.mulAssign( 1.5 );
|
|
286
|
-
|
|
287
|
-
} );
|
|
288
|
-
|
|
289
|
-
return normal.mul( materialEpsilon );
|
|
290
|
-
|
|
291
|
-
} );
|
|
269
|
+
// Vector from hitPoint to its ray spawn point (offsetRayOrigin), for callers that add it themselves.
|
|
270
|
+
export const calculateRayOffset = Fn( ( [ hitPoint, normal ] ) => offsetRayOrigin( hitPoint, normal ).sub( hitPoint ) );
|
|
292
271
|
|
|
293
272
|
// ================================================================================
|
|
294
273
|
// LIGHT IMPORTANCE ESTIMATION
|
|
@@ -81,15 +81,15 @@ import {
|
|
|
81
81
|
powerHeuristic,
|
|
82
82
|
balanceHeuristic,
|
|
83
83
|
computeDotProductsAniso,
|
|
84
|
+
offsetRayOrigin,
|
|
85
|
+
SHADOW_END,
|
|
84
86
|
} from './Common.js';
|
|
85
87
|
import {
|
|
86
88
|
sampleEquirectProbability,
|
|
87
89
|
} from './Environment.js';
|
|
90
|
+
import { shadowTerminatorOrigin } from './ShadowTerminator.js';
|
|
88
91
|
|
|
89
92
|
const TWO_PI = 2.0 * PI;
|
|
90
|
-
// Visibility rays stop this fraction short of the sampled light point: unit-free, and ≫ float
|
|
91
|
-
// error, so an emitter coplanar with its fixture is never occluded by it.
|
|
92
|
-
const SHADOW_END = 1.0 - 1e-4;
|
|
93
93
|
|
|
94
94
|
// =============================================================================
|
|
95
95
|
// Light Sampling Functions
|
|
@@ -795,12 +795,19 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
795
795
|
// Shadow catcher: when true, also accumulate the unoccluded (visibility=1) reference
|
|
796
796
|
// at every light/env site so the caller can form a shadow ratio. Dead path otherwise.
|
|
797
797
|
wantUnoccluded,
|
|
798
|
+
// The facet on the viewer's side, which shadow rays are offset off, and the shadow terminator lift
|
|
799
|
+
// (ShadowTerminator.js) for the light and environment ones
|
|
800
|
+
terminatorLift, facetNormal, terminatorCutoff,
|
|
798
801
|
] ) => {
|
|
799
802
|
|
|
800
803
|
const totalContribution = vec3( 0.0 ).toVar();
|
|
801
804
|
// Unoccluded reference (visibility forced to 1) — only filled when wantUnoccluded.
|
|
802
805
|
const unoccludedContribution = vec3( 0.0 ).toVar();
|
|
803
|
-
const rayOrigin =
|
|
806
|
+
const rayOrigin = offsetRayOrigin( hitPoint, facetNormal ).toVar();
|
|
807
|
+
const lightShadowOrigin = L => shadowTerminatorOrigin( {
|
|
808
|
+
hitPoint, offsetNormal: facetNormal, lift: terminatorLift, faceN: facetNormal, smoothN: geomNormal, L,
|
|
809
|
+
cutoff: terminatorCutoff,
|
|
810
|
+
} );
|
|
804
811
|
|
|
805
812
|
// Binds BVH params so shadow-ray sites at varying call depths use a 3-arg call
|
|
806
813
|
const shadow = Fn( ( [ origin, dir, maxDist ] ) =>
|
|
@@ -845,9 +852,10 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
845
852
|
|
|
846
853
|
// Light geometry is measured from the hit point; only the visibility ray starts on the lifted
|
|
847
854
|
// origin, aimed at the sampled point and stopped a relative hair short of it.
|
|
848
|
-
const
|
|
855
|
+
const lightOrigin = lightShadowOrigin( lightSample.direction ).toVar();
|
|
856
|
+
const toSample = hitPoint.add( lightSample.direction.mul( lightSample.distance ) ).sub( lightOrigin ).toVar();
|
|
849
857
|
const shadowDistance = length( toSample ).toVar();
|
|
850
|
-
const visibility = shadow(
|
|
858
|
+
const visibility = shadow( lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ) );
|
|
851
859
|
|
|
852
860
|
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
853
861
|
|
|
@@ -1004,7 +1012,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1004
1012
|
|
|
1005
1013
|
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction: envDirection, surfaceNormal: geomNormal } ) ), () => {
|
|
1006
1014
|
|
|
1007
|
-
const visibility = shadow(
|
|
1015
|
+
const visibility = shadow( lightShadowOrigin( envDirection ), envDirection, float( 1e20 ) );
|
|
1008
1016
|
|
|
1009
1017
|
If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
|
|
1010
1018
|
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
If, max, min, clamp, mix
|
|
4
4
|
} from 'three/tsl';
|
|
5
5
|
|
|
6
|
-
import { DotProducts, DFGResult } from './Struct.js';
|
|
6
|
+
import { DotProducts, DFGResult, BaseFresnel } from './Struct.js';
|
|
7
7
|
import { PI_INV, MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso } from './Common.js';
|
|
8
|
-
import { fresnelSchlick,
|
|
8
|
+
import { fresnelSchlick, fresnelDielectric, dielectricFresnelWeight } from './Fresnel.js';
|
|
9
9
|
import {
|
|
10
10
|
DistributionGGX, SheenDistribution, VisibilitySheen, VisibilityGGXSmithCorrelated,
|
|
11
|
-
sheenDirectionalAlbedo,
|
|
11
|
+
sheenDirectionalAlbedo, evaluateSpecularDFG, baseFresnelParams,
|
|
12
12
|
computeAnisoAlphas, DistributionGGXAniso, VisibilityGGXAniso,
|
|
13
13
|
} from './MaterialProperties.js';
|
|
14
14
|
import { evalIridescence } from './MaterialProperties.js';
|
|
@@ -43,12 +43,9 @@ export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
|
|
|
43
43
|
|
|
44
44
|
} ).Else( () => {
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
const F0 =
|
|
48
|
-
|
|
49
|
-
.mul( material.specularIntensity ),
|
|
50
|
-
vec3( 0.0 ), vec3( 1.0 )
|
|
51
|
-
).toVar();
|
|
46
|
+
const bf = BaseFresnel.wrap( baseFresnelParams( material, material.color.rgb ) ).toVar();
|
|
47
|
+
const F0 = bf.F0.toVar();
|
|
48
|
+
const iridF = vec3( 0.0 ).toVar();
|
|
52
49
|
|
|
53
50
|
// Modify material color for dispersive materials to enhance color separation
|
|
54
51
|
const materialColor = material.color.rgb.toVar();
|
|
@@ -72,13 +69,18 @@ export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
|
|
|
72
69
|
|
|
73
70
|
// Per glTF KHR_materials_iridescence spec: use max thickness when no texture
|
|
74
71
|
const thickness = material.iridescenceThicknessRange.y;
|
|
75
|
-
|
|
76
|
-
F0.assign( mix( F0,
|
|
72
|
+
iridF.assign( evalIridescence( float( 1.0 ), material.iridescenceIOR, dots.VoH, thickness, F0 ) );
|
|
73
|
+
F0.assign( mix( F0, iridF, material.iridescence ) );
|
|
77
74
|
|
|
78
75
|
} );
|
|
79
76
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
77
|
+
// Dielectric on the exact Fresnel curve, metal and iridescence on Schlick.
|
|
78
|
+
const Fd = mix( bf.f0, vec3( bf.f90 ), dielectricFresnelWeight( dots.VoH, bf.eta ) );
|
|
79
|
+
const F = mix(
|
|
80
|
+
mix( Fd, fresnelSchlick( dots.VoH, bf.F0m ), material.metalness ),
|
|
81
|
+
fresnelSchlick( dots.VoH, iridF ),
|
|
82
|
+
material.iridescence,
|
|
83
|
+
).toVar();
|
|
82
84
|
|
|
83
85
|
// Single-scatter specular BRDF (anisotropic when material.anisotropy > 0; the aniso
|
|
84
86
|
// visibility term already carries the 1/(4·NoV·NoL) denominator)
|
|
@@ -98,9 +100,11 @@ export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
|
|
|
98
100
|
|
|
99
101
|
} );
|
|
100
102
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
// Compensation factor and total directional albedo from the same table fetch.
|
|
104
|
+
const dfg = DFGResult.wrap( evaluateSpecularDFG(
|
|
105
|
+
bf.f0, bf.f90, bf.eta, bf.F0m, material.metalness, iridF, material.iridescence, F0,
|
|
106
|
+
dots.NoV, material.roughness,
|
|
107
|
+
) );
|
|
104
108
|
const specular = specularSS.mul( dfg.compensation );
|
|
105
109
|
|
|
106
110
|
// Diffuse energy budget from hemisphere-integrated specular albedo (includes multiscatter)
|
|
@@ -140,15 +144,18 @@ export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
|
|
|
140
144
|
If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
141
145
|
|
|
142
146
|
const ccRoughness = max( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS );
|
|
143
|
-
const ccF0 = vec3( 0.04 );
|
|
147
|
+
const ccF0 = vec3( 0.04 ); // IOR 1.5
|
|
144
148
|
|
|
145
149
|
// A GGX lobe like any other, so same multiscatter treatment; its albedo is what the
|
|
146
150
|
// base underneath loses.
|
|
147
|
-
const ccDfg = DFGResult.wrap(
|
|
151
|
+
const ccDfg = DFGResult.wrap( evaluateSpecularDFG(
|
|
152
|
+
ccF0, float( 1.0 ), float( 1.5 ), vec3( 0.0 ), float( 0.0 ), vec3( 0.0 ), float( 0.0 ), ccF0,
|
|
153
|
+
dots.NoV, ccRoughness,
|
|
154
|
+
) );
|
|
148
155
|
|
|
149
156
|
const ccD = DistributionGGX( dots.NoH, ccRoughness );
|
|
150
157
|
const ccVis = VisibilityGGXSmithCorrelated( dots.NoV, dots.NoL, ccRoughness );
|
|
151
|
-
const ccF =
|
|
158
|
+
const ccF = fresnelDielectric( dots.VoH, float( 1.5 ) );
|
|
152
159
|
const ccLobe = vec3( ccD.mul( ccVis ).mul( ccF ) ).mul( ccDfg.compensation );
|
|
153
160
|
|
|
154
161
|
|