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

TSL: Add function for BPCEM. #29773

Merged
merged 3 commits into from
Oct 30, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@
"webgpu_materials_arrays",
"webgpu_materials_basic",
"webgpu_materials_displacementmap",
"webgpu_materials_envmaps_bpcem",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a different name, for clarity?

webgpu_materials_envmaps_box_projected

Similar to

webgl_materials_envmaps_ground_projected

(The latter is currently missing an underscore.)

Copy link
Collaborator Author

@Mugen87 Mugen87 Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webgl_materials_envmaps_ground_projected

I think this should be webgl_skybox_ground_projected. This example is not related to the envMap material property.

Regarding the new example, I don't feel strong about the name. BPCEM is a similar term like PMREM which is why I like and picked it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever you prefer is OK with me... I thought the acronym may be unfamiliar.

"webgpu_materials_envmaps",
"webgpu_materials_lightmap",
"webgpu_materials_matcap",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
226 changes: 226 additions & 0 deletions examples/webgpu_materials_envmaps_bpcem.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - materials - bpcem</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 - box projected cube environment mapping (BPCEM)<br/>
</div>

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


<script type="module">

import * as THREE from 'three';
import { bumpMap, float, getParallaxCorrectNormal, pmremTexture, reflectVector, texture, uniform, vec3 } from 'three/tsl';

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js';

let camera, scene, renderer;

let controls, cubeCamera;

let groundPlane, wallMat;

init();

function init() {

THREE.RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );

// scene

scene = new THREE.Scene();

// camera

camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 0, 200, - 200 );

// cube camera for environment map

const renderTarget = new THREE.WebGLCubeRenderTarget( 512 );
renderTarget.texture.type = THREE.HalfFloatType;
renderTarget.texture.minFilter = THREE.LinearMipmapLinearFilter;
renderTarget.texture.magFilter = THREE.LinearFilter;
renderTarget.texture.generateMipmaps = true;
renderTarget.texture.mapping = THREE.CubeReflectionMapping;

cubeCamera = new THREE.CubeCamera( 1, 1000, renderTarget );
cubeCamera.position.set( 0, - 100, 0 );

// ground floor ( with box projected environment mapping )

const loader = new THREE.TextureLoader();
const rMap = loader.load( 'textures/lava/lavatile.jpg' );
rMap.wrapS = THREE.RepeatWrapping;
rMap.wrapT = THREE.RepeatWrapping;
rMap.repeat.set( 2, 1 );

const roughnessUniform = uniform( 0.25 );

const defaultMat = new THREE.MeshStandardNodeMaterial();
defaultMat.envNode = pmremTexture( renderTarget.texture );
defaultMat.roughnessNode = texture( rMap ).mul( roughnessUniform );
defaultMat.metalnessNode = float( 1 );

const boxProjectedMat = new THREE.MeshStandardNodeMaterial();
boxProjectedMat.envNode = pmremTexture( renderTarget.texture, getParallaxCorrectNormal( reflectVector, vec3( 200, 100, 100 ), vec3( 0, - 50, 0 ) ) );
boxProjectedMat.roughnessNode = texture( rMap ).mul( roughnessUniform );
boxProjectedMat.metalnessNode = float( 1 );

groundPlane = new THREE.Mesh( new THREE.PlaneGeometry( 200, 100, 100 ), boxProjectedMat );
groundPlane.rotateX( - Math.PI / 2 );
groundPlane.position.set( 0, - 49, 0 );
scene.add( groundPlane );

// walls

const diffuseTex = loader.load( 'textures/brick_diffuse.jpg' );
diffuseTex.colorSpace = THREE.SRGBColorSpace;
const bumpTex = loader.load( 'textures/brick_bump.jpg' );

wallMat = new THREE.MeshStandardNodeMaterial();

wallMat.colorNode = texture( diffuseTex );
wallMat.normalNode = bumpMap( texture( bumpTex ), float( 5 ) );

const planeGeo = new THREE.PlaneGeometry( 100, 100 );

const planeBack1 = new THREE.Mesh( planeGeo, wallMat );
planeBack1.position.z = - 50;
planeBack1.position.x = - 50;
scene.add( planeBack1 );

const planeBack2 = new THREE.Mesh( planeGeo, wallMat );
planeBack2.position.z = - 50;
planeBack2.position.x = 50;
scene.add( planeBack2 );

const planeFront1 = new THREE.Mesh( planeGeo, wallMat );
planeFront1.position.z = 50;
planeFront1.position.x = - 50;
planeFront1.rotateY( Math.PI );
scene.add( planeFront1 );

