Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
18f6fc7
volumetric fog - wip
sunag Feb 15, 2025
98d861f
performance-test
sunag Feb 15, 2025
1137062
revision
sunag Feb 15, 2025
6d8a66b
Update webgpu_volume_cloud4.html
sunag Feb 15, 2025
1294615
revision shadow
sunag Feb 15, 2025
8216245
revisions
sunag Feb 15, 2025
70228ce
rename
sunag Feb 15, 2025
69a4160
volumetric lighting
sunag Feb 15, 2025
6e2c7c8
rename to live test
sunag Feb 15, 2025
d2112fe
improve names
sunag Feb 15, 2025
cb874ff
update shadow intensity
sunag Feb 15, 2025
d5de760
rev
sunag Feb 15, 2025
4ecc60a
update title
sunag Feb 15, 2025
7485f0d
update description
sunag Feb 15, 2025
b8c0c85
description
sunag Feb 15, 2025
ddcd8af
rev
sunag Feb 15, 2025
8b0b405
improve example parameters
sunag Feb 16, 2025
5b60a9f
update parameters
sunag Feb 16, 2025
7ee75c2
add smoke amount
sunag Feb 16, 2025
1abde04
update example
sunag Feb 16, 2025
4d5b331
fix name
sunag Feb 16, 2025
f0c80c7
add some description
sunag Feb 16, 2025
79895d4
update parameters
sunag Feb 16, 2025
9284e22
update screenshot
sunag Feb 17, 2025
6c55f24
improve ray marching approach and fixes TODO
sunag Feb 17, 2025
71b7a08
TSL: add `modelRadius` and `objectRadius()`
sunag Feb 17, 2025
238b38c
revisions
sunag Feb 17, 2025
cfa6fe1
add some docs
sunag Feb 17, 2025
8b05205
cleanup
sunag Feb 17, 2025
2487308
cleanup
sunag Feb 17, 2025
688f7f3
fix diameter
sunag Feb 17, 2025
76b75f6
revisions and `webgpu_volume_lighting_rectarea`
sunag Feb 17, 2025
f1f5b64
Update MeshSSSNodeMaterial.js
sunag Feb 17, 2025
898d256
revision
sunag Feb 17, 2025
5200017
improve orbit controls limits
sunag Feb 17, 2025
5fe6f8d
cleanup
sunag Feb 17, 2025
7d758c5
Update webgpu_volume_lighting_rectarea.html
sunag Feb 17, 2025
8eb51df
Update TiledLightsNode.js
sunag Feb 19, 2025
fd18047
Update WGSLNodeBuilder.js
sunag Feb 19, 2025
2763f43
Merge branch 'dev' into dev-volumetric-fog
sunag Feb 19, 2025
8b0a65f
update examples
sunag Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/jsm/tsl/lighting/TiledLightsNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,12 @@ class TiledLightsNode extends LightsNode {

const { color, decay, viewPosition, distance } = this.getLightData( lightIndex.sub( 1 ) );

directPointLight( {
builder.lightsNode.setupDirectLight( builder, directPointLight( {
color,
lightViewPosition: viewPosition,
cutoffDistance: distance,
decayExponent: decay
} ).append();
decayExponent: decay,
lightViewPosition: viewPosition
} ) );

} );

Expand Down
18 changes: 18 additions & 0 deletions examples/jsm/tsl/math/Bayer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

321 changes: 321 additions & 0 deletions examples/webgpu_volume_lighting.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - volumetric cloud</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - volumetric lighting
<br>Improve the quality/performance adjusting the parameters in the Controls
</div>

<video id="video" loop muted crossOrigin="anonymous" playsinline style="display:none">
<source src="textures/sintel.ogv" type='video/ogg; codecs="theora, vorbis"'>
<source src="textures/sintel.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'>
</video>

