Skip to content
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
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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Mugen87 Do you, by chance, have a fiddle showing this helper being used, and rendering some representation of shadow camera depth correctly? 🙏

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Mugen87 The helper is rendering black for me. A quick example would be greatly appreciated. I am hoping this helper can verify if the full extent of the depth range is being used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe there is a regression since we don't have a dedicated example yet. How about we add a copy of webgl_shadowmap_viewer and name it webgpu_shadowmap_viewer. When then have some sample code for testing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK. So to confirm, we don't render the depthTexture directly, but the shadow.map.texture, instead? And the texture should display a grayscale image of depth in [0, 1]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we don't render the depthTexture directly, but the shadow.map.texture, instead?

Yes. But to be honest I don't know anymore how the visualized texture looks like. Maybe it was just a black cutout.


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 };
Binary file modified examples/screenshots/webgpu_backdrop_water.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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