<!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>