<script type="importmap">
{
"imports": {
"three": "../src/Three.WebGPU.js",
"three/webgpu": "../src/Three.WebGPU.js",
"three/tsl": "../src/Three.TSL.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { int, vec3, Fn, time, texture3D, screenUV, uniform, frameId, screenCoordinate, linearDepth, pass } from 'three/tsl';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';

import { bayer16 } from 'three/addons/tsl/math/Bayer.js';
import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';

import Stats from 'three/addons/libs/stats.module.js';

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

let renderer, scene, camera;
let volumetricMesh, teapot, pointLight, spotLight;
let postProcessing;
let stats;

init();

function createTexture3D() {

let i = 0;

const size = 128;
const data = new Uint8Array( size * size * size );

const scale = 10;
const perlin = new ImprovedNoise();

const repeatFactor = 5.0;

for ( let z = 0; z < size; z ++ ) {

for ( let y = 0; y < size; y ++ ) {

for ( let x = 0; x < size; x ++ ) {

const nx = ( x / size ) * repeatFactor;
const ny = ( y / size ) * repeatFactor;
const nz = ( z / size ) * repeatFactor;

const noiseValue = perlin.noise( nx * scale, ny * scale, nz * scale );

data[ i ] = ( 128 + 128 * noiseValue );

i ++;

}

}

}

const texture = new THREE.Data3DTexture( data, size, size, size );
texture.format = THREE.RedFormat;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.unpackAlignment = 1;
texture.needsUpdate = true;

return texture;

}

function init() {

const LAYER_VOLUMETRIC_LIGHTING = 10;

stats = new Stats();
document.body.appendChild( stats.dom );

renderer = new THREE.WebGPURenderer( { forceWebGL: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.toneMapping = THREE.NeutralToneMapping;
renderer.toneMappingExposure = 2;
renderer.shadowMap.enabled = true;
document.body.appendChild( renderer.domElement );

scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, 20, 50 );

camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 50 );
camera.position.set( 6, 1, - 6 );

const controls = new OrbitControls( camera, renderer.domElement );
controls.maxDistance = 40;
controls.minDistance = 2;

// Voluemtric Fog

const noiseTexture3D = createTexture3D();

const volumetricMaterial = new THREE.VolumeNodeMaterial();
//volumetricMaterial.depthNode = viewportLinearDepth; // Use this if the idea is to render the volumetric lighting in the same scene pass
volumetricMaterial.offsetNode = bayer16( screenCoordinate.add( frameId.modInt( int( 2 ) ) ) );
volumetricMaterial.scatteringNode = Fn( ( { positionRay } ) => {

const timeScaled = vec3( time, 0, time.mul( .3 ) );

const sampleGrain = ( scale, timeScale = 1 ) => texture3D( noiseTexture3D, positionRay.add( timeScaled.mul( timeScale ) ).mul( scale ).mod( 1 ), 0 ).r.add( .5 );

let density = sampleGrain( .1 );
density = density.mul( sampleGrain( .05, 1 ) );
density = density.mul( sampleGrain( .02, 2 ) );

return density;

} );

volumetricMesh = new THREE.Mesh( new THREE.BoxGeometry( 20, 10, 20 ), volumetricMaterial );
volumetricMesh.receiveShadow = true;
volumetricMesh.position.y = 2;
volumetricMesh.layers.disableAll();
volumetricMesh.layers.enable( LAYER_VOLUMETRIC_LIGHTING );
scene.add( volumetricMesh );

// Objects

teapot = new THREE.Mesh( new TeapotGeometry( .8, 18 ), new THREE.MeshStandardMaterial( { color: 0xffffff, side: THREE.DoubleSide } ) );
teapot.castShadow = true;
scene.add( teapot );

const floor = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100 ), new THREE.MeshStandardMaterial( { color: 0xffffff } ) );
floor.rotation.x = - Math.PI / 2;
floor.position.y = - 3;
floor.receiveShadow = true;
scene.add( floor );

// Lights

pointLight = new THREE.PointLight( 0xf9bb50, 3, 100 );
pointLight.castShadow = true;
pointLight.position.set( 0, 1.4, 0 );
pointLight.layers.enable( LAYER_VOLUMETRIC_LIGHTING );
//lightBase.add( new THREE.Mesh( new THREE.SphereGeometry( 0.1, 16, 16 ), new THREE.MeshBasicMaterial( { color: 0xf9bb50 } ) ) );
scene.add( pointLight );

spotLight = new THREE.SpotLight( 0xffffff, 100 );
spotLight.position.set( 2.5, 5, 2.5 );
spotLight.angle = Math.PI / 6;
spotLight.penumbra = 1;
spotLight.decay = 2;
spotLight.distance = 0;
spotLight.map = new THREE.TextureLoader().setPath( 'textures/' ).load( 'colors.png' );
spotLight.castShadow = true;
spotLight.shadow.intensity = .98;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 15;
spotLight.shadow.focus = 1;
spotLight.shadow.bias = - .003;
spotLight.layers.enable( LAYER_VOLUMETRIC_LIGHTING );
//sunLight.add( new THREE.Mesh( new THREE.SphereGeometry( 0.1, 16, 16 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) ) );
scene.add( spotLight );

// Post-Proccessing

postProcessing = new THREE.PostProcessing( renderer );

//

const volumetricLightingIntensity = uniform( 1 );

const volumetricLayer = new THREE.Layers();
volumetricLayer.disableAll();
volumetricLayer.enable( LAYER_VOLUMETRIC_LIGHTING );

//

const scenePass = pass( scene, camera );
const sceneLinearDepth = scenePass.getTextureNode( 'depth' );

// Oclussion depth of volumetric lighting based on the scene depth

volumetricMaterial.depthNode = linearDepth( sceneLinearDepth.sample( screenUV ) );

//

const volumetricPass = pass( scene, camera, { depthBuffer: false } );
volumetricPass.setLayers( volumetricLayer );
volumetricPass.setResolution( .2 );

const denoiseStrength = uniform( .45 );

const blurredVolumetricPass = gaussianBlur( volumetricPass, denoiseStrength );

const scenePassColor = scenePass.add( blurredVolumetricPass.mul( volumetricLightingIntensity ) );

postProcessing.outputNode = scenePassColor;

//

const params = {
quality: 1,
distance: 1,
resolution: volumetricPass.getResolution(),
denoise: true
};

function update() {

const { quality, distance } = params;

const minSteps = 2;
const maxSteps = 12;
const minStepSize = 1;
const maxStepSize = 10;

volumetricMaterial.steps = minSteps + ( Math.floor( quality * ( maxSteps - minSteps ) ) );
volumetricMaterial.stepSize = minStepSize + ( maxStepSize - ( quality * ( maxStepSize - minStepSize ) ) );

volumetricMaterial.stepSize *= distance;

}

update();

const gui = new GUI();

const rayMarching = gui.addFolder( 'Ray Marching' ).close();
rayMarching.add( params, 'resolution', .1, .9 ).onChange( ( resolution ) => {

volumetricPass.setResolution( resolution );

} );
rayMarching.add( params, 'quality', 0, 1 ).name( 'fog quality' ).onChange( update );
rayMarching.add( params, 'distance', 0, 2 ).name( 'ray distance' ).onChange( update );
rayMarching.add( denoiseStrength, 'value', 0, 2 ).name( 'denoise strength' );
rayMarching.add( params, 'denoise' ).onChange( ( denoise ) => {

const volumetric = denoise ? blurredVolumetricPass : volumetricPass;

const scenePassColor = scenePass.add( volumetric.mul( volumetricLightingIntensity ) );

postProcessing.outputNode = scenePassColor;
postProcessing.needsUpdate = true;

} );

const lighting = gui.addFolder( 'Lighting' ).close();
lighting.add( pointLight, 'intensity', 0, 6 ).name( 'light intensity' );
lighting.add( spotLight, 'intensity', 0, 200 ).name( 'spot intensity' );
lighting.add( volumetricLightingIntensity, 'value', 0, 2 ).name( 'fog intensity' );

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

stats.update();

const time = performance.now() * 0.001;
const scale = 2.4;

pointLight.position.x = Math.sin( time * 0.7 ) * scale;
pointLight.position.y = Math.cos( time * 0.5 ) * scale;
pointLight.position.z = Math.cos( time * 0.3 ) * scale;

spotLight.position.x = Math.cos( time * 0.3 ) * scale;
spotLight.lookAt( 0, 0, 0 );

teapot.rotation.y = time * 0.2;

postProcessing.render();

}

</script>

</body>
</html>
2 changes: 2 additions & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ export const range = TSL.range;
export const rangeFog = TSL.rangeFog;
export const rangeFogFactor = TSL.rangeFogFactor;
export const reciprocal = TSL.reciprocal;
export const lightProjectionUV = TSL.lightProjectionUV;
export const reference = TSL.reference;
export const referenceBuffer = TSL.referenceBuffer;
export const reflect = TSL.reflect;
Expand Down Expand Up @@ -416,6 +417,7 @@ export const select = TSL.select;
export const setCurrentStack = TSL.setCurrentStack;
export const shaderStages = TSL.shaderStages;
export const shadow = TSL.shadow;
export const pointShadow = TSL.pointShadow;
export const shadowPositionWorld = TSL.shadowPositionWorld;
export const sharedUniformGroup = TSL.sharedUniformGroup;
export const shapeCircle = TSL.shapeCircle;
Expand Down
2 changes: 1 addition & 1 deletion src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ class NodeMaterial extends Material {

if ( lightsNode && lightsNode.getScope().hasLights ) {

const lightingModel = this.setupLightingModel( builder );
const lightingModel = this.setupLightingModel( builder ) || null;

outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode );

Expand Down
Loading
Loading