图形学 - 平面阴影技术 DirectX列子
平面阴影虽然不能满足所有的阴影要求,但是如果是相对简单的场景的话,用平面阴影也是个不错的选择,它的优点是速度快,并不需占用多少运算时间。步骤如下:
1. 定位需要渲染阴影的物体,和阴影所在平面
2. 计算出平面上阴影的物体定点,定义一定透明度,如50%透明度
3. 渲染
一、首先解决问题是如何计算方法就是利用向量代数和空间解析几何的知识,知道了光照的方向向量,物体定点,和平面位置,就可以计算了。这里就不给出具体的计算了,基础不好的恶补下,这里主要讲图形学的技术,而且大多数的API,如DirectX和OpenGL都有现场的API去自动计算,记得目的就是为了定义出在平面上的阴影的定点。图形学中一般的做法就是先计算出这个转变向量,然后利用这个转变向量,把原物体转换成阴影物体,就可以把阴影渲染在平面上了。
而在DirectX中就是可以调用现成的API生成所需要的矩阵:
D3DXMATRIX *D3DXMatrixShadow( D3DXMATRIX *pOut, //我们所需要的矩阵 CONST D3DXVECTOR4 *pLight, // 光源 CONST D3DXPLANE *pPlane // 阴影所在的平面);
我们画阴影需要再一个平面上画的,但是原图形是立体的,那么会发生多个几何图形重叠的情况,比如一个物体的正反面重叠子一起,那么通过多次画图就会使得阴影物体更加黑。为了得到正确明暗程度的阴影就要用到Stencil技术了。当然如果不用也可以画出来,不过就是阴影部分更加黑罢了。
具体做法就是:我们把阴影部分标记到stencil buffer中,然后如果第二次再次写像素到同样的阴影部分的时候就拒绝写入,也就是stencil test会失败。(当然程序还是正常运行的)。这样的话,我们就只画了一次阴影部分。
gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);//开启Stencil bufferHR(gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);//设置stencil测试函数为等于函数HR(gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x0);//设置ref的值为0HR(gd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);//Mask蒙蔽层的值为1HR(gd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);//写入蒙蔽层也为1HR(gd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);//stencil测试失败则保持原有的颜色HR(gd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);HR(gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);//这要设置,已经写入一次的像素位置就不能再次写入其他像素了
// 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;//这里进行正式转换
// Alpha blend the shadow.gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);//开启Alpha blendinggd3dDevice->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 的 龙书 封面是条红龙的)