<!DOCTYPE html> <html lang="en"> <head> <title>Interior Mapping Shadows Dev</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body { background-color: #fff; color: #bbbbff; margin: 0px; overflow: hidden; font-family: Monospace; font-size: 13px; } #info { position: absolute; top: 0px; width: 100%; padding: 5px; text-align: center; } </style> </head> <body> <div id="container"></div> <div id="info">Interior Mapping Shadows Dev. 'r' for object rotation. 'l' for light rotation. <a href="http://stackoverflow.com/q/22472455/3390365?stw=2" target="_blank">stackoverflow</a> / <a href="interior.html" target="_blank">page source</a> </div> <script src="http://www.vill.ee/eye/include/build/three.min.js"></script> <script src="http://www.vill.ee/eye/include/examples/js/controls/TrackballControls.js"></script> <script src="http://www.vill.ee/eye/include/examples/js/Detector.js"></script> <script type="x-shader/x-vertex" id="vertexShader"> varying vec3 oP; // surface position in object space varying vec3 oE; // position of the eye in object space varying vec3 oI; // incident ray direction in object space varying vec3 shad_E; // shadow light position varying vec3 shad_I; // shadow direction uniform vec3 camPos; uniform vec3 lightPosition; uniform mat4 lightProjMatrix; uniform mat4 lightViewMatrix; mat4 InverseMatrix( mat4 A ) { float s0 = A[0][0] * A[1][1] - A[1][0] * A[0][1]; float s1 = A[0][0] * A[1][2] - A[1][0] * A[0][2]; float s2 = A[0][0] * A[1][3] - A[1][0] * A[0][3]; float s3 = A[0][1] * A[1][2] - A[1][1] * A[0][2]; float s4 = A[0][1] * A[1][3] - A[1][1] * A[0][3]; float s5 = A[0][2] * A[1][3] - A[1][2] * A[0][3]; float c5 = A[2][2] * A[3][3] - A[3][2] * A[2][3]; float c4 = A[2][1] * A[3][3] - A[3][1] * A[2][3]; float c3 = A[2][1] * A[3][2] - A[3][1] * A[2][2]; float c2 = A[2][0] * A[3][3] - A[3][0] * A[2][3]; float c1 = A[2][0] * A[3][2] - A[3][0] * A[2][2]; float c0 = A[2][0] * A[3][1] - A[3][0] * A[2][1]; float invdet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0); mat4 B; B[0][0] = ( A[1][1] * c5 - A[1][2] * c4 + A[1][3] * c3) * invdet; B[0][1] = (-A[0][1] * c5 + A[0][2] * c4 - A[0][3] * c3) * invdet; B[0][2] = ( A[3][1] * s5 - A[3][2] * s4 + A[3][3] * s3) * invdet; B[0][3] = (-A[2][1] * s5 + A[2][2] * s4 - A[2][3] * s3) * invdet; B[1][0] = (-A[1][0] * c5 + A[1][2] * c2 - A[1][3] * c1) * invdet; B[1][1] = ( A[0][0] * c5 - A[0][2] * c2 + A[0][3] * c1) * invdet; B[1][2] = (-A[3][0] * s5 + A[3][2] * s2 - A[3][3] * s1) * invdet; B[1][3] = ( A[2][0] * s5 - A[2][2] * s2 + A[2][3] * s1) * invdet; B[2][0] = ( A[1][0] * c4 - A[1][1] * c2 + A[1][3] * c0) * invdet; B[2][1] = (-A[0][0] * c4 + A[0][1] * c2 - A[0][3] * c0) * invdet; B[2][2] = ( A[3][0] * s4 - A[3][1] * s2 + A[3][3] * s0) * invdet; B[2][3] = (-A[2][0] * s4 + A[2][1] * s2 - A[2][3] * s0) * invdet; B[3][0] = (-A[1][0] * c3 + A[1][1] * c1 - A[1][2] * c0) * invdet; B[3][1] = ( A[0][0] * c3 - A[0][1] * c1 + A[0][2] * c0) * invdet; B[3][2] = (-A[3][0] * s3 + A[3][1] * s1 - A[3][2] * s0) * invdet; B[3][3] = ( A[2][0] * s3 - A[2][1] * s1 + A[2][2] * s0) * invdet; return B; } void main() { // available matrices to vertex shader in three.js /* modelMatrix; modelViewMatrix; projectionMatrix; viewMatrix; normalMatrix; */ mat4 modelViewMatrixInverse = InverseMatrix( modelViewMatrix ); // surface position in object space oP = position; // position of the eye in object space oE = modelViewMatrixInverse[3].xyz; // incident ray direction in object space oI = oP - oE; // shadow map calculation process in three.js /* 1. setFromMatrixPosition: x = m.elements[ 12 ], y = m.elements[ 13 ], z = m.elements[ 14 ]. (same as modelMatrix[3].xyz ? ) 2. shadowCamera.position.setFromMatrixPosition( light.matrixWorld ); 3. _matrixPosition.setFromMatrixPosition( light.target.matrixWorld ); 4. shadowCamera.lookAt( _matrixPosition ); 5. shadowCamera.updateMatrixWorld(); 6. shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld ); 7. shadowMatrix.multiply( shadowCamera.projectionMatrix ); 8. shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); 9. vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition; 10. vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w; 11. texture2D( shadowMap[ i ], shadowCoord.xy */ // link the light position to camera for testing // need to find a way for world space directional light to work shad_E = oE - lightPosition; // light vector shad_I = position - shad_E; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } </script> <script type="x-shader/x-fragment" id="fragmentShader"> varying vec3 oP; // surface position in object space varying vec3 oE; // position of the eye in object space varying vec3 oI; // incident ray direction in object space varying vec3 shad_E; // shadow light position varying vec3 shad_I; // shadow direction uniform vec3 wallFreq; uniform float wallsBias; uniform vec3 wallCeilingColor; uniform vec3 wallFloorColor; uniform vec3 wallXYColor; uniform vec3 wallZYColor; float checker(vec2 uv, float checkSize) { float fmodResult = mod( floor(checkSize * uv.x) + floor(checkSize * uv.y), 2.0); if (fmodResult < 1.0) { return 1.0; } else { return 0.85; } } void main() { // INTERIOR MAPPING by Joost van Dongen // http://interiormapping.oogst3d.net/ // email: joost@ronimo-games.com // Twitter: @JoostDevBlog vec3 wallFrequencies = wallFreq / 2.0 - wallsBias; //calculate wall locations vec3 walls = ( floor( oP * wallFrequencies) + step( vec3( 0.0 ), oI )) / wallFrequencies; //how much of the ray is needed to get from the oE to each of the walls vec3 rayFractions = ( walls - oE) / oI; //texture-coordinates of intersections vec2 intersectionXY = (oE + rayFractions.z * oI).xy; vec2 intersectionXZ = (oE + rayFractions.y * oI).xz; vec2 intersectionZY = (oE + rayFractions.x * oI).zy; //use the intersection as the texture coordinates for the ceiling vec3 ceilingColour = wallCeilingColor * checker( intersectionXZ, 2.0 ); vec3 floorColour = wallFloorColor * checker( intersectionXZ, 2.0 ); vec3 verticalColour = mix(floorColour, ceilingColour, step(0.0, oI.y)); vec3 wallXYColour = wallXYColor * checker( intersectionXY, 2.0 ); vec3 wallZYColour = wallZYColor * checker( intersectionZY, 2.0 ); // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // vec3 shad_P = oP; // just surface position in object space vec3 shad_walls = ( floor( shad_P * wallFrequencies) + step( vec3( 0.0 ), shad_I )) / wallFrequencies; vec3 shad_rayFr = ( shad_walls - shad_E ) / shad_I; // Cast shadow from ceiling planes (intersectionXZ) wallZYColour *= mix( 0.3, 1.0, step( shad_rayFr[0], shad_rayFr[1] )); verticalColour *= mix( 0.3, 1.0, step( rayFractions[1], shad_rayFr[1] )); wallXYColour *= mix( 0.3, 1.0, step( shad_rayFr[2], shad_rayFr[1] )); // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // SHADOWS DEV // // intersect walls float xVSz = step(rayFractions.x, rayFractions.z); vec3 interiorColour = mix(wallXYColour, wallZYColour, xVSz); float rayFraction_xVSz = mix(rayFractions.z, rayFractions.x, xVSz); float xzVSy = step(rayFraction_xVSz, rayFractions.y); interiorColour = mix(verticalColour, interiorColour, xzVSy); gl_FragColor.xyz = interiorColour; } </script> <script> if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); var camera, scene, renderer, time, light, lightCam, material, cube; var lightMatrix = new THREE.Matrix4(); init(); animate(); function init() { var container = document.getElementById( 'container' ); camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 5000 ); camera.position.set( -40, 30, 200 ); scene = new THREE.Scene(); controls = new THREE.TrackballControls( camera ); controls.rotateSpeed = 1.0; controls.zoomSpeed = 1.2; controls.panSpeed = 0.8; controls.noZoom = false; controls.noPan = false; controls.staticMoving = true; controls.dynamicDampingFactor = 0.15; light = new THREE.SpotLight( 0xffffff ); light.position.set( 30, 100, 200 ); light.castShadow = true; light.shadowCameraVisible = true; light.shadowMapWidth = 256; light.shadowMapHeight = 256; light.shadowCameraNear = 180; light.shadowCameraFar = 300; light.shadowCameraFov = 20; scene.add( light ); // lightMatrix.set( 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0 ); // GROUND var groundGeo = new THREE.PlaneGeometry( 200, 200 ); var groundMat = new THREE.MeshPhongMaterial( { color: 0xffffff } ); var ground = new THREE.Mesh( groundGeo, groundMat ); ground.rotation.x = -Math.PI/2; ground.position.y = -20; ground.receiveShadow = true; scene.add( ground ); var vertexShader = document.getElementById( 'vertexShader' ).textContent; var fragmentShader = document.getElementById( 'fragmentShader' ).textContent; var uniforms = { wallFreq: { type: "v3", value: new THREE.Vector3( 0.3, 0.8, 0.3 ) }, wallsBias: { type: "f", value: 0.001 }, wallCeilingColor: { type: "v3", value: new THREE.Vector3( 0.8, 0.7, 0.1 ) }, wallFloorColor: { type: "v3", value: new THREE.Vector3( 0.1, 0.4, 0.8 ) }, wallXYColor: { type: "v3", value: new THREE.Vector3( 0.8, 0.2, 0.2 ) }, wallZYColor: { type: "v3", value: new THREE.Vector3( 0.2, 0.8, 0.1 ) }, camPos: { type: "v3", value: new THREE.Vector3( 0.0, 0.0, 0.0 )}, lightPosition: { type: "v3", value: new THREE.Vector3( 30.0, 60.0, -100.0 ) }, lightProjMatrix: { type: "m4v", value: [] }, lightViewMatrix: { type: "m4v", value: [] } } uniforms.lightViewMatrix.value = light.shadowMatrix; var mergedUniforms = THREE.UniformsUtils.merge( [THREE.ShaderLib["lambert"].uniforms, uniforms ] ); mergedUniforms = THREE.UniformsUtils.merge( [THREE.UniformsLib["common"], mergedUniforms ] ); var cubeGeo = new THREE.CubeGeometry( 40, 40, 40 ); material = new THREE.ShaderMaterial( { vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: mergedUniforms, lights:true } ); cube = new THREE.Mesh( cubeGeo, material ); cube.castShadow = true; scene.add( cube ); renderer = new THREE.WebGLRenderer( { antialias: false } ); renderer.shadowMapEnabled = true; renderer.shadowMapCullFace = THREE.CullFaceBack; renderer.setSize( window.innerWidth, window.innerHeight ); container.appendChild( renderer.domElement ); window.addEventListener( 'resize', onWindowResize, false ); document.addEventListener( 'keydown', onKeyDown, false ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function onKeyDown ( event ) { switch ( event.keyCode ) { case 82: /*r*/ cube.rotation.y += .01; break; case 76: /*l*/ light.position.x = Math.sin( time * 2 ) * 200; light.position.z = Math.cos( time * 2 ) * 200; break; } } function animate() { requestAnimationFrame( animate ); render(); } function render() { // 1. setFromMatrixPosition: x = m.elements[ 12 ], y = m.elements[ 13 ], z = m.elements[ 14 ] // 2. shadowCamera.position.setFromMatrixPosition( light.matrixWorld ); // 3. _matrixPosition.setFromMatrixPosition( light.target.matrixWorld ); // 4. shadowCamera.lookAt( _matrixPosition ); // 5. shadowCamera.updateMatrixWorld(); // 6. shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld ); // 7. shadowMatrix.multiply( shadowCamera.projectionMatrix ); // 8. shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); // 9. _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); // 10._frustum.setFromMatrix( _projScreenMatrix ); lightMatrix.multiplyMatrices( light.matrixWorld, cube.matrixWorld ); material.uniforms.lightViewMatrix.value = lightMatrix.getInverse( light.matrixWorld ); material.uniforms.lightPosition.value = light.position; material.uniforms.camPos.value = camera.position; time = - performance.now() * 0.0003; controls.update(); renderer.render( scene, camera ); } </script> </body> </html>