Shadow Mapping in O3D

来源:互联网 发布:手机剪裁图片软件 编辑:程序博客网 时间:2024/05/17 03:20

                       Shadow Mapping in O3D

 

 

给一个场景增加投影,可以极大程度改善3d的幻觉效果。Shadow mapping作为一种算法给许多硬件加速投影技术提供了基础。它通过两个通道渲染场景。1.从光的视角渲染场景来生成一个画面外的灰度图像,称作Shadow map(见下图)。每个像素的灰色投影代表了光和渲染点的距离。原则上,如果一个对象被光照亮,他应该在Shadow map前面被着色。像素着色器在第二个渲染通道中采样Shadow map来判定是否一个点在投影中,每个被渲染的点,着色器计算该点在Shadow map中的显示位置,在此处取样投影,然后比较在Shadow map中的编码距离和实际距离光的距离。如果点和光的距离比映射中的编码距离确实大了一点,这个点就被认为在投影中。

 

 


 

 

 


 

 

Shadow map被一个texture渲染并用于发光计算来产生场景中的投影效果。这个场景从光的视角被渲染。


使用Shadow map来渲染的转化图

 

渲染图

 

o3d中,这两个执行Shadow map的通道通过用户渲染图来产生。渲染图需要两个子树。一个用来给texture渲染Shadow map,另一个用来渲染场景。在Shadow map示例代码中,渲染图节点有两个子节点,每个都是子树的根节点。通过子树投影的根节点被赋予更低的优先权,这样它能够首先被跨越。在这之下,有一个renderSurfaceSet节点。这个renderSurfaceSet节点成为一个标准渲染图的根节点,并用o3djs.rendergraph.createBasicView()生成。第二个渲染通道(在代码中是指“color”通道)的字树通过调用o3djs.rendergraph.createBasicView()生成。每个通道有自己的DrawContext对象,这样投影通道的模型视图和映射矩阵可以按照光的视角来设置。下面的图形展示了这个例子中的渲染图结构。

这个例子中,当用户点击空格键,toggleView()方法重新布置渲染图来在屏幕中绘制投影。它通过重新连接renderSurfaceSet的投影通道子树并将其与渲染图根节点重新连接来实现,如下图。没有renderSurfaceSet在其上面,投影通道将会绘制在屏幕上而不是渲染texture

材料

在场景中的每个基元都有两个绘图元素,一个用Phong-shaded渲染(在第二个通道被投影的材料),另一个在灰色中渲染来生成Shadow map。第一个绘图元素当o3djs.primitives中的效用函数生成外形时被添加。

// 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);

 

As the shapes in the scene are added to the transform graph, they are each equipped with the DrawElement for the shadow pass.

 

transformTable[tt].shape.createDrawElements(g_pack, g_shadowMaterial);

 

着色器

当渲染场景给投影通道的每个拥有光视角色的点深度的像素着色时,回调材料方法会被使用。实现这个方法,着色器只要将其位置乘以光视角的视角-投射矩阵。效率方面,这个乘法在顶点着色程序中执行。如果这些插入的用来产生顶点着色程序输入的矩阵是同类时,这个方法效果会很好。

output.position = mul(input.position, worldViewProjection);
output.depth = output.position.zw;

In O3D, the z coordinate of the position in the light's clip-space ranges from 0 to 1, so the pixel program puts in the red, green, and blue channels to produce a shade of gray.

 

float t = input.depth.x / input.depth.y;
return float4(t, t, t, 1);

 

颜色通道的着色器是修改过的Phong shader。这个着色器计算叫做light的系数,这个光系数捕获当前被渲染的点是被照亮还是在阴影中。为了请求Shadow map,着色器需要一个texture sampler参数传给这个map。并且,为了能够计算sample的位置,它还需要给光的视角点一个视角-投射矩阵。

float4x4 lightViewProjection;
sampler shadowMapSampler;

 

 

而且,考虑到效率问题,顶点着色程序执行矩阵计算来转换成光的裁剪空间。

output.projTextureCoords = mul(input.position, worldLightViewProjection);

 

色素着色器通过除以w转换来自同类坐标的当前渲染点,将其变成字面上的坐标。然后,抽取在右边的texture,裁剪空间xy坐标,将其传化使其符合01的范围。

projCoords.xy /= projCoords.w;
projCoords.x = 0.5 * projCoords.x + 0.5;
projCoords.y = -0.5 * projCoords.y + 0.5;

 

 

最后,将当前点深度和在Shadow map中的深度比较来判断这个点是否被照亮。

float light = tex2D(shadowMapSampler, projCoords.xy).r + 0.008 > depth;

 

进一步优化

在基本的shadow map算法中有一些更加高级的变量可以提高阴影的质量。一个简单的修改可以使阴影没有锯齿。并且,渲染图可以被重构建来增加额外的速度。为了方便,渲染图的两个在示例代码中的子树可以使用效用函数o3djs.rendergraph.createBasicView()生成。但是这个函数会生成所有的节点,这些节点必须在屏幕上放些什么东西,但是在这两个子树中并不是所有的这些节点都是有用的。尤其树穿越只需要出现一次就可以了,因为使用特殊材料的元素只会增加到关联这些材料的绘图list上。我们的本意是给o3djs增加更多的功能以便使其更加方便,但是因为几何算法的复杂性和想要的阴影效果是多种多样的,这样提供一种能够解决所有情况的途径是很困难的。Shadow map sample的目标是提供一个开端。给一个场景增加阴影包含一个shadow 通道,模仿在sample中渲染图的结构,然后微调场景中的着色器得到所要求的效果。

 

 

         -------------AUTHOR: Leo Yu from Beyond Technology

 

 

原创粉丝点击