图形学 - 平面影子技术 DirectX列子
图形学 - 平面阴影技术 DirectX列子
平面阴影虽然不能满足所有的阴影要求,但是如果是相对简单的场景的话,用平面阴影也是个不错的选择,它的优点是速度快,并不需占用多少运算时间。
步骤如下:
1. 定位需要渲染阴影的物体,和阴影所在平面
2. 计算出平面上阴影的物体定点,定义一定透明度,如50%透明度
3. 渲染
一、首先解决问题是如何计算
方法就是利用向量代数和空间解析几何的知识,知道了光照的方向向量,物体定点,和平面位置,就可以计算了。这里就不给出具体的计算了,基础不好的恶补下,这里主要讲图形学的技术,而且大多数的API,如DirectX和OpenGL都有现场的API去自动计算,记得目的就是为了定义出在平面上的阴影的定点。图形学中一般的做法就是先计算出这个转变向量,然后利用这个转变向量,把原物体转换成阴影物体,就可以把阴影渲染在平面上了。
而在DirectX中就是可以调用现成的API生成所需要的矩阵:
D3DXMATRIX *D3DXMatrixShadow( D3DXMATRIX *pOut, //我们所需要的矩阵 CONST D3DXVECTOR4 *pLight, // 光源 CONST D3DXPLANE *pPlane // 阴影所在的平面 );
利用Stencil 缓冲技术来防止多重Blending
我们画阴影需要再一个平面上画的,但是原图形是立体的,那么会发生多个几何图形重叠的情况,比如一个物体的正反面重叠子一起,那么通过多次画图就会使得阴影物体更加黑。为了得到正确明暗程度的阴影就要用到Stencil技术了。当然如果不用也可以画出来,不过就是阴影部分更加黑罢了。
具体做法就是:我们把阴影部分标记到stencil buffer中,然后如果第二次再次写像素到同样的阴影部分的时候就拒绝写入,也就是stencil test会失败。(当然程序还是正常运行的)。这样的话,我们就只画了一次阴影部分。
Direct3D代码示例:
1.参数设置
gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);//开启Stencil buffer HR(gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);//设置stencil测试函数为等于函数 HR(gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x0);//设置ref的值为0 HR(gd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);//Mask蒙蔽层的值为1 HR(gd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);//写入蒙蔽层也为1 HR(gd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);//stencil测试失败则保持原有的颜色 HR(gd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); HR(gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);//这要设置,已经写入一次的像素位置就不能再次写入其他像素了
2. 计算和定位阴影
// Position shadow. D3DXVECTOR4 lightDirection(0.577f, -0.577f, 0.577f, 0.0f); D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f); D3DXMATRIX S; D3DXMatrixShadow(&S, &lightDirection, &groundPlane);//这里得出阴影的转换矩阵 // Offset the shadow up slightly so that there is no // z-fighting with the shadow and ground. D3DXMATRIX eps; D3DXMatrixTranslation(&eps, 0.0f, 0.001f, 0.0f);//这里是Z-fighting技术,简单的来说就是当两个物体(这里是指阴影和平面)的深度测试(depth testing)发生重复的时候,就可能会出问题,例如像素会闪烁,这个时候,可以稍微把其中一个物体(这里是阴影)的位置移一点点。但是据我的实验可知,问题不会很大,还是可以正常显示的,无闪烁,不过为了安全起见,那么移动一下吧。 // Save the original teapot world matrix. D3DXMATRIX oldTeapotWorld = mTeapotWorld; // Add shadow projection transform. mTeapotWorld = mTeapotWorld * S * eps;//这里进行正式转换
3. 最后我们设置颜色,黑色,透明度50%,然后恢复原来的参数设置:
// Alpha blend the shadow. gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);//开启Alpha blending gd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA)); gd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); drawTeapot();//这里是画一个茶壶 // Restore settings. mTeapotWorld = oldTeapotWorld; gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, false);
总结:
这里是平面阴影技术,如果要做复杂的阴影的话,比如水面上的,和乱石中的阴影那就要用更加深的技术了,需要更多的知识,后续在继续贴出来吧。
Reference:
Introduction to 3D Game Programming with DirectX9.0C Shader Approach (DirectX 的 龙书 封面是条红龙的)