summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--o3d/DEPS2
-rw-r--r--o3d/core/cross/gl/renderer_gl.cc5
-rw-r--r--o3d/samples/MANIFEST1
-rw-r--r--o3d/samples/o3djs/debug.js24
-rwxr-xr-xo3d/samples/shadow-map.html627
-rw-r--r--o3d/tests/selenium/sample_list.txt1
6 files changed, 655 insertions, 5 deletions
diff --git a/o3d/DEPS b/o3d/DEPS
index b1c2625..1964999 100644
--- a/o3d/DEPS
+++ b/o3d/DEPS
@@ -2,7 +2,7 @@ vars = {
"chromium_trunk":
"http://src.chromium.org/svn/trunk",
"nixysa_rev": "28",
- "o3d_code_rev": "95",
+ "o3d_code_rev": "96",
}
deps = {
diff --git a/o3d/core/cross/gl/renderer_gl.cc b/o3d/core/cross/gl/renderer_gl.cc
index 8638607..dbd34fd 100644
--- a/o3d/core/cross/gl/renderer_gl.cc
+++ b/o3d/core/cross/gl/renderer_gl.cc
@@ -1567,6 +1567,11 @@ bool RendererGL::SaveScreen(const String& file_name) {
MakeCurrentLazy();
Bitmap::Ref bitmap = Bitmap::Ref(new Bitmap(service_locator()));
bitmap->Allocate(Texture::ARGB8, width(), height(), 1, false);
+
+ // Note: glReadPixels captures the alpha component of the frame buffer as well
+ // as the color components, the browser usually ignores the alpha channel when
+ // drawing to the screen, so unless the alpha is 1, the png image generated
+ // might exhibit suprise translucency.
::glReadPixels(0, 0, width(), height(), GL_BGRA, GL_UNSIGNED_BYTE,
bitmap->image_data());
bool result = bitmap->SaveToPNGFile((file_name + ".png").c_str());
diff --git a/o3d/samples/MANIFEST b/o3d/samples/MANIFEST
index ef3a61c..72145b8 100644
--- a/o3d/samples/MANIFEST
+++ b/o3d/samples/MANIFEST
@@ -283,6 +283,7 @@ shaders/texture-only.shader
shaders/toon.shader
shaders/vertex-color.shader
shaders/yuv2rgb.shader
+shadow-map.html
simpleviewer/assets/cube.o3dtgz
simpleviewer/simpleviewer.html
trends/assets/clouds.jpg
diff --git a/o3d/samples/o3djs/debug.js b/o3d/samples/o3djs/debug.js
index 9589c50..2f4be54 100644
--- a/o3d/samples/o3djs/debug.js
+++ b/o3d/samples/o3djs/debug.js
@@ -263,6 +263,25 @@ o3djs.debug.VertexInfo.prototype.Offset = {
};
/**
+ * Computes the number of vertices in this vertex info.
+ * @return {number} The number of vertices.
+ */
+o3djs.debug.VertexInfo.prototype.numVertices = function() {
+ return this.vertices.length / 3;
+};
+
+
+/**
+ * Given the number of a vertex returns the index in the array where
+ * the coordinates of that vertex vertexIndex.
+ * @param {number} vertexNumber The vertex number.
+ * @return {number} The index where that vertex begins.
+ */
+o3djs.debug.VertexInfo.prototype.vertexIndex = function(vertexNumber) {
+ return vertexNumber * 3;
+}
+
+/**
* Adds a vertex.
* @param {number} positionX The x position of the vertex.
* @param {number} positionY The y position of the vertex.
@@ -298,6 +317,7 @@ o3djs.debug.VertexInfo.prototype.createShape = function(
this.indices);
};
+
/**
* Reorients the vertex positions of this vertexInfo by the
* given matrix. In other words it multiplies each vertex by the
@@ -306,10 +326,6 @@ o3djs.debug.VertexInfo.prototype.createShape = function(
*/
o3djs.debug.VertexInfo.prototype.reorient = function(matrix) {
var math = o3djs.math;
- // Assume if it has a length it's not a Matrix4
- if (matrix.length) {
- matrix = math.matrix4.copy(matrix);
- }
var numVerts = this.numVertices();
for (var v = 0; v < numVerts; ++v) {
var index = this.vertexIndex(v);
diff --git a/o3d/samples/shadow-map.html b/o3d/samples/shadow-map.html
new file mode 100755
index 0000000..a7bb6e9
--- /dev/null
+++ b/o3d/samples/shadow-map.html
@@ -0,0 +1,627 @@
+<!--
+Copyright 2009, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<!--
+This sample uses a custom render graph to implement a basic shadow map
+algorithm.
+
+The technique works by rendering the scene in two passes. The first pass
+renders the geometry in the scene with a shader that colors each pixel a shade
+of gray representing how far the rendered point is from the light source. That
+image, the shadow map, is rendered to a texture, and then the second (visible)
+render pass samples it to determine which points in the scene are in shaodow.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>
+Shadow Mapping
+</title>
+<script type="text/javascript" src="o3djs/base.js"></script>
+<script type="text/javascript">
+o3djs.require('o3djs.util');
+o3djs.require('o3djs.math');
+o3djs.require('o3djs.rendergraph');
+o3djs.require('o3djs.primitives');
+o3djs.require('o3djs.effect');
+o3djs.require('o3djs.debug');
+o3djs.require('o3djs.material');
+
+// The initClient() function runs when the page has finished loading.
+window.onload = initClient;
+
+// global variables
+var g_o3dElement;
+var g_client;
+var g_o3d;
+var g_math;
+var g_pack;
+var g_colorViewInfo;
+var g_shadowViewInfo;
+var g_shadowTexture;
+var g_shadowMaterial;
+var g_shadowColorEffect;
+var g_shadowSampler;
+var g_lightViewProjection;
+var g_lightFrustumTransform;
+var g_globalParams = { };
+var g_viewFromLight = false;
+
+var g_renderSurfaceSet;
+var g_colorPassRenderRoot;
+
+var g_lightWorldPos = [5, 10, 0];
+var g_lightColor = [1, 1, 1, 1];
+var g_eyePosition = [1, 6, 20];
+var g_targetPosition = [0, 2, 0];
+
+// constants.
+var SHADOW_MAP_WIDTH = 512;
+var SHADOW_MAP_HEIGHT = 512;
+
+var g_finished = false; // for selenium testing.
+
+
+/**
+ * Creates the client area.
+ */
+function initClient() {
+ o3djs.util.makeClients(main, 'FloatingPointTextures');
+}
+
+
+/**
+ * Initializes global variables, positions camera, draws shapes.
+ * @param {Array} clientElements Array of o3d object elements.
+ */
+function main(clientElements) {
+ // Init global variables.
+ initGlobals(clientElements);
+
+ // Set up the rendergraph.
+ initRenderGraph();
+
+ // Load effects, bind material parameters.
+ initMaterials();
+
+ // Add the shapes to the transform graph.
+ createShapes();
+
+ // Set up the view and projection transformations for the camera.
+ updateCamera();
+
+ // Init global parameters. initGlobalParams() searches all materials in order
+ // to bind parameters, so it must be called after initMaterials()
+ initGlobalParams();
+
+ // Set the view and projection transformations for the light.
+ updateLightMatrix();
+
+ // Create the light that gets drawn.
+ createLightShape();
+
+ // Execute keyPressed() when we detect a keypress on the window or
+ // on the o3d object.
+ window.document.onkeypress = keyPressed;
+ g_o3dElement.onkeypress = keyPressed;
+
+ g_finished = true; // for selenium testing.
+}
+
+
+/**
+ * Initializes global variables and libraries.
+ */
+function initGlobals(clientElements) {
+ g_o3dElement = clientElements[0];
+ g_client = g_o3dElement.client;
+ g_o3d = g_o3dElement.o3d;
+ g_math = o3djs.math;
+
+ // Create a pack to manage the objects created.
+ g_pack = g_client.createPack();
+}
+
+
+/**
+ * Sets up the render graph. Builds a basic view for the camera and the light
+ * point of view, arranges for the view from the light to be rendered to a
+ * texture for the shadow map. Unlike the basic render graph created by the
+ * the utility function o3djs.rendergraph.createBasicView, to render the shadow
+ * map and then render the scene, we need two subtrees of the render graph, one
+ * for shadow map render pass and one to draw the scene.
+ */
+function initRenderGraph() {
+ // Create the texture that will store the depth information.
+ g_shadowTexture = g_pack.createTexture2D(SHADOW_MAP_WIDTH,
+ SHADOW_MAP_HEIGHT,
+ g_o3d.Texture.ABGR32F,
+ 1,
+ true);
+ var renderSurface = g_shadowTexture.getRenderSurface(0, g_pack);
+
+ // Create the depth-stencil buffer required when rendering the teapot.
+ var depthSurface = g_pack.createDepthStencilSurface(SHADOW_MAP_WIDTH,
+ SHADOW_MAP_HEIGHT);
+
+ // The children of any one node in the render graph get traversed in order by
+ // priority. Here, we're forcing the shadow map to get rendered first by
+ // by giving its render root lower priority.
+ var shadowPassRenderRoot = g_pack.createObject('RenderNode');
+ shadowPassRenderRoot.priority = 0;
+
+ g_colorPassRenderRoot = g_pack.createObject('RenderNode');
+ g_colorPassRenderRoot.priority = 1;
+
+ shadowPassRenderRoot.parent = g_client.renderGraphRoot;
+ g_colorPassRenderRoot.parent = g_client.renderGraphRoot;
+
+ g_renderSurfaceSet = g_pack.createObject('RenderSurfaceSet');
+ g_renderSurfaceSet.renderSurface = renderSurface;
+ g_renderSurfaceSet.renderDepthStencilSurface = depthSurface;
+ g_renderSurfaceSet.parent = shadowPassRenderRoot;
+
+ // Create a render sub-graph for the shadow map generation.
+ g_shadowViewInfo = o3djs.rendergraph.createBasicView(
+ g_pack,
+ g_client.root,
+ g_renderSurfaceSet,
+ [1, 1, 1, 1]);
+
+ // Create a render sub-graph for the regular pass.
+ g_colorViewInfo = o3djs.rendergraph.createBasicView(
+ g_pack,
+ g_client.root,
+ g_colorPassRenderRoot,
+ [0, 0, 0, 1]);
+}
+
+
+/**
+ * Switches between the camera and light point of view.
+ */
+function toggleView() {
+ if (g_viewFromLight) {
+ g_shadowViewInfo.root.parent = g_renderSurfaceSet;
+ g_colorPassRenderRoot.parent = g_client.renderGraphRoot;
+ g_viewFromLight = false;
+ } else {
+ g_shadowViewInfo.root.parent = g_client.renderGraphRoot;
+ g_colorPassRenderRoot.parent = null;
+ g_viewFromLight = true;
+ }
+}
+
+/**
+ * Creates a material to be put on all shapes in the scene for the shadow pass,
+ * and loads effects for materials in the scene. Other materials are created
+ * on the fly as the shapes are created.
+ */
+function initMaterials() {
+ g_shadowMaterial = g_pack.createObject('Material');
+ g_shadowMaterial.drawList = g_shadowViewInfo.performanceDrawList;
+
+ var shadowEffect = g_pack.createObject('Effect');
+ var shadowEffectString = document.getElementById('shadowShader').text;
+ shadowEffect.loadFromFXString(shadowEffectString);
+ g_shadowMaterial.effect = shadowEffect;
+ shadowEffect.createUniformParameters(g_shadowMaterial);
+
+ g_shadowColorEffect = g_pack.createObject('Effect');
+ var colorEffectString = document.getElementById('shadowColorShader').text;
+ g_shadowColorEffect.loadFromFXString(colorEffectString);
+
+ g_shadowSampler = g_pack.createObject('Sampler');
+ g_shadowSampler.texture = g_shadowTexture;
+ g_shadowSampler.minFilter = g_o3d.Sampler.POINT;
+ g_shadowSampler.magFilter = g_o3d.Sampler.POINT;
+ g_shadowSampler.mipFilter = g_o3d.Sampler.POINT;
+ g_shadowSampler.addressModeU = g_o3d.Sampler.BORDER;
+ g_shadowSampler.addressModeV = g_o3d.Sampler.BORDER;
+ g_shadowSampler.borderColor = [1, 1, 1, 1];
+}
+
+
+/**
+ * Sets up reasonable view and projection matrices.
+ */
+function updateCamera() {
+ // Set up a perspective transformation for the projection.
+ g_colorViewInfo.drawContext.projection = g_math.matrix4.perspective(
+ g_math.degToRad(30), // 30 degree frustum.
+ g_o3dElement.clientWidth / g_o3dElement.clientHeight, // Aspect ratio.
+ 1, // Near plane.
+ 5000); // Far plane.
+
+ // Set up our view transformation to look towards the world origin where the
+ // cube is located.
+ g_colorViewInfo.drawContext.view = g_math.matrix4.lookAt(
+ g_eyePosition, // eye
+ g_targetPosition, // target
+ [0, 1, 0]); // up
+}
+
+
+/**
+ * Computes the view and projection matrices from the point of view of the
+ * light. Sets the lightViewProjection parameter so the color shader can access
+ * it.
+ */
+function updateLightMatrix() {
+ // The perspective projection matrix for the light.
+ var lightProjection = g_math.matrix4.perspective(
+ g_math.degToRad(45), // 45 degree fov.
+ SHADOW_MAP_WIDTH / SHADOW_MAP_HEIGHT, // Aspect ratio.
+ 4, // Near plane.
+ 20); // Far plane.
+
+ // Make the light point toward the origin
+ var lightView = g_math.matrix4.lookAt(
+ g_lightWorldPos, // light
+ [0, 0, 0], // target
+ [1, 0, 0]); // up
+
+ g_lightViewProjection = g_math.matrix4.composition(
+ lightProjection, lightView);
+
+ g_shadowViewInfo.drawContext.projection = lightProjection;
+ g_shadowViewInfo.drawContext.view = lightView;
+
+ g_globalParams.lightViewProjection.value = g_lightViewProjection;
+}
+
+
+/**
+ * Creates shapes using the primitives utility library, and adds them to the
+ * transform graph at the root node.
+ */
+function createShapes() {
+ // A green phong-shaded material for the cube.
+ var cubeMaterial = createShadowColorMaterial([0.2, 0.5, 0, 1]);
+
+ // The cube shape.
+ var cube = o3djs.primitives.createCube(
+ g_pack,
+ cubeMaterial,
+ 2); // The length of each side of the cube.
+
+ // A red phong-shaded material for the sphere.
+ var sphereMaterial = createShadowColorMaterial([0.7, 0.2, 0.1, 1]);
+
+ // The sphere shape.
+ var sphere = o3djs.primitives.createSphere(
+ g_pack, sphereMaterial, 0.5, 50, 50);
+
+ // A blue phong-shaded material for the plane.
+ var planeMaterial = createShadowColorMaterial([0, 0.3, 0.5, 1]);
+
+ // The plane shape.
+ var plane = o3djs.primitives.createPlane(
+ g_pack,
+ planeMaterial,
+ 20, // Width.
+ 20, // Depth.
+ 1, // Horizontal subdivisions.
+ 1); // Vertical subdivisions.
+
+ // Associate to each shape, a translation vector.
+ var transformTable = [
+ {shape: cube, translation: [0, 1, 0]},
+ {shape: sphere, translation: [0.5, 2.5, 0]},
+ {shape: plane, translation: [0, 0, 0]}
+ ];
+
+ // Add the shapes to the transform graph with the translation.
+ var modelRoot = g_pack.createObject('Transform');
+ modelRoot.parent = g_client.root;
+ for (var tt = 0; tt < transformTable.length; ++tt) {
+ var transform = g_pack.createObject('Transform');
+ transform.addShape(transformTable[tt].shape);
+ // The shadow material is bound to a DrawList in the subtree of the
+ // rendergraph that handles the shadow map generation, so it gets drawn in
+ // that render pass only.
+ transformTable[tt].shape.createDrawElements(g_pack, g_shadowMaterial);
+
+ transform.translate(transformTable[tt].translation);
+ transform.parent = modelRoot;
+ }
+}
+
+
+/**
+ * Creates the wireframe frustum showing the shadow map's render volume.
+ */
+function createLightShape() {
+ var inverseMatrix = g_math.matrix4.inverse(g_lightViewProjection);
+
+ // Scale and translate a cube of side length 2 to get a box
+ // that extends from [-1, -1, 0] to [1, 1, 1].
+ var shape = o3djs.debug.createLineCube(
+ g_pack,
+ o3djs.material.createConstantMaterial(g_pack,
+ g_colorViewInfo,
+ [1, 0, 0, 1]),
+ 2,
+ g_math.matrix4.compose(
+ g_math.matrix4.translation([0, 0, 0.5]),
+ g_math.matrix4.scaling([1, 1, 0.5])));
+
+ g_lightFrustumTransform = g_pack.createObject('Transform');
+ g_lightFrustumTransform.localMatrix = inverseMatrix;
+ g_lightFrustumTransform.parent = g_client.root;
+ g_lightFrustumTransform.addShape(shape);
+}
+
+
+/**
+ * Creates a Phong-shaded, shadowed material based on the given color.
+ */
+function createShadowColorMaterial(baseColor) {
+ var material = g_pack.createObject('Material');
+ material.drawList = g_colorViewInfo.performanceDrawList;
+
+ material.effect = g_shadowColorEffect;
+ g_shadowColorEffect.createUniformParameters(material);
+
+ material.getParam('shadowMapSampler').value = g_shadowSampler;
+
+ material.getParam('ambient').value = g_math.mulScalarVector(0.1, baseColor);
+ material.getParam('diffuse').value = g_math.mulScalarVector(0.8, baseColor);
+ material.getParam('specular').value = [1, 1, 1, 1];
+ material.getParam('shininess').value = 80;
+
+ return material;
+}
+
+/**
+ * Binds params for light position, light color and the light view-projection
+ * matrix to all materials in the scene where they apply.
+ */
+function initGlobalParams() {
+ var paramSpec = {
+ 'lightColor': 'ParamFloat4',
+ 'lightWorldPos': 'ParamFloat3',
+ 'lightViewProjection': 'ParamMatrix4'};
+
+ g_globalParams = o3djs.material.createParams(g_pack, paramSpec);
+ o3djs.material.bindParams(g_pack, g_globalParams);
+
+ g_globalParams.lightWorldPos.value = g_lightWorldPos;
+ g_globalParams.lightColor.value = g_lightColor;
+}
+
+
+/**
+ * The keyboard event handler.
+ */
+function keyPressed(event) {
+ var keyChar = String.fromCharCode(o3djs.event.getEventKeyChar(event));
+ keyChar = keyChar.toLowerCase();
+
+ var delta = 0.2;
+ switch(keyChar) {
+ case 'a':
+ moveLight([-delta, 0, 0]);
+ break;
+ case 'd':
+ moveLight([delta, 0, 0]);
+ break;
+ case 's':
+ moveLight([0, -delta, 0]);
+ break;
+ case 'w':
+ moveLight([0, delta, 0]);
+ break;
+ case 'i':
+ moveLight([0, 0, delta]);
+ break;
+ case 'o':
+ moveLight([0, 0, -delta]);
+ break;
+
+ case ' ':
+ toggleView();
+ break;
+ }
+}
+
+
+/**
+ * Moves the light by the given vector delta, and updates params so the light
+ * draws in the right spot and the shadows move.
+ */
+function moveLight(delta) {
+ g_lightWorldPos = g_math.addVector(g_lightWorldPos, delta);
+ g_globalParams.lightWorldPos.value = g_lightWorldPos;
+ updateLightMatrix();
+ g_lightFrustumTransform.localMatrix =
+ g_math.matrix4.inverse(g_lightViewProjection);
+}
+
+
+</script>
+<script id="shadowShader" type="text/O3DShader">
+ /**
+ * This shader is for the effect applied in the first render pass, when the
+ * shadow map is created. The scene is rendered from the perspective of the
+ * light, the grayscale value of each pixel in the rendered image represents
+ * how far away the rendered point is from the light (the lighter, the
+ * farther) This image gets rendered to a texture, and that texture gets
+ * sampled in the second render pass, when the geometry is drawn to the
+ * screen.
+ */
+
+ // The light's wvp matrix
+ float4x4 worldViewProjection : WorldViewProjection;
+
+ // Input parameters for our vertex shader.
+ struct VertexShaderInput {
+ float4 position : POSITION;
+ };
+
+ // Input parameters for our pixel shader.
+ struct PixelShaderInput {
+ float4 position : POSITION;
+ float2 depth : TEXCOORD0;
+ };
+
+ /**
+ * The vertex shader simply transforms the input vertices to screen space.
+ */
+ PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
+ PixelShaderInput output;
+ // Render from the light's perspective.
+ output.position = mul(input.position, worldViewProjection);
+ output.depth = output.position.zw;
+ return output;
+ }
+
+ /**
+ * The pixel shader returns a shade of gray. The lighter the shade the
+ * farther that fragment is from the light.
+ */
+ float4 pixelShaderFunction(PixelShaderInput input): COLOR {
+ // Pixels in the shadowmap store the pixel depth from the light's
+ // perspective in normalized device coordinates.
+ return float4(input.depth.x / input.depth.y);
+ }
+
+ // #o3d VertexShaderEntryPoint vertexShaderFunction
+ // #o3d PixelShaderEntryPoint pixelShaderFunction
+ // #o3d MatrixLoadOrder RowMajor
+</script>
+
+
+<script id="shadowColorShader" type="text/O3DShader">
+ /**
+ * This shader is for the effect applied in the second render pass when the
+ * shadowed shapes are drawn to the screen. In the pixel shader, the distance
+ * from the rendered point to the camera is compared to the distance encoded
+ * in the shadow map. If the distance is much greater, the rendered point is
+ * considered to be in shadow and is given a light coefficient of 0.
+ */
+
+ float4x4 world : World;
+ float4x4 worldViewProjection : WorldViewProjection;
+ float4x4 worldInverseTranspose : WorldInverseTranspose;
+ float4x4 viewInverse : ViewInverse;
+ float4x4 lightViewProjection;
+ sampler shadowMapSampler;
+
+ // Parameters for the phong shader.
+ uniform float3 lightWorldPos;
+ uniform float4 lightColor;
+ uniform float4 ambient;
+ uniform float4 diffuse;
+ uniform float4 specular;
+ uniform float shininess;
+
+ // input parameters for our vertex shader
+ struct VertexShaderInput {
+ float4 position : POSITION;
+ float3 normal : NORMAL;
+ };
+
+ // input parameters for our pixel shader
+ struct PixelShaderInput {
+ float4 position : POSITION;
+ float4 projTextureCoords : TEXCOORD0;
+ float4 worldPosition : TEXCOORD1;
+ float3 normal : TEXCOORD2;
+ };
+
+ PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
+ PixelShaderInput output;
+
+ // Transform to homogeneous clip space.
+ output.position = mul(input.position, worldViewProjection);
+
+ // Compute the projective texture coordinates to project the shadow map
+ // onto the scene.
+ float4x4 worldLightViewProjection = mul(world, lightViewProjection);
+ output.projTextureCoords = mul(input.position, worldLightViewProjection);
+ output.worldPosition = mul(input.position, world);
+ output.normal = mul(float4(input.normal, 0), worldInverseTranspose).xyz;
+
+ return output;
+ }
+
+ float4 pixelShaderFunction(PixelShaderInput input): COLOR {
+ float3 surfaceToLight = normalize(lightWorldPos - input.worldPosition);
+ float3 surfaceToView = normalize(viewInverse[3].xyz - input.worldPosition);
+ float3 normal = normalize(input.normal);
+ float3 halfVector = normalize(surfaceToLight + surfaceToView);
+ float4 litResult = lit(dot(normal, surfaceToLight),
+ dot(normal, halfVector), shininess);
+ float4 outColor = ambient;
+ float4 projCoords = input.projTextureCoords;
+
+ // Convert texture coords to [0, 1] range.
+ projCoords.xy /= projCoords.w;
+ projCoords.x = 0.5 * projCoords.x + 0.5;
+ projCoords.y = -0.5 * projCoords.y + 0.5;
+
+ // Compute the pixel depth for shadowing.
+ float depth = projCoords.z / projCoords.w;
+
+ // If the rednered point is farter from the light than the distance encoded
+ // in the shadow map, we give it a light coefficient of 0.
+ float light = tex2D(shadowMapSampler, projCoords.xy).r + 0.008 > depth;
+
+ // Make the illuninated area a round spotlight shape just for fun.
+ // Comment this line out to see just the shadows.
+ light *= 1 - smoothstep(0.45, 0.5, length(projCoords - float2(0.5, 0.5)));
+
+ outColor += light * lightColor *
+ (diffuse * litResult.y + specular * litResult.z);
+ return outColor;
+ }
+
+ // #o3d VertexShaderEntryPoint vertexShaderFunction
+ // #o3d PixelShaderEntryPoint pixelShaderFunction
+ // #o3d MatrixLoadOrder RowMajor
+</script>
+
+
+</head>
+<body>
+<h1>Shadow Maps</h1>
+This sample implements a basic shadow map.
+<br/>
+<!-- Start of O3D plugin -->
+<div id="o3d" style="width: 600px; height: 600px;"></div>
+<!-- End of O3D plugin -->
+Use A, S, D, W, I and O to move the light.
+Press spacebar to see the shadow map.
+</body>
+</html>
diff --git a/o3d/tests/selenium/sample_list.txt b/o3d/tests/selenium/sample_list.txt
index de0f665..6a8dc5d 100644
--- a/o3d/tests/selenium/sample_list.txt
+++ b/o3d/tests/selenium/sample_list.txt
@@ -101,6 +101,7 @@ medium simpletexture screenshot pdiff_threshold(5100)
medium skinning screenshot pdiff_threshold(500)
medium sobel screenshot pdiff_threshold(1400)
medium stencil_example screenshot(0) screenshot(100) screenshot(7777) pdiff_threshold(4800) pdiff_threshold_win(20800)
+medium shadow-map screenshot pdiff_threshold(10000)
small texturesamplers screenshot pdiff_threshold(32200) pdiff_threshold_win(37100)
medium tutorial-primitive screenshot pdiff_threshold(1200) pdiff_threshold_mac(10400)
large vertex-shader screenshot timeout(45000) pdiff_threshold(1400) except(*iexplore)