const planeFront2 = new THREE.Mesh( planeGeo, wallMat );
planeFront2.position.z = 50;
planeFront2.position.x = 50;
planeFront2.rotateY( Math.PI );
scene.add( planeFront2 );

const planeRight = new THREE.Mesh( planeGeo, wallMat );
planeRight.position.x = 100;
planeRight.rotateY( - Math.PI / 2 );
scene.add( planeRight );

const planeLeft = new THREE.Mesh( planeGeo, wallMat );
planeLeft.position.x = - 100;
planeLeft.rotateY( Math.PI / 2 );
scene.add( planeLeft );

// area lights

const width = 50;
const height = 50;
const intensity = 5;

const blueRectLight = new THREE.RectAreaLight( 0x9aaeff, intensity, width, height );
blueRectLight.position.set( - 99, 5, 0 );
blueRectLight.lookAt( 0, 5, 0 );
scene.add( blueRectLight );

const blueRectLightHelper = new RectAreaLightHelper( blueRectLight, 0xffffff );
blueRectLight.add( blueRectLightHelper );

const redRectLight = new THREE.RectAreaLight( 0xf3aaaa, intensity, width, height );
redRectLight.position.set( 99, 5, 0 );
redRectLight.lookAt( 0, 5, 0 );
scene.add( redRectLight );

const redRectLightHelper = new RectAreaLightHelper( redRectLight, 0xffffff );
redRectLight.add( redRectLightHelper );

// renderer

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

window.addEventListener( 'resize', onWindowResize );

// controls

controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, - 10, 0 );
controls.maxDistance = 400;
controls.minDistance = 10;
controls.update();

// gui

const gui = new GUI();
const params = {
'box projected': true
};
gui.add( params, 'box projected' ).onChange( ( value ) => {

groundPlane.material = ( value ) ? boxProjectedMat : defaultMat;

} );
gui.add( roughnessUniform, 'value', 0, 1 ).name( 'roughness' );

}

function onWindowResize() {

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

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

}

function updateCubeMap() {

groundPlane.visible = false;

cubeCamera.position.copy( groundPlane.position );

cubeCamera.update( renderer, scene );

groundPlane.visible = true;

}

function animate() {

updateCubeMap();

renderer.render( scene, camera );

}

</script>

</body>
</html>
1 change: 1 addition & 0 deletions src/nodes/TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,6 @@ export { default as V_GGX_SmithCorrelated } from './functions/BSDF/V_GGX_SmithCo
export * from './lighting/LightUtils.js';

export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js';
export { default as getParallaxCorrectNormal } from './functions/material/getParallaxCorrectNormal.js';
export { default as getRoughness } from './functions/material/getRoughness.js';
export { default as getShIrradianceAt } from './functions/material/getShIrradianceAt.js';
22 changes: 22 additions & 0 deletions src/nodes/functions/material/getParallaxCorrectNormal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { positionWorld } from '../../accessors/Position';
import { float, Fn, min, normalize, sub, vec3 } from '../../tsl/TSLBase.js';

// https://devlog-martinsh.blogspot.com/2011/09/box-projected-cube-environment-mapping.html

const getParallaxCorrectNormal = /*@__PURE__*/ Fn( ( [ normal, cubeSize, cubePos ] ) => {

const nDir = normalize( normal ).toVar( 'nDir' );
const rbmax = sub( float( 0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar( 'rbmax' );
const rbmin = sub( float( - 0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar( 'rbmin' );
const rbminmax = vec3().toVar( 'rbminmax' );
rbminmax.x = nDir.x.greaterThan( float( 0 ) ).select( rbmax.x, rbmin.x );
rbminmax.y = nDir.y.greaterThan( float( 0 ) ).select( rbmax.y, rbmin.y );
rbminmax.z = nDir.z.greaterThan( float( 0 ) ).select( rbmax.z, rbmin.z );

const correction = min( min( rbminmax.x, rbminmax.y ), rbminmax.z ).toVar( 'correction' );
const boxIntersection = positionWorld.add( nDir.mul( correction ) ).toVar( 'boxIntersection' );
return boxIntersection.sub( cubePos );

} );

export default getParallaxCorrectNormal;
1 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const exceptionList = [
'webgpu_tsl_vfx_linkedparticles',
'webgpu_tsl_vfx_tornado',
'webgpu_textures_anisotropy',
'webgpu_materials_envmaps_bpcem',

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