参考教程
学习记录
这一篇文章中,主要介绍水的实现,本篇更新后使用的框架如下:

水面的渲染比较麻烦,首先纹理是靠着反射与折射实现,我们使用 LightShaderClass 来处理光照,RefractionShaderClass 处理折射相关的着色,WaterShaderClass 则负责综合各种效果实现水的渲染。
首先看 LightClass 和 LightShaderClass ,LightClass 只是用来做一个简单的数据封装,如下:
1 | //////////////////////////////////////////////////////////////////////////////// |
LightShaderClass 以 ShaderClass 为基础封装了 LightShader 的处理,声明如下:
1 | //////////////////////////////////////////////////////////////////////////////// |
DX 代码里只是多了一个 LightBufferType ,而光照的计算代码之前我们写过,这里暂时不贴出来了。需要的话可以去文末的源代码地址下载。
RefractionShaderClass 则是封装了折射效果的实现,其声明如下:
1 | //////////////////////////////////////////////////////////////////////////////// |
而 WaterShaderClass 则是实现使用多张纹理来渲染水的动作,同时为了模拟出动态的效果以及水面的波纹,我们首先需要一张水面的法线贴图。如下:

除了法线贴图以外,反射和折射的效果也将作为纹理来使用,WaterShaderClass 所对应的着色器 WaterVertexShader 中,我们有一个反射矩阵和三个纹理坐标,分别为他们计算变换后的值:
1 | ///////////// |
WaterPixelShader 中,我们使用反射纹理和折射纹理混合,最终可以渲染出这样的效果:

着色器代码如下:
1 | Texture2D reflectionTexture; |
但是水面并非为平面,所以我们需要一个变量来模仿波纹效果。我们使用法线向量来对纹理进行偏移,如下:
1 | // Sample the normal from the normal map texture. |
我们使用法线贴图读取法线,然后对折射和反射做一定程度上的偏移计算,使得水面出现一定的波纹效果。效果如下:

和上图相比,由于折射和反射纹理的局部移位,我们的水面已经看起来很真实了,不过依旧可以改进,我们使用一个外部传进来的值来对法线贴图的纹理坐标进行计算以模拟水面的动态效果。
1 | input.tex.y += waterTranslation; |
这里就不截图了,反正截图也看不出来动态的效果。
最后,当我们修改完毕之后,我们的最终的 WaterPixelShader 如下:
1 | ///////////// |
我们有 waterTranslation ,reflectRefractScale 两个变量来对纹理坐标计算使得水面更加真实。在 WaterShaderClass 中我们对这两个着色器服务,声明如下:
1 | //////////////////////////////////////////////////////////////////////////////// |
在 GraphicsClass 中,我们实现将折射和反射渲染至纹理的方法,然后再用两张纹理和法线纹理来渲染水面,如下:
1 | bool GraphicsClass::RenderRefractionToTexture() |
在 Render 里调用:
1 | bool GraphicsClass::Render() |