Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -439,6 +439,7 @@
"webgpu_rtt",
"webgpu_sandbox",
"webgpu_shadertoy",
"webgpu_shadow_contact",
"webgpu_shadowmap",
"webgpu_shadowmap_array",
"webgpu_shadowmap_csm",
Expand Down
Binary file added examples/screenshots/webgpu_shadow_contact.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion examples/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,6 @@
"webgpu_tsl_compute_attractors_particles": [ "gpgpu" ],
"webgpu_ocean": [ "water" ],
"webgpu_video_frame": [ "webcodecs" ],
"webgpu_shadowmap_array": [ "tile" ]
"webgpu_shadowmap_array": [ "tile" ],
"webgpu_shadow_contact": [ "shadow", "soft", "tsl" ]
}
257 changes: 257 additions & 0 deletions examples/webgpu_shadow_contact.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - contact shadows</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="example.css">
</head>
<body>
<div id="info" class="invert">
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

<div class="title-wrapper">
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Contact Shadows</span>
</div>

<small>Contact shadows using Gaussian blur for smooth rendering.</small>
</div>

<div id="container"></div>

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

<script type="module">

import * as THREE from 'three/webgpu';
import { vec3, vec4, uniform, texture, depth, float } from 'three/tsl';
import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { Inspector } from 'three/addons/inspector/Inspector.js';

let camera, scene, renderer;
let shadowCamera, shadowGroup;
let renderTarget;
let plane, fillPlane, cameraHelper;
let depthMaterial, shadowPlaneMaterial, fillPlaneMaterial;

const meshes = [];

const PLANE_WIDTH = 2.5;
const PLANE_HEIGHT = 2.5;
const CAMERA_HEIGHT = 0.3;

const state = {
shadow: { blur: 3.5, darkness: 1.0, opacity: 1.0 },
plane: { color: '#ffffff', opacity: 1.0 },
showWireframe: false
};


const uBlur = uniform( state.shadow.blur );
const uDarkness = uniform( state.shadow.darkness );
const uShadowOpacity = uniform( state.shadow.opacity );
const uPlaneOpacity = uniform( state.plane.opacity );
const uPlaneColor = uniform( new THREE.Color( state.plane.color ) );
const uPlaneY = uniform( - 0.3 );

init();

function init() {

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 0.5, 1, 2 );

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );

window.addEventListener( 'resize', onWindowResize );

const geometries = [
new THREE.BoxGeometry( 0.4, 0.4, 0.4 ),
new THREE.IcosahedronGeometry( 0.3 ),
new THREE.TorusKnotGeometry( 0.4, 0.05, 256, 24, 1, 3 )
];

const material = new THREE.MeshNormalMaterial();

for ( let i = 0, l = geometries.length; i < l; i ++ ) {

const angle = ( i / l ) * Math.PI * 2;
const mesh = new THREE.Mesh( geometries[ i ], material );
mesh.position.y = 0.1;
mesh.position.x = Math.cos( angle ) / 2.0;
mesh.position.z = Math.sin( angle ) / 2.0;
scene.add( mesh );
meshes.push( mesh );

}

shadowGroup = new THREE.Group();
shadowGroup.position.y = uPlaneY.value;
scene.add( shadowGroup );

renderTarget = new THREE.RenderTarget( 512, 512, { depthBuffer: true } );
renderTarget.texture.generateMipmaps = false;

const planeGeometry = new THREE.PlaneGeometry( PLANE_WIDTH, PLANE_HEIGHT ).rotateX( Math.PI / 2 );

depthMaterial = new THREE.MeshBasicNodeMaterial();

const alphaDepth = float( 1 ).sub( depth ).mul( uDarkness );

depthMaterial.outputNode = vec4( vec3( 0 ), alphaDepth );
depthMaterial.depthTest = false;
depthMaterial.depthWrite = false;

shadowPlaneMaterial = new THREE.MeshBasicNodeMaterial();
shadowPlaneMaterial.transparent = true;
shadowPlaneMaterial.depthWrite = false;

if ( ! renderTarget.texture.image ) renderTarget.texture.image = { width: 512, height: 512 };

