-
-
Notifications
You must be signed in to change notification settings - Fork 35.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18787 from marcofugaro/contact-shadow
Examples: add contact shadow example
- Loading branch information
Showing
2 changed files
with
313 additions
and
0 deletions.
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
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,312 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>three.js webgl - contact shadows</title> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||
<link type="text/css" rel="stylesheet" href="main.css"> | ||
<style> | ||
body { | ||
background-color: #fff; | ||
color: #000; | ||
} | ||
a { | ||
color: #08f; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="info"> | ||
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - contact shadows | ||
</div> | ||
<script type="module"> | ||
|
||
import * as THREE from '../build/three.module.js'; | ||
import { OrbitControls } from './jsm/controls/OrbitControls.js'; | ||
import Stats from './jsm/libs/stats.module.js'; | ||
import { GUI } from './jsm/libs/dat.gui.module.js'; | ||
import { HorizontalBlurShader } from './jsm/shaders/HorizontalBlurShader.js'; | ||
import { VerticalBlurShader } from './jsm/shaders/VerticalBlurShader.js'; | ||
|
||
var camera, scene, renderer, stats, gui; | ||
|
||
var meshes = []; | ||
|
||
const PLANE_WIDTH = 2.5; | ||
const PLANE_HEIGHT = 2.5; | ||
const CAMERA_HEIGHT = 0.3; | ||
|
||
var state = { | ||
shadow: { | ||
blur: 3.5, | ||
darkness: 1, | ||
opacity: 1, | ||
}, | ||
plane: { | ||
color: '#ffffff', | ||
opacity: 1, | ||
}, | ||
showWireframe: false, | ||
}; | ||
|
||
var shadowGroup, renderTarget, renderTargetBlur, shadowCamera, cameraHelper, depthMaterial, horizontalBlurMaterial, verticalBlurMaterial; | ||
|
||
var plane, blurPlane, fillPlane; | ||
|
||
init(); | ||
animate(); | ||
|
||
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 ); | ||
|
||
stats = new Stats(); | ||
document.body.appendChild( stats.dom ); | ||
|
||
window.addEventListener( 'resize', onWindowResize ); | ||
|
||
// add the example meshes | ||
|
||
var geometries = [ | ||
new THREE.BoxBufferGeometry( 0.4, 0.4, 0.4 ), | ||
new THREE.IcosahedronBufferGeometry( 0.3 ), | ||
new THREE.TorusKnotBufferGeometry( 0.4, 0.05, 256, 24, 1, 3 ) | ||
]; | ||
|
||
var material = new THREE.MeshNormalMaterial(); | ||
|
||
for ( var i = 0, l = geometries.length; i < l; i ++ ) { | ||
|
||
var angle = ( i / l ) * Math.PI * 2; | ||
|
||
var geometry = geometries[ i ]; | ||
var mesh = new THREE.Mesh( geometry, 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 ); | ||
|
||
} | ||
|
||
|
||
|
||
// the container, if you need to move the plane just move this | ||
shadowGroup = new THREE.Group(); | ||
shadowGroup.position.y = - 0.3; | ||
scene.add( shadowGroup ); | ||
|
||
// the render target that will show the shadows in the plane texture | ||
renderTarget = new THREE.WebGLRenderTarget( 512, 512 ); | ||
renderTarget.texture.generateMipmaps = false; | ||
|
||
// the render target that we will use to blur the first render target | ||
renderTargetBlur = new THREE.WebGLRenderTarget( 512, 512 ); | ||
renderTargetBlur.texture.generateMipmaps = false; | ||
|
||
|
||
// make a plane and make it face up | ||
var planeGeometry = new THREE.PlaneBufferGeometry( PLANE_WIDTH, PLANE_HEIGHT ).rotateX( Math.PI / 2 ); | ||
var material = new THREE.MeshBasicMaterial( { | ||
map: renderTarget.texture, | ||
opacity: state.shadow.opacity, | ||
transparent: true, | ||
} ); | ||
plane = new THREE.Mesh( planeGeometry, material ); | ||
shadowGroup.add( plane ); | ||
|
||
// the y from the texture is flipped! | ||
plane.scale.y = - 1; | ||
|
||
// the plane onto which to blur the texture | ||
blurPlane = new THREE.Mesh( planeGeometry ); | ||
blurPlane.visible = false; | ||
shadowGroup.add( blurPlane ); | ||
|
||
// the plane with the color of the ground | ||
var material = new THREE.MeshBasicMaterial( { | ||
color: state.plane.color, | ||
opacity: state.plane.opacity, | ||
transparent: true, | ||
} ); | ||
fillPlane = new THREE.Mesh( planeGeometry, material ); | ||
fillPlane.rotateX( Math.PI ); | ||
fillPlane.position.y -= 0.00001; | ||
shadowGroup.add( fillPlane ); | ||
|
||
// the camera to render the depth material from | ||
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; // get the camera to look up | ||
shadowGroup.add( shadowCamera ); | ||
|
||
cameraHelper = new THREE.CameraHelper( shadowCamera ); | ||
|
||
// like MeshDepthMaterial, but goes from black to transparent | ||
depthMaterial = new THREE.MeshDepthMaterial(); | ||
depthMaterial.userData.darkness = { value: state.shadow.darkness }; | ||
depthMaterial.onBeforeCompile = function ( shader ) { | ||
|
||
shader.uniforms.darkness = depthMaterial.userData.darkness; | ||
shader.fragmentShader = ` | ||
uniform float darkness; | ||
${shader.fragmentShader.replace( | ||
'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );', | ||
'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );' | ||
)} | ||
`; | ||
|
||
}; | ||
depthMaterial.depthTest = false; | ||
depthMaterial.depthWrite = false; | ||
|
||
horizontalBlurMaterial = new THREE.ShaderMaterial( HorizontalBlurShader ); | ||
horizontalBlurMaterial.depthTest = false; | ||
|
||
verticalBlurMaterial = new THREE.ShaderMaterial( VerticalBlurShader ); | ||
verticalBlurMaterial.depthTest = false; | ||
|
||
// | ||
|
||
gui = new GUI(); | ||
var shadowFolder = gui.addFolder( 'shadow' ); | ||
shadowFolder.open(); | ||
var planeFolder = gui.addFolder( 'plane' ); | ||
planeFolder.open(); | ||
|
||
|
||
shadowFolder.add( state.shadow, 'blur', 0, 15, 0.1 ); | ||
shadowFolder.add( state.shadow, 'darkness', 1, 5, 0.1 ).onChange( function () { | ||
|
||
depthMaterial.userData.darkness.value = state.shadow.darkness; | ||
|
||
} ); | ||
shadowFolder.add( state.shadow, 'opacity', 0, 1, 0.01 ).onChange( function () { | ||
|
||
plane.material.opacity = state.shadow.opacity; | ||
|
||
} ); | ||
planeFolder.addColor( state.plane, 'color' ).onChange( function () { | ||
|
||
fillPlane.material.color = new THREE.Color( state.plane.color ); | ||
|
||
} ); | ||
planeFolder.add( state.plane, 'opacity', 0, 1, 0.01 ).onChange( function () { | ||
|
||
fillPlane.material.opacity = state.plane.opacity; | ||
|
||
} ); | ||
|
||
gui.add( state, 'showWireframe', true ).onChange( function () { | ||
|
||
if ( state.showWireframe ) { | ||
|
||
scene.add( cameraHelper ); | ||
|
||
} else { | ||
|
||
scene.remove( cameraHelper ); | ||
|
||
} | ||
|
||
} ); | ||
|
||
// | ||
|
||
renderer = new THREE.WebGLRenderer( { antialias: true } ); | ||
renderer.setPixelRatio( window.devicePixelRatio ); | ||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
document.body.appendChild( renderer.domElement ); | ||
|
||
// | ||
|
||
new OrbitControls( camera, renderer.domElement ); | ||
|
||
} | ||
|
||
function onWindowResize() { | ||
|
||
camera.aspect = window.innerWidth / window.innerHeight; | ||
camera.updateProjectionMatrix(); | ||
|
||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
|
||
} | ||
|
||
// renderTarget --> blurPlane (horizontalBlur) --> renderTargetBlur --> blurPlane (verticalBlur) --> renderTarget | ||
function blurShadow( amount ) { | ||
|
||
blurPlane.visible = true; | ||
|
||
// blur horizontally and draw in the renderTargetBlur | ||
blurPlane.material = horizontalBlurMaterial; | ||
blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture; | ||
horizontalBlurMaterial.uniforms.h.value = amount * 1 / 256; | ||
|
||
renderer.setRenderTarget( renderTargetBlur ); | ||
renderer.render( blurPlane, shadowCamera ); | ||
|
||
// blur vertically and draw in the main renderTarget | ||
blurPlane.material = verticalBlurMaterial; | ||
blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture; | ||
verticalBlurMaterial.uniforms.v.value = amount * 1 / 256; | ||
|
||
renderer.setRenderTarget( renderTarget ); | ||
renderer.render( blurPlane, shadowCamera ); | ||
|
||
blurPlane.visible = false; | ||
|
||
} | ||
|
||
function animate( ) { | ||
|
||
requestAnimationFrame( animate ); | ||
|
||
// | ||
|
||
meshes.forEach( mesh => { | ||
|
||
mesh.rotation.x += 0.01; | ||
mesh.rotation.y += 0.02; | ||
|
||
} ); | ||
|
||
// | ||
|
||
// remove the background | ||
var initialBackground = scene.background; | ||
scene.background = null; | ||
|
||
// force the depthMaterial to everything | ||
cameraHelper.visible = false; | ||
scene.overrideMaterial = depthMaterial; | ||
|
||
// render to the render target to get the depths | ||
renderer.setRenderTarget( renderTarget ); | ||
renderer.render( scene, shadowCamera ); | ||
|
||
// and reset the override material | ||
scene.overrideMaterial = null; | ||
cameraHelper.visible = true; | ||
|
||
blurShadow( state.shadow.blur ); | ||
|
||
// a second pass to reduce the artifacts | ||
// (0.4 is the minimum blur amout so that the artifacts are gone) | ||
blurShadow( state.shadow.blur * 0.4 ); | ||
|
||
// reset and render the normal scene | ||
renderer.setRenderTarget( null ); | ||
scene.background = initialBackground; | ||
|
||
renderer.render( scene, camera ); | ||
stats.update(); | ||
|
||
} | ||
|
||
</script> | ||
</body> | ||
</html> |