Buckets:
| import LightingModel from '../core/LightingModel.js'; | |
| import { property } from '../core/PropertyNode.js'; | |
| import { float, If, uniform, vec3, vec4 } from '../tsl/TSLBase.js'; | |
| import { positionWorld } from '../accessors/Position.js'; | |
| import { cameraFar, cameraNear, cameraPosition, cameraViewMatrix } from '../accessors/Camera.js'; | |
| import { Loop } from '../utils/LoopNode.js'; | |
| import { linearDepth, viewZToPerspectiveDepth } from '../display/ViewportDepthNode.js'; | |
| import { modelRadius } from '../accessors/ModelNode.js'; | |
| import { LTC_Evaluate_Volume } from './BSDF/LTC.js'; | |
| const scatteringDensity = property( 'vec3' ); | |
| const linearDepthRay = property( 'vec3' ); | |
| const outgoingRayLight = property( 'vec3' ); | |
| /** | |
| * VolumetricLightingModel class extends the LightingModel to implement volumetric lighting effects. | |
| * This model calculates the scattering and transmittance of light through a volumetric medium. | |
| * It dynamically adjusts the direction of the ray based on the camera and object positions. | |
| * The model supports custom scattering and depth nodes to enhance the lighting effects. | |
| * | |
| * @augments LightingModel | |
| */ | |
| class VolumetricLightingModel extends LightingModel { | |
| constructor() { | |
| super(); | |
| } | |
| start( builder ) { | |
| const { material, context } = builder; | |
| const startPos = property( 'vec3' ); | |
| const endPos = property( 'vec3' ); | |
| // This approach dynamically changes the direction of the ray, | |
| // prioritizing the ray from the camera to the object if it is inside the mesh, and from the object to the camera if it is far away. | |
| If( cameraPosition.sub( positionWorld ).length().greaterThan( modelRadius.mul( 2 ) ), () => { | |
| startPos.assign( cameraPosition ); | |
| endPos.assign( positionWorld ); | |
| } ).Else( () => { | |
| startPos.assign( positionWorld ); | |
| endPos.assign( cameraPosition ); | |
| } ); | |
| // | |
| const viewVector = endPos.sub( startPos ); | |
| const steps = uniform( 'int' ).onRenderUpdate( ( { material } ) => material.steps ); | |
| const stepSize = viewVector.length().div( steps ).toVar(); | |
| const rayDir = viewVector.normalize().toVar(); // TODO: toVar() should be automatic here ( in loop ) | |
| const distTravelled = float( 0.0 ).toVar(); | |
| const transmittance = vec3( 1 ).toVar(); | |
| if ( material.offsetNode ) { | |
| // reduce banding | |
| distTravelled.addAssign( material.offsetNode.mul( stepSize ) ); | |
| } | |
| Loop( steps, () => { | |
| const positionRay = startPos.add( rayDir.mul( distTravelled ) ); | |
| const positionViewRay = cameraViewMatrix.mul( vec4( positionRay, 1 ) ).xyz; | |
| if ( material.depthNode !== null ) { | |
| linearDepthRay.assign( linearDepth( viewZToPerspectiveDepth( positionViewRay.z, cameraNear, cameraFar ) ) ); | |
| context.sceneDepthNode = linearDepth( material.depthNode ).toVar(); | |
| } | |
| context.positionWorld = positionRay; | |
| context.shadowPositionWorld = positionRay; | |
| context.positionView = positionViewRay; | |
| scatteringDensity.assign( 0 ); | |
| let scatteringNode; | |
| if ( material.scatteringNode ) { | |
| scatteringNode = material.scatteringNode( { | |
| positionRay | |
| } ); | |
| } | |
| super.start( builder ); | |
| if ( scatteringNode ) { | |
| scatteringDensity.mulAssign( scatteringNode ); | |
| } | |
| // beer's law | |
| const falloff = scatteringDensity.mul( .01 ).negate().mul( stepSize ).exp(); | |
| transmittance.mulAssign( falloff ); | |
| // move along the ray | |
| distTravelled.addAssign( stepSize ); | |
| } ); | |
| outgoingRayLight.addAssign( transmittance.saturate().oneMinus() ); | |
| } | |
| scatteringLight( lightColor, builder ) { | |
| const sceneDepthNode = builder.context.sceneDepthNode; | |
| if ( sceneDepthNode ) { | |
| If( sceneDepthNode.greaterThanEqual( linearDepthRay ), () => { | |
| scatteringDensity.addAssign( lightColor ); | |
| } ); | |
| } else { | |
| scatteringDensity.addAssign( lightColor ); | |
| } | |
| } | |
| direct( { lightNode, lightColor }, builder ) { | |
| // Ignore lights with infinite distance | |
| if ( lightNode.light.distance === undefined ) return; | |
| // TODO: We need a viewportOpaque*() ( output, depth ) to fit with modern rendering approaches | |
| const directLight = lightColor.xyz.toVar(); | |
| directLight.mulAssign( lightNode.shadowNode ); // it no should be necessary if used in the same render pass | |
| this.scatteringLight( directLight, builder ); | |
| } | |
| directRectArea( { lightColor, lightPosition, halfWidth, halfHeight }, builder ) { | |
| const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction | |
| const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); | |
| const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); | |
| const p3 = lightPosition.add( halfWidth ).add( halfHeight ); | |
| const P = builder.context.positionView; | |
| const directLight = lightColor.xyz.mul( LTC_Evaluate_Volume( { P, p0, p1, p2, p3 } ) ).pow( 1.5 ); | |
| this.scatteringLight( directLight, builder ); | |
| } | |
| finish( builder ) { | |
| builder.context.outgoingLight.assign( outgoingRayLight ); | |
| } | |
| } | |
| export default VolumetricLightingModel; | |
Xet Storage Details
- Size:
- 4.97 kB
- Xet hash:
- f82d49938f3409e78c83040dad288c37e6b99de975069a8b444fb3dba663b963
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.