Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: Custom fog example, TriNoise3D and fixes #27599

Merged
merged 11 commits into from Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Expand Up @@ -330,6 +330,7 @@
"webgpu_cubemap_adjustments",
"webgpu_cubemap_dynamic",
"webgpu_cubemap_mix",
"webgpu_custom_fog",
"webgpu_depth_texture",
"webgpu_equirectangular",
"webgpu_instance_mesh",
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/Nodes.js
Expand Up @@ -41,6 +41,7 @@ export { NodeUtils };
// math
export { default as MathNode, PI, PI2, EPSILON, INFINITY, radians, degrees, exp, exp2, log, log2, sqrt, inverseSqrt, floor, ceil, normalize, fract, sin, cos, tan, asin, acos, atan, abs, sign, length, lengthSq, negate, oneMinus, dFdx, dFdy, round, reciprocal, trunc, fwidth, bitcast, atan2, min, max, mod, step, reflect, distance, difference, dot, cross, pow, pow2, pow3, pow4, transformDirection, mix, clamp, saturate, refract, smoothstep, faceForward, cbrt } from './math/MathNode.js';
export { parabola, gain, pcurve, sinc } from './math/MathNode.js';
export { triNoise3D } from './math/TriNoise3D.js';

export { default as OperatorNode, add, sub, mul, div, remainder, equal, lessThan, greaterThan, lessThanEqual, greaterThanEqual, and, or, xor, bitAnd, bitOr, bitXor, shiftLeft, shiftRight } from './math/OperatorNode.js';
export { default as CondNode, cond } from './math/CondNode.js';
Expand Down
3 changes: 2 additions & 1 deletion examples/jsm/nodes/fog/FogNode.js
@@ -1,4 +1,5 @@
import Node, { addNodeClass } from '../core/Node.js';
import { mix } from '../math/MathNode.js';
import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';

class FogNode extends Node {
Expand All @@ -16,7 +17,7 @@ class FogNode extends Node {

mixAssign( outputNode ) {

return this.mix( outputNode, this.colorNode );
return mix( outputNode, this.colorNode, this );

}

Expand Down
71 changes: 71 additions & 0 deletions examples/jsm/nodes/math/TriNoise3D.js
@@ -0,0 +1,71 @@
// https://github.com/cabbibo/glsl-tri-noise-3d

import { loop } from '../utils/LoopNode.js';
import { float, vec3, tslFn } from '../shadernode/ShaderNode.js';

const tri = tslFn( ( [ x ] ) => {

return x.fract().sub( .5 ).abs();

} );

const tri3 = tslFn( ( [ p ] ) => {

return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) );

} );

const triNoise3D = tslFn( ( [ p_immutable, spd, time ] ) => {

const p = vec3( p_immutable ).toVar();
const z = float( 1.4 ).toVar();
const rz = float( 0.0 ).toVar();
const bp = vec3( p ).toVar();

loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => {

const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar();
p.addAssign( dg.add( time.mul( float( 0.1 ).mul( spd ) ) ) );
bp.mulAssign( 1.8 );
z.mulAssign( 1.5 );
p.mulAssign( 1.2 );

const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar();
rz.addAssign( t.div( z ) );
bp.addAssign( 0.14 );

} );

return rz;

} );

// layouts

tri.setLayout( {
name: 'tri',
type: 'float',
inputs: [
{ name: 'x', type: 'float' }
]
} );

tri3.setLayout( {
name: 'tri3',
type: 'vec3',
inputs: [
{ name: 'p', type: 'vec3' }
]
} );

triNoise3D.setLayout( {
name: 'triNoise3D',
type: 'float',
inputs: [
{ name: 'p', type: 'vec3' },
{ name: 'spd', type: 'float' },
{ name: 'time', type: 'float' }
]
} );