const blurredShadow = gaussianBlur( texture( renderTarget.texture ), uBlur, 4, { premultipliedAlpha: false } );
shadowPlaneMaterial.outputNode = vec4( vec3( 0 ), blurredShadow.a.mul( uShadowOpacity ) );

plane = new THREE.Mesh( planeGeometry, shadowPlaneMaterial );
plane.renderOrder = 1;
plane.scale.y = - 1;
plane.scale.z = - 1;
shadowGroup.add( plane );

fillPlaneMaterial = new THREE.MeshBasicNodeMaterial();
fillPlaneMaterial.transparent = true;
fillPlaneMaterial.depthWrite = false;
fillPlaneMaterial.outputNode = vec4( uPlaneColor.toVec3(), uPlaneOpacity );
fillPlane = new THREE.Mesh( planeGeometry, fillPlaneMaterial );
fillPlane.rotateX( Math.PI );
shadowGroup.add( fillPlane );

shadowCamera = new THREE.OrthographicCamera( - PLANE_WIDTH / 2, PLANE_WIDTH / 2, PLANE_HEIGHT / 2, - PLANE_HEIGHT / 2, 0, CAMERA_HEIGHT );
shadowCamera.rotation.x = Math.PI / 2;
shadowGroup.add( shadowCamera );

cameraHelper = new THREE.CameraHelper( shadowCamera );

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

renderer.inspector = new Inspector();

const params = {
shadowBlur: state.shadow.blur,
shadowDarkness: state.shadow.darkness,
shadowOpacity: state.shadow.opacity,
planeColor: state.plane.color,
planeOpacity: state.plane.opacity,
showWireframe: state.showWireframe
};

const gui = renderer.inspector.createParameters( 'Settings' );

gui.add( params, 'shadowBlur', 0, 15, 0.1 ).onChange( () => {

state.shadow.blur = params.shadowBlur;
uBlur.value = state.shadow.blur;

} );

gui.add( params, 'shadowDarkness', 0.1, 5, 0.1 ).onChange( () => {

state.shadow.darkness = params.shadowDarkness;
uDarkness.value = state.shadow.darkness;

} );

gui.add( params, 'shadowOpacity', 0, 1, 0.01 ).onChange( () => {

state.shadow.opacity = params.shadowOpacity;
uShadowOpacity.value = state.shadow.opacity;

} );

gui.addColor( params, 'planeColor' ).onChange( () => {

state.plane.color = params.planeColor;
uPlaneColor.value.set( state.plane.color );

} );

gui.add( params, 'planeOpacity', 0, 1, 0.01 ).onChange( () => {

state.plane.opacity = params.planeOpacity;
uPlaneOpacity.value = state.plane.opacity;

} );

gui.add( params, 'showWireframe' ).onChange( () => {

if ( params.showWireframe ) scene.add( cameraHelper );
else scene.remove( cameraHelper );

} );

new OrbitControls( camera, renderer.domElement );

}

function onWindowResize() {

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

}

function animate() {

meshes.forEach( m => {

m.rotation.x += 0.01;
m.rotation.y += 0.02;

} );


const initialBackground = scene.background;
scene.background = null;

const prevOverride = scene.overrideMaterial;
const prevHelperVisible = cameraHelper.visible;
cameraHelper.visible = false;
scene.overrideMaterial = depthMaterial;
const initialAutoClear = renderer.autoClear;
renderer.autoClear = true;
const initialClearAlpha = renderer.getClearAlpha ? renderer.getClearAlpha() : undefined;
if ( initialClearAlpha !== undefined ) renderer.setClearAlpha( 0 );

renderer.setRenderTarget( renderTarget );
renderer.clear();
renderer.render( scene, shadowCamera );

scene.overrideMaterial = prevOverride;
renderer.setRenderTarget( null );
renderer.autoClear = initialAutoClear;
if ( initialClearAlpha !== undefined ) renderer.setClearAlpha( initialClearAlpha );
scene.background = initialBackground;
cameraHelper.visible = prevHelperVisible;

renderer.render( scene, camera );

}

</script>
</body>
</html>