Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(192)

Side by Side Diff: samples/shadow-map.html

Issue 155401: Shadow Map Sample (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/o3d/
Patch Set: '' Created 11 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « samples/o3djs/debug.js ('k') | tests/selenium/sample_list.txt » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:executable
+ *
OLDNEW
(Empty)
1 <!--
2 Copyright 2009, Google Inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above
12 copyright notice, this list of conditions and the following disclaimer
13 in the documentation and/or other materials provided with the
14 distribution.
15 * Neither the name of Google Inc. nor the names of its
16 contributors may be used to endorse or promote products derived from
17 this software without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 -->
31
32 <!--
33 This sample uses a custom render graph to implement a basic shadow map
34 algorithm.
35
36 The technique works by rendering the scene in two passes. The first pass
37 renders the geometry in the scene with a shader that colors each pixel a shade
38 of gray representing how far the rendered point is from the light source. That
39 image, the shadow map, is rendered to a texture, and then the second (visible)
40 render pass samples it to determine which points in the scene are in shaodow.
41 -->
42 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
43 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
45 <head>
46 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
47 <title>
48 Shadow Mapping
49 </title>
50 <script type="text/javascript" src="o3djs/base.js"></script>
51 <script type="text/javascript">
52 o3djs.require('o3djs.util');
53 o3djs.require('o3djs.math');
54 o3djs.require('o3djs.rendergraph');
55 o3djs.require('o3djs.primitives');
56 o3djs.require('o3djs.effect');
57 o3djs.require('o3djs.debug');
58 o3djs.require('o3djs.material');
59
60 // The initClient() function runs when the page has finished loading.
61 window.onload = initClient;
62
63 // global variables
64 var g_o3dElement;
65 var g_client;
66 var g_o3d;
67 var g_math;
68 var g_pack;
69 var g_colorViewInfo;
70 var g_shadowViewInfo;
71 var g_shadowTexture;
72 var g_shadowMaterial;
73 var g_shadowColorEffect;
74 var g_shadowSampler;
75 var g_lightViewProjection;
76 var g_lightFrustumTransform;
77 var g_globalParams = { };
78 var g_viewFromLight = false;
79
80 var g_renderSurfaceSet;
81 var g_colorPassRenderRoot;
82
83 var g_lightWorldPos = [5, 10, 0];
84 var g_lightColor = [1, 1, 1, 1];
85 var g_eyePosition = [1, 6, 20];
86 var g_targetPosition = [0, 2, 0];
87
88 // constants.
89 var SHADOW_MAP_WIDTH = 512;
90 var SHADOW_MAP_HEIGHT = 512;
91
92 var g_finished = false; // for selenium testing.
93
94
95 /**
96 * Creates the client area.
97 */
98 function initClient() {
99 o3djs.util.makeClients(main, 'FloatingPointTextures');
100 }
101
102
103 /**
104 * Initializes global variables, positions camera, draws shapes.
105 * @param {Array} clientElements Array of o3d object elements.
106 */
107 function main(clientElements) {
108 // Init global variables.
109 initGlobals(clientElements);
110
111 // Set up the rendergraph.
112 initRenderGraph();
113
114 // Load effects, bind material parameters.
115 initMaterials();
116
117 // Add the shapes to the transform graph.
118 createShapes();
119
120 // Set up the view and projection transformations for the camera.
121 updateCamera();
122
123 // Init global parameters. initGlobalParams() searches all materials in order
124 // to bind parameters, so it must be called after initMaterials()
125 initGlobalParams();
126
127 // Set the view and projection transformations for the light.
128 updateLightMatrix();
129
130 // Create the light that gets drawn.
131 createLightShape();
132
133 // Execute keyPressed() when we detect a keypress on the window or
134 // on the o3d object.
135 window.document.onkeypress = keyPressed;
136 g_o3dElement.onkeypress = keyPressed;
137
138 g_finished = true; // for selenium testing.
139 }
140
141
142 /**
143 * Initializes global variables and libraries.
144 */
145 function initGlobals(clientElements) {
146 g_o3dElement = clientElements[0];
147 g_client = g_o3dElement.client;
148 g_o3d = g_o3dElement.o3d;
149 g_math = o3djs.math;
150
151 // Create a pack to manage the objects created.
152 g_pack = g_client.createPack();
153 }
154
155
156 /**
157 * Sets up the render graph. Builds a basic view for the camera and the light
158 * point of view, arranges for the view from the light to be rendered to a
159 * texture for the shadow map. Unlike the basic render graph created by the
160 * the utility function o3djs.rendergraph.createBasicView, to render the shadow
161 * map and then render the scene, we need two subtrees of the render graph, one
162 * for shadow map render pass and one to draw the scene.
163 */
164 function initRenderGraph() {
165 // Create the texture that will store the depth information.
166 g_shadowTexture = g_pack.createTexture2D(SHADOW_MAP_WIDTH,
167 SHADOW_MAP_HEIGHT,
168 g_o3d.Texture.ABGR32F,
169 1,
170 true);
171 var renderSurface = g_shadowTexture.getRenderSurface(0, g_pack);
172
173 // Create the depth-stencil buffer required when rendering the teapot.
174 var depthSurface = g_pack.createDepthStencilSurface(SHADOW_MAP_WIDTH,
175 SHADOW_MAP_HEIGHT);
176
177 // The children of any one node in the render graph get traversed in order by
178 // priority. Here, we're forcing the shadow map to get rendered first by
179 // by giving its render root lower priority.
180 var shadowPassRenderRoot = g_pack.createObject('RenderNode');
181 shadowPassRenderRoot.priority = 0;
182
183 g_colorPassRenderRoot = g_pack.createObject('RenderNode');
184 g_colorPassRenderRoot.priority = 1;
185
186 shadowPassRenderRoot.parent = g_client.renderGraphRoot;
187 g_colorPassRenderRoot.parent = g_client.renderGraphRoot;
188
189 g_renderSurfaceSet = g_pack.createObject('RenderSurfaceSet');
190 g_renderSurfaceSet.renderSurface = renderSurface;
191 g_renderSurfaceSet.renderDepthStencilSurface = depthSurface;
192 g_renderSurfaceSet.parent = shadowPassRenderRoot;
193
194 // Create a render sub-graph for the shadow map generation.
195 g_shadowViewInfo = o3djs.rendergraph.createBasicView(
196 g_pack,
197 g_client.root,
198 g_renderSurfaceSet,
199 [1, 1, 1, 1]);
200
201 // Create a render sub-graph for the regular pass.
202 g_colorViewInfo = o3djs.rendergraph.createBasicView(
203 g_pack,
204 g_client.root,
205 g_colorPassRenderRoot,
206 [0, 0, 0, 1]);
207 }
208
209
210 /**
211 * Switches between the camera and light point of view.
212 */
213 function toggleView() {
214 if (g_viewFromLight) {
215 g_shadowViewInfo.root.parent = g_renderSurfaceSet;
216 g_colorPassRenderRoot.parent = g_client.renderGraphRoot;
217 g_viewFromLight = false;
218 } else {
219 g_shadowViewInfo.root.parent = g_client.renderGraphRoot;
220 g_colorPassRenderRoot.parent = null;
221 g_viewFromLight = true;
222 }
223 }
224
225 /**
226 * Creates a material to be put on all shapes in the scene for the shadow pass,
227 * and loads effects for materials in the scene. Other materials are created
228 * on the fly as the shapes are created.
229 */
230 function initMaterials() {
231 g_shadowMaterial = g_pack.createObject('Material');
232 g_shadowMaterial.drawList = g_shadowViewInfo.performanceDrawList;
233
234 var shadowEffect = g_pack.createObject('Effect');
235 var shadowEffectString = document.getElementById('shadowShader').text;
236 shadowEffect.loadFromFXString(shadowEffectString);
237 g_shadowMaterial.effect = shadowEffect;
238 shadowEffect.createUniformParameters(g_shadowMaterial);
239
240 g_shadowColorEffect = g_pack.createObject('Effect');
241 var colorEffectString = document.getElementById('shadowColorShader').text;
242 g_shadowColorEffect.loadFromFXString(colorEffectString);
243
244 g_shadowSampler = g_pack.createObject('Sampler');
245 g_shadowSampler.texture = g_shadowTexture;
246 g_shadowSampler.minFilter = g_o3d.Sampler.POINT;
247 g_shadowSampler.magFilter = g_o3d.Sampler.POINT;
248 g_shadowSampler.mipFilter = g_o3d.Sampler.POINT;
249 g_shadowSampler.addressModeU = g_o3d.Sampler.BORDER;
250 g_shadowSampler.addressModeV = g_o3d.Sampler.BORDER;
251 g_shadowSampler.borderColor = [1, 1, 1, 1];
252 }
253
254
255 /**
256 * Sets up reasonable view and projection matrices.
257 */
258 function updateCamera() {
259 // Set up a perspective transformation for the projection.
260 g_colorViewInfo.drawContext.projection = g_math.matrix4.perspective(
261 g_math.degToRad(30), // 30 degree frustum.
262 g_o3dElement.clientWidth / g_o3dElement.clientHeight, // Aspect ratio.
263 1, // Near plane.
264 5000); // Far plane.
265
266 // Set up our view transformation to look towards the world origin where the
267 // cube is located.
268 g_colorViewInfo.drawContext.view = g_math.matrix4.lookAt(
269 g_eyePosition, // eye
270 g_targetPosition, // target
271 [0, 1, 0]); // up
272 }
273
274
275 /**
276 * Computes the view and projection matrices from the point of view of the
277 * light. Sets the lightViewProjection parameter so the color shader can access
278 * it.
279 */
280 function updateLightMatrix() {
281 // The perspective projection matrix for the light.
282 var lightProjection = g_math.matrix4.perspective(
283 g_math.degToRad(45), // 45 degree fov.
284 SHADOW_MAP_WIDTH / SHADOW_MAP_HEIGHT, // Aspect ratio.
285 4, // Near plane.
286 20); // Far plane.
287
288 // Make the light point toward the origin
289 var lightView = g_math.matrix4.lookAt(
290 g_lightWorldPos, // light
291 [0, 0, 0], // target
292 [1, 0, 0]); // up
293
294 g_lightViewProjection = g_math.matrix4.composition(
295 lightProjection, lightView);
296
297 g_shadowViewInfo.drawContext.projection = lightProjection;
298 g_shadowViewInfo.drawContext.view = lightView;
299
300 g_globalParams.lightViewProjection.value = g_lightViewProjection;
301 }
302
303
304 /**
305 * Creates shapes using the primitives utility library, and adds them to the
306 * transform graph at the root node.
307 */
308 function createShapes() {
309 // A green phong-shaded material for the cube.
310 var cubeMaterial = createShadowColorMaterial([0.2, 0.5, 0, 1]);
311
312 // The cube shape.
313 var cube = o3djs.primitives.createCube(
314 g_pack,
315 cubeMaterial,
316 2); // The length of each side of the cube.
317
318 // A red phong-shaded material for the sphere.
319 var sphereMaterial = createShadowColorMaterial([0.7, 0.2, 0.1, 1]);
320
321 // The sphere shape.
322 var sphere = o3djs.primitives.createSphere(
323 g_pack, sphereMaterial, 0.5, 50, 50);
324
325 // A blue phong-shaded material for the plane.
326 var planeMaterial = createShadowColorMaterial([0, 0.3, 0.5, 1]);
327
328 // The plane shape.
329 var plane = o3djs.primitives.createPlane(
330 g_pack,
331 planeMaterial,
332 20, // Width.
333 20, // Depth.
334 1, // Horizontal subdivisions.
335 1); // Vertical subdivisions.
336
337 // Associate to each shape, a translation vector.
338 var transformTable = [
339 {shape: cube, translation: [0, 1, 0]},
340 {shape: sphere, translation: [0.5, 2.5, 0]},
341 {shape: plane, translation: [0, 0, 0]}
342 ];
343
344 // Add the shapes to the transform graph with the translation.
345 var modelRoot = g_pack.createObject('Transform');
346 modelRoot.parent = g_client.root;
347 for (var tt = 0; tt < transformTable.length; ++tt) {
348 var transform = g_pack.createObject('Transform');
349 transform.addShape(transformTable[tt].shape);
350 // The shadow material is bound to a DrawList in the subtree of the
351 // rendergraph that handles the shadow map generation, so it gets drawn in
352 // that render pass only.
353 transformTable[tt].shape.createDrawElements(g_pack, g_shadowMaterial);
354
355 transform.translate(transformTable[tt].translation);
356 transform.parent = modelRoot;
357 }
358 }
359
360
361 /**
362 * Creates the wireframe frustum showing the shadow map's render volume.
363 */
364 function createLightShape() {
365 var inverseMatrix = g_math.matrix4.inverse(g_lightViewProjection);
366
367 // Scale and translate a cube of side length 2 to get a box
368 // that extends from [-1, -1, 0] to [1, 1, 1].
369 var shape = o3djs.debug.createLineCube(
370 g_pack,
371 o3djs.material.createConstantMaterial(g_pack,
372 g_colorViewInfo,
373 [1, 0, 0, 1]),
374 2,
375 g_math.matrix4.compose(
376 g_math.matrix4.translation([0, 0, 0.5]),
377 g_math.matrix4.scaling([1, 1, 0.5])));
378
379 g_lightFrustumTransform = g_pack.createObject('Transform');
380 g_lightFrustumTransform.localMatrix = inverseMatrix;
381 g_lightFrustumTransform.parent = g_client.root;
382 g_lightFrustumTransform.addShape(shape);
383 }
384
385
386 /**
387 * Creates a Phong-shaded, shadowed material based on the given color.
388 */
389 function createShadowColorMaterial(baseColor) {
390 var material = g_pack.createObject('Material');
391 material.drawList = g_colorViewInfo.performanceDrawList;
392
393 material.effect = g_shadowColorEffect;
394 g_shadowColorEffect.createUniformParameters(material);
395
396 material.getParam('shadowMapSampler').value = g_shadowSampler;
397
398 material.getParam('ambient').value = g_math.mulScalarVector(0.1, baseColor);
399 material.getParam('diffuse').value = g_math.mulScalarVector(0.8, baseColor);
400 material.getParam('specular').value = [1, 1, 1, 1];
401 material.getParam('shininess').value = 80;
402
403 return material;
404 }
405
406 /**
407 * Binds params for light position, light color and the light view-projection
408 * matrix to all materials in the scene where they apply.
409 */
410 function initGlobalParams() {
411 var paramSpec = {
412 'lightColor': 'ParamFloat4',
413 'lightWorldPos': 'ParamFloat3',
414 'lightViewProjection': 'ParamMatrix4'};
415
416 g_globalParams = o3djs.material.createParams(g_pack, paramSpec);
417 o3djs.material.bindParams(g_pack, g_globalParams);
418
419 g_globalParams.lightWorldPos.value = g_lightWorldPos;
420 g_globalParams.lightColor.value = g_lightColor;
421 }
422
423
424 /**
425 * The keyboard event handler.
426 */
427 function keyPressed(event) {
428 var keyChar = String.fromCharCode(o3djs.event.getEventKeyChar(event));
429 keyChar = keyChar.toLowerCase();
430
431 var delta = 0.2;
432 switch(keyChar) {
433 case 'a':
434 moveLight([-delta, 0, 0]);
435 break;
436 case 'd':
437 moveLight([delta, 0, 0]);
438 break;
439 case 's':
440 moveLight([0, -delta, 0]);
441 break;
442 case 'w':
443 moveLight([0, delta, 0]);
444 break;
445 case 'i':
446 moveLight([0, 0, delta]);
447 break;
448 case 'o':
449 moveLight([0, 0, -delta]);
450 break;
451
452 case ' ':
453 toggleView();
454 break;
455 }
456 }
457
458
459 /**
460 * Moves the light by the given vector delta, and updates params so the light
461 * draws in the right spot and the shadows move.
462 */
463 function moveLight(delta) {
464 g_lightWorldPos = g_math.addVector(g_lightWorldPos, delta);
465 g_globalParams.lightWorldPos.value = g_lightWorldPos;
466 updateLightMatrix();
467 g_lightFrustumTransform.localMatrix =
468 g_math.matrix4.inverse(g_lightViewProjection);
469 }
470
471
472 </script>
473 <script id="shadowShader" type="text/O3DShader">
474 /**
475 * This shader is for the effect applied in the first render pass, when the
476 * shadow map is created. The scene is rendered from the perspective of the
477 * light, the grayscale value of each pixel in the rendered image represents
478 * how far away the rendered point is from the light (the lighter, the
479 * farther) This image gets rendered to a texture, and that texture gets
480 * sampled in the second render pass, when the geometry is drawn to the
481 * screen.
482 */
483
484 // The light's wvp matrix
485 float4x4 worldViewProjection : WorldViewProjection;
486
487 // Input parameters for our vertex shader.
488 struct VertexShaderInput {
489 float4 position : POSITION;
490 };
491
492 // Input parameters for our pixel shader.
493 struct PixelShaderInput {
494 float4 position : POSITION;
495 float2 depth : TEXCOORD0;
496 };
497
498 /**
499 * The vertex shader simply transforms the input vertices to screen space.
500 */
501 PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
502 PixelShaderInput output;
503 // Render from the light's perspective.
504 output.position = mul(input.position, worldViewProjection);
505 output.depth = output.position.zw;
506 return output;
507 }
508
509 /**
510 * The pixel shader returns a shade of gray. The lighter the shade the
511 * farther that fragment is from the light.
512 */
513 float4 pixelShaderFunction(PixelShaderInput input): COLOR {
514 // Pixels in the shadowmap store the pixel depth from the light's
515 // perspective in normalized device coordinates.
516 return float4(input.depth.x / input.depth.y);
517 }
518
519 // #o3d VertexShaderEntryPoint vertexShaderFunction
520 // #o3d PixelShaderEntryPoint pixelShaderFunction
521 // #o3d MatrixLoadOrder RowMajor
522 </script>
523
524
525 <script id="shadowColorShader" type="text/O3DShader">
526 /**
527 * This shader is for the effect applied in the second render pass when the
528 * shadowed shapes are drawn to the screen. In the pixel shader, the distance
529 * from the rendered point to the camera is compared to the distance encoded
530 * in the shadow map. If the distance is much greater, the rendered point is
531 * considered to be in shadow and is given a light coefficient of 0.
532 */
533
534 float4x4 world : World;
535 float4x4 worldViewProjection : WorldViewProjection;
536 float4x4 worldInverseTranspose : WorldInverseTranspose;
537 float4x4 viewInverse : ViewInverse;
538 float4x4 lightViewProjection;
539 sampler shadowMapSampler;
540
541 // Parameters for the phong shader.
542 uniform float3 lightWorldPos;
543 uniform float4 lightColor;
544 uniform float4 ambient;
545 uniform float4 diffuse;
546 uniform float4 specular;
547 uniform float shininess;
548
549 // input parameters for our vertex shader
550 struct VertexShaderInput {
551 float4 position : POSITION;
552 float3 normal : NORMAL;
553 };
554
555 // input parameters for our pixel shader
556 struct PixelShaderInput {
557 float4 position : POSITION;
558 float4 projTextureCoords : TEXCOORD0;
559 float4 worldPosition : TEXCOORD1;
560 float3 normal : TEXCOORD2;
561 };
562
563 PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
564 PixelShaderInput output;
565
566 // Transform to homogeneous clip space.
567 output.position = mul(input.position, worldViewProjection);
568
569 // Compute the projective texture coordinates to project the shadow map
570 // onto the scene.
571 float4x4 worldLightViewProjection = mul(world, lightViewProjection);
572 output.projTextureCoords = mul(input.position, worldLightViewProjection);
573 output.worldPosition = mul(input.position, world);
574 output.normal = mul(float4(input.normal, 0), worldInverseTranspose).xyz;
575
576 return output;
577 }
578
579 float4 pixelShaderFunction(PixelShaderInput input): COLOR {
580 float3 surfaceToLight = normalize(lightWorldPos - input.worldPosition);
581 float3 surfaceToView = normalize(viewInverse[3].xyz - input.worldPosition);
582 float3 normal = normalize(input.normal);
583 float3 halfVector = normalize(surfaceToLight + surfaceToView);
584 float4 litResult = lit(dot(normal, surfaceToLight),
585 dot(normal, halfVector), shininess);
586 float4 outColor = ambient;
587 float4 projCoords = input.projTextureCoords;
588
589 // Convert texture coords to [0, 1] range.
590 projCoords.xy /= projCoords.w;
591 projCoords.x = 0.5 * projCoords.x + 0.5;
592 projCoords.y = -0.5 * projCoords.y + 0.5;
593
594 // Compute the pixel depth for shadowing.
595 float depth = projCoords.z / projCoords.w;
596
597 // If the rednered point is farter from the light than the distance encoded
598 // in the shadow map, we give it a light coefficient of 0.
599 float light = tex2D(shadowMapSampler, projCoords.xy).r + 0.008 > depth;
600
601 // Make the illuninated area a round spotlight shape just for fun.
602 // Comment this line out to see just the shadows.
603 light *= 1 - smoothstep(0.45, 0.5, length(projCoords - float2(0.5, 0.5)));
604
605 outColor += light * lightColor *
606 (diffuse * litResult.y + specular * litResult.z);
607 return outColor;
608 }
609
610 // #o3d VertexShaderEntryPoint vertexShaderFunction
611 // #o3d PixelShaderEntryPoint pixelShaderFunction
612 // #o3d MatrixLoadOrder RowMajor
613 </script>
614
615
616 </head>
617 <body>
618 <h1>Shadow Maps</h1>
619 This sample implements a basic shadow map.
620 <br/>
621 <!-- Start of O3D plugin -->
622 <div id="o3d" style="width: 600px; height: 600px;"></div>
623 <!-- End of O3D plugin -->
624 Use A, S, D, W, I and O to move the light.
625 Press spacebar to see the shadow map.
626 </body>
627 </html>
OLDNEW
« no previous file with comments | « samples/o3djs/debug.js ('k') | tests/selenium/sample_list.txt » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698