-
-
Notifications
You must be signed in to change notification settings - Fork 35.5k
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
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 onMeshDepthMaterial
. Hence, the debug texture looks a bit different since we use the shadow color for visualization purposes.