rayzee 8.4.1 → 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 +61 -9
- 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 +27278 -9133
- 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 +84 -4
- package/src/EngineIssues.js +3 -1
- package/src/Passes/AIUpscaler.js +16 -7
- package/src/PathTracerApp.js +318 -35
- package/src/Processor/AssetLoader.js +47 -55
- package/src/Processor/CameraOptimizer.js +22 -6
- package/src/Processor/EmissiveTriangleBuilder.js +34 -2
- package/src/Processor/GeometryExtractor.js +151 -138
- package/src/Processor/LightSerializer.js +17 -20
- 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 +10 -1
- package/src/Stages/Compositor.js +11 -2
- package/src/Stages/PathTracer.js +21 -4
- package/src/Stages/PathTracerStage.js +11 -18
- 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 -2
- package/src/managers/DenoisingManager.js +3 -7
- package/src/managers/EnvironmentManager.js +20 -0
- package/src/managers/LightManager.js +17 -15
- package/src/managers/MaterialDataManager.js +118 -208
- package/src/managers/UniformManager.js +1 -0
- package/src/managers/VideoRenderManager.js +9 -9
- package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
- package/src/LightUnits.js +0 -122
package/src/RenderSettings.js
CHANGED
|
@@ -42,6 +42,7 @@ const SETTING_ROUTES = {
|
|
|
42
42
|
anamorphicRatio: { uniform: 'anamorphicRatio', reset: true },
|
|
43
43
|
samplingTechnique: { uniform: 'samplingTechnique', reset: true },
|
|
44
44
|
fireflyThreshold: { uniform: 'fireflyThreshold', reset: true },
|
|
45
|
+
shadowTerminatorOffset: { uniform: 'shadowTerminatorOffset', reset: true },
|
|
45
46
|
enableAlphaShadows: { uniform: 'enableAlphaShadows', reset: true },
|
|
46
47
|
// Adaptive sampling — whole-frame early-stop (useAdaptiveSampling) + per-pixel freeze (usePixelFreeze)
|
|
47
48
|
useAdaptiveSampling: { uniform: 'useAdaptiveSampling', reset: true },
|
|
@@ -63,6 +64,7 @@ const SETTING_ROUTES = {
|
|
|
63
64
|
panoramaLonRange: { handler: 'handlePanoramaLonRange', reset: true },
|
|
64
65
|
panoramaLatRange: { handler: 'handlePanoramaLatRange', reset: true },
|
|
65
66
|
interactionModeEnabled: { handler: 'handleInteractionModeEnabled', reset: false },
|
|
67
|
+
interactionRenderScale: { handler: 'handleInteractionRenderScale', reset: false },
|
|
66
68
|
maxSamples: { handler: 'handleMaxSamples', reset: false },
|
|
67
69
|
transparentBackground: { handler: 'handleTransparentBackground' },
|
|
68
70
|
backgroundColor: { handler: 'handleBackgroundColor', reset: true },
|
|
@@ -148,6 +150,7 @@ export class RenderSettings extends EventDispatcher {
|
|
|
148
150
|
* @param {Function} [params.reconcileCompletion] - Called when completion limits change
|
|
149
151
|
* @param {Object} [params.denoisingManager] - Needed to force ASVGF off under panorama
|
|
150
152
|
* @param {Object} [params.cameraManager] - Needed to force auto-focus manual under panorama
|
|
153
|
+
* @param {Function} [params.onInteractionRenderScale] - Applies a new moving-camera render scale
|
|
151
154
|
*/
|
|
152
155
|
bind( params ) {
|
|
153
156
|
|
|
@@ -162,7 +165,7 @@ export class RenderSettings extends EventDispatcher {
|
|
|
162
165
|
* Builds handler functions for multi-stage settings that can't
|
|
163
166
|
* be routed with a simple uniform forward.
|
|
164
167
|
*/
|
|
165
|
-
_buildHandlers( { stages, renderer, resetCallback, reconcileCompletion, denoisingManager, cameraManager } ) {
|
|
168
|
+
_buildHandlers( { stages, renderer, resetCallback, reconcileCompletion, denoisingManager, cameraManager, onInteractionRenderScale } ) {
|
|
166
169
|
|
|
167
170
|
// UniformManager copies into the existing node, so one scratch vector serves every write.
|
|
168
171
|
const panoScratch = new Vector2();
|
|
@@ -279,6 +282,12 @@ export class RenderSettings extends EventDispatcher {
|
|
|
279
282
|
|
|
280
283
|
},
|
|
281
284
|
|
|
285
|
+
handleInteractionRenderScale: ( value ) => {
|
|
286
|
+
|
|
287
|
+
onInteractionRenderScale?.( value );
|
|
288
|
+
|
|
289
|
+
},
|
|
290
|
+
|
|
282
291
|
};
|
|
283
292
|
|
|
284
293
|
}
|
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
|
@@ -853,10 +853,25 @@ export class PathTracer extends PathTracerStage {
|
|
|
853
853
|
// (row-major active list → under-coverage of the lower rows → GI band). Force full coverage
|
|
854
854
|
// until the readback re-measures at the new size; bump the generation so any readback already
|
|
855
855
|
// in flight (carrying the old-resolution counts) is discarded when it resolves.
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
this.
|
|
856
|
+
// The early exit only compares energy against a per-pixel threshold, so the curve rescaled to the
|
|
857
|
+
// new pixel count keeps it working — nothing re-measures while the camera moves, and the moving-camera
|
|
858
|
+
// resolution drop would otherwise run every bounce slot. A multi-chunk curve holds only the last band.
|
|
859
|
+
if ( this._lastBounceCounts && this._numChunks <= 1 && oldW > 0 && oldH > 0 ) {
|
|
860
|
+
|
|
861
|
+
const ratio = ( newW * newH ) / ( oldW * oldH );
|
|
862
|
+
const rescale = ( v ) => Math.min( 0xFFFFFFFF, Math.round( v * ratio ) );
|
|
863
|
+
this._lastBounceCounts = this._lastBounceCounts.map( rescale );
|
|
864
|
+
this._lastBounceEnergy = this._lastBounceEnergy.map( rescale );
|
|
865
|
+
|
|
866
|
+
} else {
|
|
867
|
+
|
|
868
|
+
this._lastBounceCounts = null;
|
|
869
|
+
this._lastBounceEnergy = null;
|
|
870
|
+
this._lastBounceCountsBudget = - 1;
|
|
871
|
+
this._lastBounceCountsLoopBound = - 1;
|
|
872
|
+
|
|
873
|
+
}
|
|
874
|
+
|
|
860
875
|
this._curveSizingValid = false;
|
|
861
876
|
this._readbackFrameCounter = 0;
|
|
862
877
|
this._readbackGeneration ++;
|
|
@@ -1378,6 +1393,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1378
1393
|
activeIndicesRO: qm.getActiveReadRO(),
|
|
1379
1394
|
counters,
|
|
1380
1395
|
maxRayCount: this._wfMaxRayCount,
|
|
1396
|
+
shadowTerminatorOffset: this.shadowTerminatorOffset,
|
|
1381
1397
|
} );
|
|
1382
1398
|
this._kernelManager.register( 'extend',
|
|
1383
1399
|
extFn().compute(
|
|
@@ -1476,6 +1492,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1476
1492
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1477
1493
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1478
1494
|
fireflyThreshold: this.fireflyThreshold,
|
|
1495
|
+
shadowTerminatorOffset: this.shadowTerminatorOffset,
|
|
1479
1496
|
// RNG axis only (keys STBN via frame & 63).
|
|
1480
1497
|
frame: this.seedFrame,
|
|
1481
1498
|
accumFrame: this.frame,
|
|
@@ -298,18 +298,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
298
298
|
const self = this;
|
|
299
299
|
const materialInterface = {
|
|
300
300
|
uniforms: {
|
|
301
|
-
maxBounceCount: {
|
|
302
|
-
get value() {
|
|
303
|
-
|
|
304
|
-
return self.maxBounces.value;
|
|
305
|
-
|
|
306
|
-
},
|
|
307
|
-
set value( v ) {
|
|
308
|
-
|
|
309
|
-
self.maxBounces.value = v;
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
301
|
enableAccumulation: {
|
|
314
302
|
get value() {
|
|
315
303
|
|
|
@@ -322,15 +310,15 @@ export class PathTracerStage extends RenderStage {
|
|
|
322
310
|
|
|
323
311
|
}
|
|
324
312
|
},
|
|
325
|
-
|
|
313
|
+
fireflyThreshold: {
|
|
326
314
|
get value() {
|
|
327
315
|
|
|
328
|
-
return self.
|
|
316
|
+
return self.fireflyThreshold.value;
|
|
329
317
|
|
|
330
318
|
},
|
|
331
319
|
set value( v ) {
|
|
332
320
|
|
|
333
|
-
self.
|
|
321
|
+
self.fireflyThreshold.value = v;
|
|
334
322
|
|
|
335
323
|
}
|
|
336
324
|
},
|
|
@@ -351,11 +339,16 @@ export class PathTracerStage extends RenderStage {
|
|
|
351
339
|
|
|
352
340
|
this.cameraOptimizer = new CameraOptimizer( this.renderer, materialInterface, {
|
|
353
341
|
enabled: DEFAULT_STATE.interactionModeEnabled,
|
|
342
|
+
// Nothing that costs light: the app lowers the resolution instead (PathTracerApp._applyRenderScale).
|
|
343
|
+
// The firefly limit grows with sqrt(frame + 1), and every moving frame is frame 0, where it clipped
|
|
344
|
+
// ~10% of an emissive-lit interior's light. ×8 is the limit a still image reaches at 64 samples:
|
|
345
|
+
// lowest large-area error of the values measured, and half the extra grain of turning it off.
|
|
354
346
|
qualitySettings: {
|
|
355
|
-
maxBounceCount: 1,
|
|
356
347
|
enableAccumulation: false,
|
|
357
|
-
|
|
348
|
+
fireflyThreshold: ( threshold ) => threshold * 8,
|
|
358
349
|
},
|
|
350
|
+
onEnter: () => this.emit( 'pathtracer:interactionStart' ),
|
|
351
|
+
onExit: () => this.emit( 'pathtracer:interactionEnd' ),
|
|
359
352
|
onReset: () => {
|
|
360
353
|
|
|
361
354
|
this.reset();
|
|
@@ -487,7 +480,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
487
480
|
this.setTriangleData( this.sdfs.triangleData, this.sdfs.triangleCount );
|
|
488
481
|
this.setBVHData( this.sdfs.bvhData );
|
|
489
482
|
this.setInstanceTable( this.sdfs.instanceTable );
|
|
490
|
-
this.materialData.setMaterialData( this.sdfs.materialData );
|
|
483
|
+
this.materialData.setMaterialData( this.sdfs.materialData, this.sdfs.materials?.map( m => m.sources ) );
|
|
491
484
|
|
|
492
485
|
// Material texture arrays
|
|
493
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
|
|