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

ShadowMapViewer: Add WebGPU version. #29331

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
201 changes: 201 additions & 0 deletions examples/jsm/utils/ShadowMapViewerGPU.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {
DoubleSide,
CanvasTexture,
Mesh,
MeshBasicMaterial,
NodeMaterial,
OrthographicCamera,
PlaneGeometry,
Scene,
Texture
} from 'three';
import { texture } from 'three/tsl';

/**
* This is a helper for visualising a given light's shadow map.
* It works for shadow casting lights: DirectionalLight and SpotLight.
* It renders out the shadow map and displays it on a HUD.
*
* Example usage:
* 1) Import ShadowMapViewer into your app.
*
* 2) Create a shadow casting light and name it optionally:
* let light = new DirectionalLight( 0xffffff, 1 );
* light.castShadow = true;
* light.name = 'Sun';
*
* 3) Create a shadow map viewer for that light and set its size and position optionally:
* let shadowMapViewer = new ShadowMapViewer( light );
* shadowMapViewer.size.set( 128, 128 ); //width, height default: 256, 256
* shadowMapViewer.position.set( 10, 10 ); //x, y in pixel default: 0, 0 (top left corner)
*
* 4) Render the shadow map viewer in your render loop:
* shadowMapViewer.render( renderer );
*
* 5) Optionally: Update the shadow map viewer on window resize:
* shadowMapViewer.updateForWindowResize();
*
* 6) If you set the position or size members directly, you need to call shadowMapViewer.update();
*/

class ShadowMapViewer {

constructor( light ) {

//- Internals
const scope = this;
const doRenderLabel = ( light.name !== undefined && light.name !== '' );
let currentAutoClear;

//Holds the initial position and dimension of the HUD
const frame = {
x: 10,
y: 10,
width: 256,
height: 256
};

const camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 10 );
camera.position.set( 0, 0, 2 );
const scene = new Scene();

//HUD for shadow map

const material = new NodeMaterial();

const shadowMapUniform = texture( new Texture() );
material.fragmentNode = shadowMapUniform;

const plane = new PlaneGeometry( frame.width, frame.height );
const mesh = new Mesh( plane, material );

scene.add( mesh );

//Label for light's name
let labelCanvas, labelMesh;

if ( doRenderLabel ) {

labelCanvas = document.createElement( 'canvas' );

const context = labelCanvas.getContext( '2d' );
context.font = 'Bold 20px Arial';

const labelWidth = context.measureText( light.name ).width;
labelCanvas.width = labelWidth;
labelCanvas.height = 25; //25 to account for g, p, etc.

context.font = 'Bold 20px Arial';
context.fillStyle = 'rgba( 255, 0, 0, 1 )';
context.fillText( light.name, 0, 20 );

const labelTexture = new CanvasTexture( labelCanvas );

const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide, transparent: true } );

const labelPlane = new PlaneGeometry( labelCanvas.width, labelCanvas.height );
labelMesh = new Mesh( labelPlane, labelMaterial );

scene.add( labelMesh );

}

function resetPosition() {

scope.position.set( scope.position.x, scope.position.y );

}

//- API
// Set to false to disable displaying this shadow map
this.enabled = true;

// Set the size of the displayed shadow map on the HUD
this.size = {
width: frame.width,
height: frame.height,
set: function ( width, height ) {

this.width = width;
this.height = height;

mesh.scale.set( this.width / frame.width, this.height / frame.height, 1 );

//Reset the position as it is off when we scale stuff
resetPosition();

}
};

// Set the position of the displayed shadow map on the HUD
this.position = {
x: frame.x,
y: frame.y,
set: function ( x, y ) {

this.x = x;
this.y = y;

const width = scope.size.width;
const height = scope.size.height;

mesh.position.set( - window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0 );

if ( doRenderLabel ) labelMesh.position.set( mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0 );

}
};

this.render = function ( renderer ) {

if ( this.enabled ) {

//Because a light's .shadowMap is only initialised after the first render pass
//we have to make sure the correct map is sent into the shader, otherwise we
//always end up with the scene's first added shadow casting light's shadowMap
//in the shader
//See: https://github.com/mrdoob/three.js/issues/5932
shadowMapUniform.value = light.shadow.map.texture;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Shadow maps are implemented differently in WebGPURenderer since they use depth textures and don't rely on MeshDepthMaterial. Hence, the debug texture looks a bit different since we use the shadow color for visualization purposes.


currentAutoClear = renderer.autoClear;
renderer.autoClear = false; // To allow render overlay
renderer.clearDepth();
renderer.render( scene, camera );
renderer.autoClear = currentAutoClear;

}

};

this.updateForWindowResize = function () {

if ( this.enabled ) {

camera.left = window.innerWidth / - 2;
camera.right = window.innerWidth / 2;
camera.top = window.innerHeight / 2;
camera.bottom = window.innerHeight / - 2;
camera.updateProjectionMatrix();

this.update();

}

};

this.update = function () {

this.position.set( this.position.x, this.position.y );
this.size.set( this.size.width, this.size.height );

};

//Force an update to set position/size
this.update();

}

}


export { ShadowMapViewer };
1 change: 1 addition & 0 deletions src/nodes/lighting/AnalyticLightNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class AnalyticLightNode extends LightingNode {
const shadowNode = frustumTest.select( filterFn( { depthTexture: ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture, shadowCoord, shadow } ), float( 1 ) );

this.shadowMap = shadowMap;
this.light.shadow.map = shadowMap;

this.shadowNode = shadowNode;
this.shadowColorNode = shadowColorNode = this.colorNode.mul( mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ) );
Expand Down
Loading