export { tri, tri3, triNoise3D };
6 changes: 3 additions & 3 deletions examples/jsm/nodes/utils/LoopNode.js
Expand Up @@ -144,15 +144,15 @@ class LoopNode extends Node {

if ( ! update ) {

if ( type === 'int' ) {
if ( type === 'int' || type === 'uint' ) {

if ( condition.includes( '<' ) ) update = '++';
else update = '--';

} else {

if ( condition.includes( '<' ) ) update = '+= 1';
else update = '-= 1';
if ( condition.includes( '<' ) ) update = '+= 1.';
else update = '-= 1.';

}

Expand Down
Binary file added examples/screenshots/webgpu_custom_fog.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
174 changes: 174 additions & 0 deletions examples/webgpu_custom_fog.html
@@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - custom fog</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 custom fog
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/",
"three/nodes": "./jsm/nodes/Nodes.js"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { color, fog, float, positionWorld, triNoise3D, positionView, normalWorld, timerLocal, MeshPhongNodeMaterial } from 'three/nodes';

import WebGPU from 'three/addons/capabilities/WebGPU.js';
import WebGL from 'three/addons/capabilities/WebGL.js';

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, scene, renderer;
let controls;

init();

function init() {

if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {

document.body.appendChild( WebGPU.getErrorMessage() );

throw new Error( 'No WebGPU or WebGL2 support' );

}

camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 600 );
camera.position.set( 30, 15, 30 );

scene = new THREE.Scene();

// custom fog

const skyColor = color( 0xf0f5f5 );
const groundColor = color( 0xd0dee7 );

const fogNoiseDistance = positionView.z.negate().smoothstep( 0, camera.far - 300 );

const distance = fogNoiseDistance.mul( 20 ).max( 4 );
const alpha = .98;
const groundFogArea = float( distance ).sub( positionWorld.y ).div( distance ).pow( 3 ).saturate().mul( alpha );

const timer = timerLocal( 1 );

const fogNoiseA = triNoise3D( positionWorld.mul( .005 ), 0.2, timer );
const fogNoiseB = triNoise3D( positionWorld.mul( .01 ), 0.2, timer.mul( 1.2 ) );

const fogNoise = fogNoiseA.add( fogNoiseB ).mul( groundColor );

// apply custom fog

scene.fogNode = fog( fogNoiseDistance.oneMinus().mix( groundColor, fogNoise ), groundFogArea );
scene.backgroundNode = normalWorld.y.max( 0 ).mix( groundColor, skyColor );

// builds

const buildWindows = positionWorld.y.mul( 10 ).floor().mod( 4 ).sign().mix( color( 0x000066 ).add( fogNoiseDistance ), color( 0xffffff ) );

const buildGeometry = new THREE.BoxGeometry( 1, 1, 1 );
const buildMaterial = new MeshPhongNodeMaterial( {
colorNode: buildWindows
} );

const buildMesh = new THREE.InstancedMesh( buildGeometry, buildMaterial, 4000 );
scene.add( buildMesh );

const dummy = new THREE.Object3D();
const center = new THREE.Vector3();

for ( let i = 0; i < buildMesh.count; i ++ ) {

const scaleY = Math.random() * 7 + .5;

dummy.position.x = Math.random() * 600 - 300;
dummy.position.z = Math.random() * 600 - 300;

const distance = Math.max( dummy.position.distanceTo( center ) * .012, 1 );

dummy.position.y = .5 * scaleY * distance;

dummy.scale.x = dummy.scale.z = Math.random() * 3 + .5;
dummy.scale.y = scaleY * distance;

dummy.updateMatrix();

buildMesh.setMatrixAt( i, dummy.matrix );

}

// lights

scene.add( new THREE.HemisphereLight( skyColor.value, groundColor.value, 0.5 ) );

// geometry

const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
const planeMaterial = new THREE.MeshPhongMaterial( {
color: 0x999999
} );

const ground = new THREE.Mesh( planeGeometry, planeMaterial );
ground.rotation.x = - Math.PI / 2;
ground.scale.multiplyScalar( 3 );
ground.castShadow = true;
ground.receiveShadow = true;
scene.add( ground );

// renderer

renderer = new WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );

// controls

controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 2, 0 );
controls.minDistance = 7;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI / 2;
controls.autoRotate = true;
controls.autoRotateSpeed = .1;
controls.update();

window.addEventListener( 'resize', resize );

}

function resize() {

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

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

}

function animate() {

controls.update();

renderer.render( scene, camera );

}

</script>
</body>
</html>
1 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Expand Up @@ -134,6 +134,7 @@ const exceptionList = [
'webgpu_tsl_editor',
'webgpu_tsl_transpiler',
'webgpu_portal',
'webgpu_custom_fog',

// WebGPU idleTime and parseTime too low
'webgpu_compute_particles',
Expand Down