rayzee 9.4.2 → 9.5.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 +223 -21
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44079
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +22 -8
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +85 -33
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
package/src/Storage/CDFCache.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ENGINE_AREAS } from './
|
|
1
|
+
import { ENGINE_AREAS } from './areas.js';
|
|
2
2
|
import { sharedStorage } from './shared.js';
|
|
3
3
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
4
4
|
|
|
5
|
-
const FORMAT =
|
|
5
|
+
const FORMAT = 5;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Where an environment texture came from, recorded by the loader so its sampling tables can be
|
|
@@ -25,7 +25,10 @@ function keyFor( texture ) {
|
|
|
25
25
|
|
|
26
26
|
const area = () => sharedStorage( getAssetConfig().cacheNamespace )?.area( ENGINE_AREAS.CDF ) ?? null;
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const ARRAYS = [ 'exactConditional', 'exactMarginal', 'exactRowGuide', 'exactMarginalGuide' ];
|
|
29
|
+
const FILES = ARRAYS.map( name => `${name}.f32` );
|
|
30
|
+
|
|
31
|
+
/** @returns {Promise<?Object>} the fields of an EquirectHDRInfo build */
|
|
29
32
|
export async function loadCDF( texture ) {
|
|
30
33
|
|
|
31
34
|
const key = keyFor( texture );
|
|
@@ -34,14 +37,11 @@ export async function loadCDF( texture ) {
|
|
|
34
37
|
|
|
35
38
|
try {
|
|
36
39
|
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
conditionalData: new Float32Array( await conditional.arrayBuffer() ),
|
|
43
|
-
totalSum, compensationDelta, width, height,
|
|
44
|
-
};
|
|
40
|
+
const files = await Promise.all( FILES.map( name => entry.file( name ) ) );
|
|
41
|
+
if ( files.some( f => ! f ) ) return null;
|
|
42
|
+
const arrays = await Promise.all( files.map( async f => new Float32Array( await f.arrayBuffer() ) ) );
|
|
43
|
+
const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = entry.extra;
|
|
44
|
+
return { ...Object.fromEntries( ARRAYS.map( ( name, i ) => [ name, arrays[ i ] ] ) ), totalSum, width, height, exactWidth, exactHeight, radianceIntegral };
|
|
45
45
|
|
|
46
46
|
} finally {
|
|
47
47
|
|
|
@@ -51,20 +51,21 @@ export async function loadCDF( texture ) {
|
|
|
51
51
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export async function saveCDF( texture,
|
|
54
|
+
export async function saveCDF( texture, info ) {
|
|
55
55
|
|
|
56
|
+
const { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } = info;
|
|
57
|
+
const arrays = ARRAYS.map( name => info[ name ] );
|
|
56
58
|
const key = keyFor( texture );
|
|
57
59
|
const target = key ? area() : null;
|
|
58
|
-
if ( ! target ||
|
|
60
|
+
if ( ! target || arrays.some( a => ! a ) ) return false;
|
|
59
61
|
|
|
60
|
-
const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes:
|
|
62
|
+
const writer = await target.create( key, { label: texture.name || 'environment', expectedBytes: arrays.reduce( ( n, a ) => n + a.byteLength, 0 ) } );
|
|
61
63
|
if ( ! writer ) return false;
|
|
62
64
|
|
|
63
65
|
try {
|
|
64
66
|
|
|
65
|
-
await writer.write(
|
|
66
|
-
await writer.
|
|
67
|
-
await writer.commit( { totalSum, compensationDelta, width, height } );
|
|
67
|
+
for ( let i = 0; i < FILES.length; i ++ ) await writer.write( FILES[ i ], arrays[ i ] );
|
|
68
|
+
await writer.commit( { totalSum, width, height, exactWidth, exactHeight, radianceIntegral } );
|
|
68
69
|
return true;
|
|
69
70
|
|
|
70
71
|
} catch {
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
AnimationClip, BufferAttribute, BufferGeometry, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
|
|
13
|
-
MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera,
|
|
12
|
+
AnimationClip, BufferAttribute, BufferGeometry, Color, DataTexture, Group, InstancedBufferAttribute, InstancedMesh,
|
|
13
|
+
MaterialLoader, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, DirectionalLight, PointLight, SpotLight,
|
|
14
14
|
} from 'three';
|
|
15
15
|
|
|
16
16
|
export const SCENE_GRAPH_FORMAT = 2;
|
|
17
|
+
// Texture slots the engine reads off a material that three.js does not serialise.
|
|
18
|
+
const ENGINE_TEXTURES = [ 'diffuseTransmissionMap', 'diffuseTransmissionColorMap' ];
|
|
17
19
|
export const ARCHIVE_PATH = '__rayzeeArchivePath';
|
|
18
20
|
export const ARCHIVE_LOADER = '__rayzeeArchiveLoader';
|
|
19
21
|
|
|
@@ -28,6 +30,7 @@ const TEXTURE_PROPS = [
|
|
|
28
30
|
'flipY', 'generateMipmaps', 'premultiplyAlpha', 'unpackAlignment', 'colorSpace',
|
|
29
31
|
];
|
|
30
32
|
|
|
33
|
+
const LIGHT_PROPS = [ 'intensity', 'distance', 'decay', 'angle', 'penumbra' ];
|
|
31
34
|
const OBJECT_PROPS = [ 'name', 'visible', 'castShadow', 'receiveShadow', 'frustumCulled', 'renderOrder', 'matrixAutoUpdate' ];
|
|
32
35
|
|
|
33
36
|
export class SceneGraphUnsupported extends Error {
|
|
@@ -48,6 +51,9 @@ function kindOf( object ) {
|
|
|
48
51
|
if ( object.isMesh ) return 'Mesh';
|
|
49
52
|
if ( object.isPerspectiveCamera ) return 'PerspectiveCamera';
|
|
50
53
|
if ( object.isOrthographicCamera ) return 'OrthographicCamera';
|
|
54
|
+
if ( object.isDirectionalLight ) return 'DirectionalLight';
|
|
55
|
+
if ( object.isPointLight ) return 'PointLight';
|
|
56
|
+
if ( object.isSpotLight ) return 'SpotLight';
|
|
51
57
|
if ( object.isLight || object.isPoints || object.isLine || object.isSprite || object.isLOD ) return null;
|
|
52
58
|
if ( object.isGroup ) return 'Group';
|
|
53
59
|
return object.constructor === Object3D ? 'Object3D' : null;
|
|
@@ -163,6 +169,9 @@ function encodeMaterial( material, meta ) {
|
|
|
163
169
|
for ( const [ key, value ] of Object.entries( material ) ) if ( value?.isColor ) json.exactColors[ key ] = [ value.r, value.g, value.b ];
|
|
164
170
|
// MaterialLoader sets `reflectivity` after `ior`, and that setter rewrites ior with rounding.
|
|
165
171
|
if ( material.ior !== undefined ) json.exactIor = material.ior;
|
|
172
|
+
// Engine properties three.js does not know, so toJSON drops them.
|
|
173
|
+
if ( material.diffuseTransmission !== undefined ) json.diffuseTransmission = material.diffuseTransmission;
|
|
174
|
+
for ( const key of ENGINE_TEXTURES ) if ( material[ key ]?.isTexture ) ( json.engineTextures ??= {} )[ key ] = material[ key ].uuid;
|
|
166
175
|
return json;
|
|
167
176
|
|
|
168
177
|
}
|
|
@@ -234,6 +243,14 @@ export function encodeSceneGraph( root, { environment = null, animations = [], s
|
|
|
234
243
|
|
|
235
244
|
}
|
|
236
245
|
|
|
246
|
+
if ( object.isLight ) {
|
|
247
|
+
|
|
248
|
+
node.color = object.color.toArray();
|
|
249
|
+
for ( const prop of LIGHT_PROPS ) if ( object[ prop ] !== undefined ) node[ prop ] = object[ prop ];
|
|
250
|
+
if ( object.target ) node.target = object.target.uuid;
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
237
254
|
const index = nodes.push( node ) - 1;
|
|
238
255
|
for ( const child of object.children ) visit( child, index );
|
|
239
256
|
|
|
@@ -419,8 +436,16 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
|
|
|
419
436
|
for ( const json of manifest.materials ) {
|
|
420
437
|
|
|
421
438
|
const material = materialLoader.parse( json );
|
|
422
|
-
for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) )
|
|
439
|
+
for ( const [ key, rgb ] of Object.entries( json.exactColors ?? {} ) ) {
|
|
440
|
+
|
|
441
|
+
if ( key === 'diffuseTransmissionColor' ) material[ key ] = new Color();
|
|
442
|
+
material[ key ]?.setRGB( rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] );
|
|
443
|
+
|
|
444
|
+
}
|
|
445
|
+
|
|
423
446
|
if ( json.exactIor !== undefined ) material.ior = json.exactIor;
|
|
447
|
+
if ( json.diffuseTransmission !== undefined ) material.diffuseTransmission = json.diffuseTransmission;
|
|
448
|
+
for ( const [ key, uuid ] of Object.entries( json.engineTextures ?? {} ) ) if ( textures[ uuid ] ) material[ key ] = textures[ uuid ];
|
|
424
449
|
materials[ json.uuid ] = material;
|
|
425
450
|
|
|
426
451
|
}
|
|
@@ -462,6 +487,9 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
|
|
|
462
487
|
case 'PerspectiveCamera': object = new PerspectiveCamera(); break;
|
|
463
488
|
case 'OrthographicCamera': object = new OrthographicCamera(); break;
|
|
464
489
|
case 'Group': object = new Group(); break;
|
|
490
|
+
case 'DirectionalLight': object = new DirectionalLight(); break;
|
|
491
|
+
case 'PointLight': object = new PointLight(); break;
|
|
492
|
+
case 'SpotLight': object = new SpotLight(); break;
|
|
465
493
|
default: object = new Object3D();
|
|
466
494
|
|
|
467
495
|
}
|
|
@@ -487,11 +515,26 @@ export async function decodeSceneGraph( manifest, data, { loadTexture } ) {
|
|
|
487
515
|
|
|
488
516
|
}
|
|
489
517
|
|
|
518
|
+
if ( object.isLight ) {
|
|
519
|
+
|
|
520
|
+
object.color.fromArray( node.color );
|
|
521
|
+
for ( const prop of LIGHT_PROPS ) if ( node[ prop ] !== undefined ) object[ prop ] = node[ prop ];
|
|
522
|
+
|
|
523
|
+
}
|
|
524
|
+
|
|
490
525
|
built.push( object );
|
|
491
526
|
if ( node.parent >= 0 ) built[ node.parent ].add( object );
|
|
492
527
|
|
|
493
528
|
}
|
|
494
529
|
|
|
530
|
+
// A light aims at its target, which is stored as an object of its own.
|
|
531
|
+
const byUuid = new Map( built.map( ( object ) => [ object.uuid, object ] ) );
|
|
532
|
+
manifest.nodes.forEach( ( node, i ) => {
|
|
533
|
+
|
|
534
|
+
if ( node.target && byUuid.has( node.target ) ) built[ i ].target = byUuid.get( node.target );
|
|
535
|
+
|
|
536
|
+
} );
|
|
537
|
+
|
|
495
538
|
return {
|
|
496
539
|
root: built[ 0 ],
|
|
497
540
|
environment: manifest.environment ? textures[ manifest.environment ] : null,
|
|
@@ -3,11 +3,9 @@ import { acquireLock, heldLockNames } from './locks.js';
|
|
|
3
3
|
import { entryIdFor } from './identity.js';
|
|
4
4
|
import { Emitter } from './events.js';
|
|
5
5
|
import { InlineTransport } from './inlineTransport.js';
|
|
6
|
+
import { STORAGE_KIND, ENGINE_AREAS } from './areas.js';
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
/** Areas the engine itself writes; hosts define their own with `defineArea`. */
|
|
10
|
-
export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );
|
|
8
|
+
export { STORAGE_KIND, ENGINE_AREAS };
|
|
11
9
|
|
|
12
10
|
const META = 'meta.json';
|
|
13
11
|
const META_VERSION = 1;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const STORAGE_KIND = Object.freeze( { CACHE: 'cache', USER: 'user', SCRATCH: 'scratch' } );
|
|
2
|
+
|
|
3
|
+
/** Areas the engine itself writes; hosts define their own with `defineArea`. */
|
|
4
|
+
export const ENGINE_AREAS = Object.freeze( { DOWNLOADS: 'downloads', ARCHIVES: 'archives', SCENES: 'scenes', CDF: 'cdf', SPILL: 'spill' } );
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sceneResources } from './SceneResources.js';
|
|
1
2
|
import {
|
|
2
3
|
Fn,
|
|
3
4
|
wgslFn,
|
|
@@ -88,8 +89,10 @@ const toObjectDir = ( rows, d ) => vec3(
|
|
|
88
89
|
// at shared triangle edges that Möller-Trumbore exhibits under FP32. Per-ray shears
|
|
89
90
|
// are precomputed once via computeWoopFromInvDir; per-triangle test is FMA-friendly
|
|
90
91
|
// and uses sign-aware depth comparison so it works for any det orientation.
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
// guardT: reject t within its own rounding error. Closest-hit rays need it to leave a large triangle; shadow rays
|
|
93
|
+
// start lifted off their surface and skip it — its registers cost Shade's shadow traversal ~4 %.
|
|
94
|
+
const rayTriangle = ( name, guardT ) => wgslFn( `
|
|
95
|
+
fn ${ name }( rayOrigin: vec3f, rayDir: vec3f, pA: vec3f, pB: vec3f, pC: vec3f, closestHitDst: f32, woopParams: vec4f ) -> vec4f {
|
|
93
96
|
|
|
94
97
|
// Returns vec4(t, u, v, hit) where hit > 0.5 means intersection.
|
|
95
98
|
// woopParams: (Sx, Sy, Sz, bitcast<f32>(packed kx|ky<<2|kz<<4))
|
|
@@ -138,7 +141,19 @@ const RayTriangleGeometry = wgslFn( `
|
|
|
138
141
|
let tSigned = T * detSign;
|
|
139
142
|
let detAbs = abs( det );
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
${ guardT ? `
|
|
145
|
+
// t must clear its own rounding error (pbrt-v4 Triangle::Intersect, γn = n·2⁻²⁴ / (1 − n·2⁻²⁴)), or a
|
|
146
|
+
// ray leaving a large triangle can hit it again: its error grows with the distance to the vertices.
|
|
147
|
+
let maxZ = max( max( abs( Sz * Akz ), abs( Sz * Bkz ) ), abs( Sz * Ckz ) );
|
|
148
|
+
let maxX = max( max( abs( Ax ), abs( Bx ) ), abs( Cx ) );
|
|
149
|
+
let maxY = max( max( abs( Ay ), abs( By ) ), abs( Cy ) );
|
|
150
|
+
let maxE = max( max( abs( U ), abs( V ) ), abs( W ) );
|
|
151
|
+
let deltaX = 2.98023e-7f * ( maxX + maxZ );
|
|
152
|
+
let deltaY = 2.98023e-7f * ( maxY + maxZ );
|
|
153
|
+
let deltaE = 2.0f * ( 1.19209e-7f * maxX * maxY + deltaY * maxX + deltaX * maxY );
|
|
154
|
+
let errT = 3.0f * maxZ * ( 3.57628e-7f * maxE + deltaE );
|
|
155
|
+
` : '' }
|
|
156
|
+
if ( tSigned > ${ guardT ? 'errT' : '0.0f' } && tSigned < closestHitDst * detAbs ) {
|
|
142
157
|
|
|
143
158
|
// Match Möller-Trumbore convention: u = weight of B, v = weight of C.
|
|
144
159
|
// In Woop's edge functions, U → weight of A, V → weight of B, W → weight of C.
|
|
@@ -156,6 +171,9 @@ const RayTriangleGeometry = wgslFn( `
|
|
|
156
171
|
}
|
|
157
172
|
` );
|
|
158
173
|
|
|
174
|
+
const RayTriangleGeometry = rayTriangle( 'RayTriangleGeometry', true );
|
|
175
|
+
const RayTriangleGeometryShadow = rayTriangle( 'RayTriangleGeometryShadow', false );
|
|
176
|
+
|
|
159
177
|
// Compute Woop ray-space transform (Woop 2013, §3.1) — runs once per ray and
|
|
160
178
|
// amortizes across hundreds of triangle tests. Returns Sx/Sy/Sz shears plus the
|
|
161
179
|
// permuted axis indices packed via bitcast into the .w slot.
|
|
@@ -517,12 +535,15 @@ export const traverseBVHDebug = /*@__PURE__*/ makeTraverseBVH( true );
|
|
|
517
535
|
// SHADOW RAY TRAVERSAL (OPTIMIZED - early exit on any hit)
|
|
518
536
|
// ================================================================================
|
|
519
537
|
|
|
520
|
-
|
|
538
|
+
// cameraCulled: skip the faces a camera ray along −direction would cull, for a ray standing in for one.
|
|
539
|
+
const makeTraverseBVHShadow = ( cameraCulled ) => Fn( ( [
|
|
521
540
|
ray,
|
|
522
541
|
bvhBuffer,
|
|
523
542
|
triangleBuffer,
|
|
524
543
|
maxShadowDist,
|
|
525
|
-
] ) => {
|
|
544
|
+
], builder ) => {
|
|
545
|
+
|
|
546
|
+
const { alphaShadows } = sceneResources( builder );
|
|
526
547
|
|
|
527
548
|
const folded = bvhBuffer.value?.foldedLeaves === true;
|
|
528
549
|
const isLeafTag = leafTagTest( folded );
|
|
@@ -572,36 +593,50 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
572
593
|
const pB = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 1 ).xyz );
|
|
573
594
|
const pC = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 2 ).xyz );
|
|
574
595
|
|
|
575
|
-
const triResult =
|
|
596
|
+
const triResult = RayTriangleGeometryShadow( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
|
|
576
597
|
|
|
577
598
|
If( triResult.w.greaterThan( 0.5 ), () => {
|
|
578
599
|
|
|
579
600
|
// Per-mesh visibility is handled at the BLAS-pointer level.
|
|
580
601
|
const uvData2 = triangleRow( triangleBuffer, triIndex, 4 ).toVar();
|
|
581
602
|
|
|
582
|
-
|
|
583
|
-
closestHit.dst.assign( triResult.x );
|
|
584
|
-
closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
|
|
585
|
-
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
586
|
-
closestHit.instanceLeaf.assign( instLeaf );
|
|
603
|
+
const record = () => {
|
|
587
604
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
605
|
+
closestHit.didHit.assign( true );
|
|
606
|
+
closestHit.dst.assign( triResult.x );
|
|
607
|
+
closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
|
|
608
|
+
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
609
|
+
closestHit.instanceLeaf.assign( instLeaf );
|
|
593
610
|
|
|
594
|
-
|
|
595
|
-
|
|
611
|
+
// Hit point is cheap (origin + dir*t). Geometric normal is deferred
|
|
612
|
+
// to traceShadowRay — only the transmission branch needs it, so we
|
|
613
|
+
// skip the cross+normalize for the (much more common) opaque-blocker
|
|
614
|
+
// and alpha-cutout paths. Normal stays vec3(0) from struct init.
|
|
615
|
+
closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
|
|
596
616
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
If( shadowFlagsSettle( uvData2.z ), () => {
|
|
617
|
+
closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
|
|
618
|
+
closestHit.triangleIndex.assign( triIndex );
|
|
600
619
|
|
|
601
|
-
|
|
602
|
-
|
|
620
|
+
// An opaque blocker settles the ray. A surface light passes through has to be
|
|
621
|
+
// the nearest one, or the layers behind the first find are never counted.
|
|
622
|
+
If( shadowFlagsSettle( uvData2.z, alphaShadows ), () => {
|
|
603
623
|
|
|
604
|
-
|
|
624
|
+
blocked.assign( true );
|
|
625
|
+
Break();
|
|
626
|
+
|
|
627
|
+
} );
|
|
628
|
+
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
if ( cameraCulled ) {
|
|
632
|
+
|
|
633
|
+
const w = float( 1.0 ).sub( triResult.y ).sub( triResult.z );
|
|
634
|
+
const n = normalize( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 0 ).w ).mul( w )
|
|
635
|
+
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 1 ).w ).mul( triResult.y ) )
|
|
636
|
+
.add( unpackTriangleNormal( triangleRow( triangleBuffer, triIndex, 2 ).w ).mul( triResult.z ) ) );
|
|
637
|
+
If( sideAccepts( triangleSide( uvData2.z ), rayDirection.dot( n ).negate() ), record );
|
|
638
|
+
|
|
639
|
+
} else record();
|
|
605
640
|
|
|
606
641
|
} );
|
|
607
642
|
|
|
@@ -726,3 +761,6 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
726
761
|
return closestHit;
|
|
727
762
|
|
|
728
763
|
} );
|
|
764
|
+
|
|
765
|
+
export const traverseBVHShadow = /*@__PURE__*/ makeTraverseBVHShadow( false );
|
|
766
|
+
export const traverseBVHShadowCameraCulled = /*@__PURE__*/ makeTraverseBVHShadow( true );
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bidirectional path tracing — shared by the light pass, ConnectKernel and LightSplatKernel.
|
|
3
|
+
* MIS is Georgiev 2012's dVCM/dVC recursion; every density comes from calculateMaterialPDF, both ways
|
|
4
|
+
* round, so every strategy evaluates the same weights. With vertex merging (integrator 'vcm') a state also
|
|
5
|
+
* carries `vm`, η² at its vertex (η = πr² · light paths, r that vertex's merge radius): Georgiev's dVM there is
|
|
6
|
+
* dVC / η², so it is never stored.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
float, vec2, vec4, int, uint, If, max, select, abs, dot, length,
|
|
11
|
+
} from 'three/tsl';
|
|
12
|
+
|
|
13
|
+
import { getMaterial, MIN_ROUGHNESS, REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
|
|
14
|
+
import { RayTracingMaterial, MaterialSamples, ExtMapResult } from './Struct.js';
|
|
15
|
+
import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, getTransformedUV } from './TextureSampling.js';
|
|
16
|
+
import { SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
17
|
+
|
|
18
|
+
// 1D dimensions of a camera vertex's bounce block (Random.js budget) picking its connection.
|
|
19
|
+
export const DIM_CONNECT_PATH = 16;
|
|
20
|
+
export const DIM_CONNECT_VERTEX = 17;
|
|
21
|
+
// Emission: 1D triangle pick, 2D point, 2D direction, 1D side, 1D sun or emitter, 2D sun direction, 2D disc point.
|
|
22
|
+
export const DIM_EMIT = SAMPLER_DIM_AUX_BASE + 2048;
|
|
23
|
+
|
|
24
|
+
// Light paths draw at pixels far below the image, so their sequences never repeat a camera path's.
|
|
25
|
+
export const LIGHT_PIXEL_ROW_OFFSET = 65536;
|
|
26
|
+
|
|
27
|
+
// Light tracing adds into u32 per channel: value · SPLAT_SCALE, rounded stochastically.
|
|
28
|
+
export const SPLAT_SCALE = 16384;
|
|
29
|
+
export const SPLAT_MAX = 4096;
|
|
30
|
+
|
|
31
|
+
// Pending-connection tags set the high bit: a light path's data in that lane is a positive float's bits.
|
|
32
|
+
export const PASS_TAG_BIT = 0x80000000;
|
|
33
|
+
|
|
34
|
+
const MIS_MAX = 1e30;
|
|
35
|
+
|
|
36
|
+
const finite = ( x ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, MIS_MAX );
|
|
37
|
+
|
|
38
|
+
export const lightPathPixel = ( index, resolution ) => {
|
|
39
|
+
|
|
40
|
+
const w = uint( resolution.x );
|
|
41
|
+
return vec2( float( uint( index ).mod( w ) ).add( 0.5 ), float( uint( index ).div( w ).add( uint( LIGHT_PIXEL_ROW_OFFSET ) ) ).add( 0.5 ) );
|
|
42
|
+
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// The power heuristic (β = 2). The recursion stores every density ratio already raised to it.
|
|
46
|
+
export const mis = ( x ) => x.mul( x );
|
|
47
|
+
|
|
48
|
+
// MIS recursion: `state` holds two .toVar()s, { dVCM, dVC }, updated in place, and `vm` when merging.
|
|
49
|
+
|
|
50
|
+
/** Arriving at a vertex `dist` from the last one, at |cos| `cosIn` to its facet. */
|
|
51
|
+
export const misOnHit = ( state, dist, cosIn ) => {
|
|
52
|
+
|
|
53
|
+
const c = max( cosIn, 1e-6 );
|
|
54
|
+
state.dVCM.assign( finite( state.dVCM.mul( mis( dist.mul( dist ).div( c ) ) ) ) );
|
|
55
|
+
state.dVC.assign( finite( state.dVC.div( mis( c ) ) ) );
|
|
56
|
+
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Leaving through a delta lobe, a refraction or a subsurface boundary: nothing can connect here. */
|
|
60
|
+
export const misOnSpecular = ( state, cosOut ) => {
|
|
61
|
+
|
|
62
|
+
state.dVC.assign( finite( state.dVC.mul( mis( cosOut ) ) ) );
|
|
63
|
+
state.dVCM.assign( 0.0 );
|
|
64
|
+
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Leaving by a BSDF sample; densities are solid angle, forward the sampler's and reverse its partner's. */
|
|
68
|
+
export const misOnScatter = ( state, cosOut, pdfForward, pdfReverse ) => {
|
|
69
|
+
|
|
70
|
+
const p = max( pdfForward, 1e-12 );
|
|
71
|
+
const partial = state.dVC.mul( mis( pdfReverse ) ).add( state.dVCM );
|
|
72
|
+
state.dVC.assign( finite( mis( cosOut.div( p ) ).mul( finite( state.vm ? partial.add( state.vm ) : partial ) ) ) );
|
|
73
|
+
state.dVCM.assign( finite( mis( float( 1.0 ).div( p ) ) ) );
|
|
74
|
+
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Georgiev (40)-(46): `lead` is the other side's density of reaching this end, `reverse` this end's density back.
|
|
78
|
+
export const misPartial = ( lead, state, reverse ) => {
|
|
79
|
+
|
|
80
|
+
const partial = state.dVCM.add( state.dVC.mul( mis( reverse ) ) );
|
|
81
|
+
return finite( finite( mis( lead ) ).mul( finite( state.vm ? partial.add( state.vm ) : partial ) ) );
|
|
82
|
+
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Vertex merging's radius at p: a few pixels' footprint seen from the camera (`mergeConst` + `mergeSlope` · distance),
|
|
86
|
+
// at least `mergeMin`. A function of position alone, so both subpaths weigh a merge at any vertex alike.
|
|
87
|
+
export const mergeRadiusAt = ( bdpt, p ) =>
|
|
88
|
+
max( bdpt.mergeConst.add( bdpt.mergeSlope.mul( length( p.sub( bdpt.cameraPosition ) ) ) ), bdpt.mergeMin );
|
|
89
|
+
|
|
90
|
+
// η = πr² · light paths: a merge's density over a connection's, there.
|
|
91
|
+
export const mergeEta = ( bdpt, radius ) => radius.mul( radius ).mul( Math.PI ).mul( float( bdpt.lightPaths ) );
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* η² at p as the weights take it, a state's `vm` (Georgiev's vmFactor, per vertex): η scaled by `mergeTrust`. Any
|
|
95
|
+
* density the strategies agree on still sums their weights to one; trust below 1 hands light other strategies can
|
|
96
|
+
* also reach back to them, unblurred, and leaves light only a merge reaches (a caustic in a mirror) all to merging.
|
|
97
|
+
*/
|
|
98
|
+
export const mergeVmAt = ( bdpt, p ) => {
|
|
99
|
+
|
|
100
|
+
const eta = mergeEta( bdpt, mergeRadiusAt( bdpt, p ) ).mul( bdpt.mergeTrust );
|
|
101
|
+
return eta.mul( eta );
|
|
102
|
+
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Georgiev (38)-(39), a merge at a camera vertex: each side's sum, `density` the camera vertex's BSDF density
|
|
106
|
+
// (of the light's arrival direction for the light side, back along its own for the camera side); `vc` = 1 / η².
|
|
107
|
+
export const misMergePartial = ( state, density, vc ) => finite( finite( state.dVCM.add( state.dVC.mul( mis( density ) ) ) ).mul( vc ) );
|
|
108
|
+
|
|
109
|
+
export const misWeight = ( wLight, wCamera ) => float( 1.0 ).div( float( 1.0 ).add( finite( wLight ) ).add( finite( wCamera ) ) );
|
|
110
|
+
|
|
111
|
+
// Verification: a view keeps one strategy, MIS-weighted, or alone at full weight (+ STRATEGY_ALONE).
|
|
112
|
+
export const STRATEGY = { ALL: 0, HIT: 1, NEE: 2, CONNECT: 3, LIGHT_TRACE: 4, MERGE: 5 };
|
|
113
|
+
export const STRATEGY_ALONE = 8;
|
|
114
|
+
|
|
115
|
+
export const strategyWeight = ( view, strategy, weight, alone = float( 1.0 ) ) =>
|
|
116
|
+
select( view.equal( uint( STRATEGY.ALL ) ).or( view.equal( uint( strategy ) ) ), weight,
|
|
117
|
+
select( view.equal( uint( strategy + STRATEGY_ALONE ) ), alone, float( 0.0 ) ) );
|
|
118
|
+
|
|
119
|
+
// Chance a light path leaves this side of a triangle toward `cosToward` (cosine to the winding normal).
|
|
120
|
+
export const emitterSideProbability = ( side, cosToward ) => select( side.equal( int( 2 ) ), float( 0.5 ),
|
|
121
|
+
select( side.equal( int( 1 ) ), select( cosToward.lessThan( 0.0 ), float( 1.0 ), float( 0.0 ) ),
|
|
122
|
+
select( cosToward.greaterThan( 0.0 ), float( 1.0 ), float( 0.0 ) ) ) );
|
|
123
|
+
|
|
124
|
+
/** Area density of a light path's start on a triangle with this emission — power over total, per area. */
|
|
125
|
+
export const emitterAreaPdf = ( emission, totalPower, share = 1 ) =>
|
|
126
|
+
max( dot( emission, REC709_LUMINANCE_COEFFICIENTS ), 0.0 ).div( max( totalPower, 1e-10 ) ).mul( share );
|
|
127
|
+
|
|
128
|
+
// The sun as a light subpath start: a direction over its disc, then a point on the scene's bounding disc at `discPdf`
|
|
129
|
+
// (LightGuide.js guidedDiscPdf).
|
|
130
|
+
export const sunEmissionPdf = ( bdpt, sunParams, discPdf ) => bdpt.sunPick.mul( discPdf ).div( max( sunParams.y, 1e-30 ) );
|
|
131
|
+
|
|
132
|
+
// Where a light subpath starts, in its pick table's order (`sourceCdf`, a running sum); each lamp list follows
|
|
133
|
+
// at `sourceOffsets[ type ]`, by LIGHT_TYPE. A light path's origin code is its triangle, or −1 − its source.
|
|
134
|
+
export const SOURCE = { SUN: 0, EMITTERS: 1, ENVIRONMENT: 2, LAMPS: 3 };
|
|
135
|
+
|
|
136
|
+
export const sourcePick = ( bdpt, source ) => {
|
|
137
|
+
|
|
138
|
+
const s = int( source );
|
|
139
|
+
return bdpt.sourceCdf.element( s ).sub( select( s.greaterThan( int( 0 ) ), bdpt.sourceCdf.element( max( s.sub( int( 1 ) ), int( 0 ) ) ), float( 0.0 ) ) );
|
|
140
|
+
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const lampSource = ( bdpt, type, index ) => select( type.equal( int( 0 ) ), int( bdpt.sourceOffsets[ 0 ] ),
|
|
144
|
+
select( type.equal( int( 1 ) ), int( bdpt.sourceOffsets[ 1 ] ), select( type.equal( int( 2 ) ), int( bdpt.sourceOffsets[ 2 ] ), int( bdpt.sourceOffsets[ 3 ] ) ) ) ).add( index );
|
|
145
|
+
|
|
146
|
+
export const sourceLampType = ( bdpt, source ) => select( source.lessThan( int( bdpt.sourceOffsets[ 1 ] ) ), int( 0 ),
|
|
147
|
+
select( source.lessThan( int( bdpt.sourceOffsets[ 2 ] ) ), int( 1 ), select( source.lessThan( int( bdpt.sourceOffsets[ 3 ] ) ), int( 2 ), int( 3 ) ) ) );
|
|
148
|
+
|
|
149
|
+
export const sourceLampIndex = ( bdpt, source, type ) => source.sub( select( type.equal( int( 0 ) ), int( bdpt.sourceOffsets[ 0 ] ),
|
|
150
|
+
select( type.equal( int( 1 ) ), int( bdpt.sourceOffsets[ 1 ] ), select( type.equal( int( 2 ) ), int( bdpt.sourceOffsets[ 2 ] ), int( bdpt.sourceOffsets[ 3 ] ) ) ) ) );
|
|
151
|
+
|
|
152
|
+
// The material at a stored vertex, folded as ShadeKernel folds it; its shading normal is already stored.
|
|
153
|
+
export function resolveSurfaceMaterial( materialIndex, uv, N, materialBuffer ) {
|
|
154
|
+
|
|
155
|
+
const material = RayTracingMaterial.wrap( getMaterial( int( materialIndex ), materialBuffer ) ).toVar();
|
|
156
|
+
material.normalMapIndex.assign( int( - 1 ) );
|
|
157
|
+
material.bumpMapIndex.assign( int( - 1 ) );
|
|
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
|
+
|
|
162
|
+
const samples = MaterialSamples.wrap( sampleAllMaterialTextures( material, uv, N, vec4( 0.0 ) ) ).toVar();
|
|
163
|
+
const rawRough = samples.roughness.toVar();
|
|
164
|
+
|
|
165
|
+
material.color.assign( samples.albedo );
|
|
166
|
+
material.metalness.assign( samples.metalness.clamp( 0.0, 1.0 ) );
|
|
167
|
+
material.roughness.assign( samples.roughness.clamp( MIN_ROUGHNESS, 1.0 ) );
|
|
168
|
+
material.sheenRoughness.assign( material.sheenRoughness.clamp( 0.05, 1.0 ) );
|
|
169
|
+
|
|
170
|
+
const extUV = getTransformedUV( { uv, transform: material.albedoTransform } ).toVar();
|
|
171
|
+
|
|
172
|
+
If( material.anisotropyMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
173
|
+
|
|
174
|
+
const aniso = processAnisotropyMap( material, extUV ).toVar();
|
|
175
|
+
material.anisotropy.assign( aniso.x );
|
|
176
|
+
material.anisotropyRotation.assign( aniso.y );
|
|
177
|
+
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
const ext = ExtMapResult.wrap( applyExtensionMaps( material, extUV ) ).toVar();
|
|
181
|
+
material.transmission.assign( ext.transmission );
|
|
182
|
+
material.clearcoat.assign( ext.clearcoat );
|
|
183
|
+
material.clearcoatRoughness.assign( ext.clearcoatRoughness );
|
|
184
|
+
material.sheenColor.assign( ext.sheenColor );
|
|
185
|
+
material.sheenRoughness.assign( ext.sheenRoughness );
|
|
186
|
+
material.iridescence.assign( ext.iridescence );
|
|
187
|
+
material.iridescenceThicknessRange.assign( vec2( material.iridescenceThicknessRange.x, ext.iridescenceThickness ) );
|
|
188
|
+
material.specularIntensity.assign( ext.specularIntensity );
|
|
189
|
+
material.specularColor.assign( ext.specularColor );
|
|
190
|
+
|
|
191
|
+
If( rawRough.lessThan( MIN_ROUGHNESS ).and( material.anisotropy.equal( 0.0 ) ).and( material.clearcoat.equal( 0.0 ) )
|
|
192
|
+
.and( material.transmission.equal( 0.0 ) ).and( material.subsurface.equal( 0.0 ) ), () => {
|
|
193
|
+
|
|
194
|
+
material.roughness.assign( 0.0 );
|
|
195
|
+
|
|
196
|
+
} );
|
|
197
|
+
|
|
198
|
+
return material;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The light end's cosine with the shading-normal correction (Veach 5.3.2): |V·N| |d·Ng| / |V·Ng|.
|
|
203
|
+
export const lightEndCosine = ( V, N, facetN, toOther ) =>
|
|
204
|
+
abs( dot( V, N ) ).mul( abs( dot( toOther, facetN ) ) ).div( max( abs( dot( V, facetN ) ), 1e-4 ) );
|