webgl_shadow_contact.html 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - contact shadows</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  7. <link type="text/css" rel="stylesheet" href="main.css">
  8. <style>
  9. body {
  10. background-color: #fff;
  11. color: #000;
  12. }
  13. a {
  14. color: #08f;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div id="info">
  20. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - contact shadows
  21. </div>
  22. <script type="module">
  23. import * as THREE from '../build/three.module.js';
  24. import { OrbitControls } from './jsm/controls/OrbitControls.js';
  25. import Stats from './jsm/libs/stats.module.js';
  26. import { GUI } from './jsm/libs/dat.gui.module.js';
  27. import { HorizontalBlurShader } from './jsm/shaders/HorizontalBlurShader.js';
  28. import { VerticalBlurShader } from './jsm/shaders/VerticalBlurShader.js';
  29. var camera, scene, renderer, stats, gui;
  30. var meshes = [];
  31. const PLANE_WIDTH = 2.5;
  32. const PLANE_HEIGHT = 2.5;
  33. const CAMERA_HEIGHT = 0.3;
  34. var state = {
  35. blur: 3.5,
  36. darkness: 1,
  37. opacity: 1,
  38. showWireframe: false,
  39. };
  40. var shadowGroup, renderTarget, renderTargetBlur, shadowCamera, cameraHelper, depthMaterial, horizontalBlurMaterial, verticalBlurMaterial;
  41. var planeGeometry, planeMaterial, plane, blurPlane;
  42. init();
  43. animate();
  44. function init() {
  45. camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
  46. camera.position.set( 0.5, 1, 2 );
  47. scene = new THREE.Scene();
  48. stats = new Stats();
  49. document.body.appendChild( stats.dom );
  50. window.addEventListener( 'resize', onWindowResize );
  51. // add the example meshes
  52. var geometries = [
  53. new THREE.BoxBufferGeometry( 0.4, 0.4, 0.4 ),
  54. new THREE.IcosahedronBufferGeometry( 0.3 ),
  55. new THREE.TorusKnotBufferGeometry( 0.4, 0.05, 256, 24, 1, 3 )
  56. ];
  57. var material = new THREE.MeshNormalMaterial();
  58. for ( var i = 0, l = geometries.length; i < l; i ++ ) {
  59. var angle = ( i / l ) * Math.PI * 2;
  60. var geometry = geometries[ i ];
  61. var mesh = new THREE.Mesh( geometry, material );
  62. mesh.position.y = 0.1;
  63. mesh.position.x = Math.cos( angle ) / 2.0;
  64. mesh.position.z = Math.sin( angle ) / 2.0;
  65. scene.add( mesh );
  66. meshes.push( mesh );
  67. }
  68. // the container, if you need to move the plane just move this
  69. shadowGroup = new THREE.Group();
  70. shadowGroup.position.y = - 0.3;
  71. scene.add( shadowGroup );
  72. // the render target that will show the shadows in the plane texture
  73. renderTarget = new THREE.WebGLRenderTarget( 512, 512 );
  74. renderTarget.texture.generateMipmaps = false;
  75. // the render target that we will use to blur the first render target
  76. renderTargetBlur = new THREE.WebGLRenderTarget( 512, 512 );
  77. renderTargetBlur.texture.generateMipmaps = false;
  78. // make a plane and make it face up
  79. planeGeometry = new THREE.PlaneGeometry( PLANE_WIDTH, PLANE_HEIGHT ).rotateX( Math.PI / 2 );
  80. planeMaterial = new THREE.MeshBasicMaterial( {
  81. map: renderTarget.texture,
  82. opacity: state.opacity,
  83. transparent: true,
  84. } );
  85. plane = new THREE.Mesh( planeGeometry, planeMaterial );
  86. shadowGroup.add( plane );
  87. // the y from the texture is flipped!
  88. plane.scale.y = - 1;
  89. // the plane onto which to blur the texture
  90. blurPlane = new THREE.Mesh( planeGeometry );
  91. blurPlane.visible = false;
  92. shadowGroup.add( blurPlane );
  93. // the camera to render the depth material from
  94. shadowCamera = new THREE.OrthographicCamera( - PLANE_WIDTH / 2, PLANE_WIDTH / 2, PLANE_HEIGHT / 2, - PLANE_HEIGHT / 2, 0, CAMERA_HEIGHT );
  95. shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
  96. shadowGroup.add( shadowCamera );
  97. cameraHelper = new THREE.CameraHelper( shadowCamera );
  98. // like MeshDepthMaterial, but goes from black to transparent
  99. depthMaterial = new THREE.ShaderMaterial( {
  100. uniforms: {
  101. darkness: { value: state.darkness },
  102. },
  103. vertexShader: THREE.ShaderChunk[ 'depth_vert' ],
  104. fragmentShader: `
  105. #define DEPTH_PACKING 3200
  106. uniform float darkness;
  107. ${THREE.ShaderChunk[ 'depth_frag' ].replace(
  108. 'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
  109. 'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );'
  110. )}
  111. `,
  112. } );
  113. depthMaterial.depthTest = false;
  114. depthMaterial.depthWrite = false;
  115. horizontalBlurMaterial = new THREE.ShaderMaterial( HorizontalBlurShader );
  116. horizontalBlurMaterial.depthTest = false;
  117. verticalBlurMaterial = new THREE.ShaderMaterial( VerticalBlurShader );
  118. verticalBlurMaterial.depthTest = false;
  119. //
  120. gui = new GUI();
  121. gui.add( state, 'blur', 0, 15, 0.1 );
  122. gui.add( state, 'darkness', 1, 5, 0.1 ).onChange( function () {
  123. depthMaterial.uniforms.darkness.value = state.darkness;
  124. } );
  125. gui.add( state, 'opacity', 0, 1, 0.01 ).onChange( function () {
  126. planeMaterial.opacity = state.opacity;
  127. } );
  128. gui.add( state, 'showWireframe', true ).onChange( function () {
  129. if ( state.showWireframe ) {
  130. scene.add( cameraHelper );
  131. } else {
  132. scene.remove( cameraHelper );
  133. }
  134. } );
  135. //
  136. renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
  137. renderer.setPixelRatio( window.devicePixelRatio );
  138. renderer.setSize( window.innerWidth, window.innerHeight );
  139. renderer.outputEncoding = THREE.sRGBEncoding;
  140. document.body.appendChild( renderer.domElement );
  141. //
  142. new OrbitControls( camera, renderer.domElement );
  143. }
  144. function onWindowResize() {
  145. camera.aspect = window.innerWidth / window.innerHeight;
  146. camera.updateProjectionMatrix();
  147. renderer.setSize( window.innerWidth, window.innerHeight );
  148. }
  149. function blurShadow( amount ) {
  150. blurPlane.visible = true;
  151. // blur horizontally and draw in the renderTargetBlur
  152. blurPlane.material = horizontalBlurMaterial;
  153. blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture;
  154. horizontalBlurMaterial.uniforms.h.value = amount * 1 / 256;
  155. renderer.setRenderTarget( renderTargetBlur );
  156. renderer.render( blurPlane, shadowCamera );
  157. // blur vertically and draw in the main renderTarget
  158. blurPlane.material = verticalBlurMaterial;
  159. blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture;
  160. verticalBlurMaterial.uniforms.v.value = amount * 1 / 256;
  161. renderer.setRenderTarget( renderTarget );
  162. renderer.render( blurPlane, shadowCamera );
  163. blurPlane.visible = false;
  164. }
  165. function animate( time ) {
  166. requestAnimationFrame( animate );
  167. //
  168. meshes.forEach( mesh => {
  169. mesh.rotation.x += 0.01;
  170. mesh.rotation.y += 0.02;
  171. } );
  172. //
  173. // remove the background and force the depthMaterial to everything
  174. var initialBackground = scene.background;
  175. scene.background = null;
  176. cameraHelper.visible = false;
  177. scene.overrideMaterial = depthMaterial;
  178. // render to the render target to get the depths
  179. renderer.setRenderTarget( renderTarget );
  180. renderer.render( scene, shadowCamera );
  181. // and reset the override material
  182. scene.overrideMaterial = null;
  183. cameraHelper.visible = true;
  184. blurShadow( state.blur );
  185. // a second pass to reduce the artifacts
  186. // (0.4 is the minimum blur amout so that the artifacts are gone)
  187. blurShadow( state.blur * 0.4 );
  188. // reset and render the normal scene
  189. renderer.setRenderTarget( null );
  190. scene.background = initialBackground;
  191. renderer.render( scene, camera );
  192. }
  193. </script>
  194. </body>
  195. </html>