对于实时渲染的水,看了很多文章后,大体思路如下:
(一)水波的实现
1、创建一个由四个顶点组成的矩形网格。
2、将两张法线纹理平铺在该网格上。
3、通过实时分别变动两张纹理的uv坐标,产生水流和水波的效果。
(二)倒影的实现
1、将场景中的模型相对于水面镜像后渲染到一张纹理上(Render Mirror Scene to a texture )。
2、将纹理投影到水面上,然后将水面原有的颜色和纹理颜色适当的混合。
相信大家对于水波的实现并不困难,很多教程都有。但是对于倒影的实现,虽然有很多教程也讲述了,但是在实现的过程中,仍会出现很多细节问题,稍有偏差结果大不相同。相信大家对于基本原理应经很清楚,但是在这我仅把个人在实现过程中所碰到的相当恶心的问题讲述一下,以用来遇到类似问题的朋友们尽快解决。
先详细讲一下倒影实现的具体步骤:
1.设物体的世界矩阵为 worldMatrix,
摄像机的观察矩阵为 viewMatrix,
摄像机的投影矩阵为 proMatrix,
接下来要将物体镜像,很简单的做法就是将worldMatrix乘以一个镜像矩阵。
镜像矩阵可由现成的函数生成:
D3DXPLANE waterPlane;
D3DXPlaneFromPoints(&waterPlane,
&D3DXVECTOR3(1.f,10.f,1.f),
&D3DXVECTOR3(2.f,10.f,1.f),
&D3DXVECTOR3(1.5f,10.f,2.f));
D3DXMatrixReflect(&reflectMatrix,&waterPlane);
最后得到的镜像矩阵如下:
1 0 0 0
0 -1 0 0
0 0 1 0
0 2h 0 1
h为水面的高度!
所以与其用上面复杂的代码创建镜像矩阵,不如直接自己赋值得到:
D3DXMATRIX reflectMatrix;
D3DXMatrixIdentity(&reflectMatrix);
reflectMatrix._22 = -1.f;
reflectMatrix._42 =20.f;
最后简单的将世界矩阵与其相乘,然后将所谓的WVP矩阵(World*View*Projection)传入shader就万事大吉了~
worldMatrix*= reflectMatrix;
综上所述最终需要传入shader的矩阵为 WVP = reflectMatrix*worldMatrix*viewMatrix*ProMatrix;
2.到此我们得到了渲染有镜像世界的texture。这一步我们把它投射到眼前的水面上,而不是简单的将它贴在上面,原因自己想想就明白了。方法网上有说是用投影矩阵的,投影矩阵的数学原理很简单,类似于顶点标准管线流程的处理,只不过普通管线处理得到的坐标范围是在屏幕上的[-1,1]范围内,而我们要得到的投影uv坐标要在[0,1]范围内,所以只需将WVP矩阵处理后的顶点变换一下范围即可:
[-1,1]*0.5+0.5 = [0,1]
所以投影矩阵(projectiveMatrix)的内容自然如下:
0.5 0 0 0.5
0 0.5 0 0.5
0 0 0.5 0.5
0 0 0 1
将 PWVP = projectiveMatrix*worldMatrix*viewMatrix*ProMatrix;
传入shader中的gPWVP变量后,
在shader中投影矩阵的纹理坐标获取如下:
float3 proTex = mul(float4(posL, 1.0f), gPWVP);
float4 reflectColor = tex2Dproj(ReflectMap, proTex );
其实这样做我没有得到正确的结果,原因就错在投影矩阵的处理上。对于tex2Dproj函数的内部实现我在看了一篇文章后有所启发,便绕过了这一不知存在什么错误的繁杂处理,直接在ps中将投影uv 坐标搞定。
(那篇文章:http://www.cnblogs.com/wangze/archive/2010/04/07/1706640.html)
处理如下:
vs中:
outVS.posH = mul(float4(posL, 1.0f), gWVP);
outVS.texProj = outVS.posH;
ps中:
float2 texf = float2(texProj.x,1-texProj.y)/texProj.w*0.5+0.5+ (normalT*0.01f);
float3 reflectedColor = tex2D(EnvMapS,texf);
这里的gWVP为普通worldMatrix*viewMatrix*proMatrix得到,normalT为两张法线纹理取样均值。其中的1-texProj.y是我经过修改后得到的,也可能是texProj.y,无需“1-”,可能与我的水面网格绘制顺序有关。
3.平面剪裁。
当渲染镜像场景时会出现意想不到的问题,试想将陆地翻过来,如果你的视角正好在陆地里面,那岂不是会把其他物体遮挡住?所以我们要做的就是将水面以上的镜像物体全部剔除掉。比较简单的方法就是user cliping plane。
如果是管线渲染则很简单:
pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, D3DCLIPPLANE0 );
pd3dDevice-> SetClipPlane( 0, (float*)clipPlane);
...
RenderScene();
...
pd3dDevice-> SetRenderState( D3DRS_CLIPPLANEENABLE, 0x00 );
shader渲染时有点麻烦:
D3DXMATRIX temp;
D3DXMatrixInverse( &temp, 0, &g_Camera->GetView() );
D3DXMatrixTranspose( &temp, &temp );
D3DXPlaneTransform(&clipPlane,&clipPlane,&temp);
D3DXMatrixInverse( &temp, 0, &g_Camera->GetProjection() );
D3DXMatrixTranspose( &temp, &temp );
D3DXPlaneTransform(&clipPlane,&clipPlane,&temp);
pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, D3DCLIPPLANE0 );
pd3dDevice-> SetClipPlane( 0, (float*)clipPlane);
...
RenderScene();
...
pd3dDevice-> SetRenderState( D3DRS_CLIPPLANEENABLE, 0x00 );
具体原理不说了,太晚了,最后是效果图: