开始是想要做一个类似于poly bridge2的水面
找了一个类似的lowpoly风格的实现,就开始照着写了,顶点的高度是用的正弦波叠加的方式来实现的,可是到了法线计算的时候发现了一个坑,计算法线的时候需要用到geometry shader通过三角形三个顶点来计算法线
[maxvertexcount(3)]
void geom(triangle v2g input[3], inout TriangleStream outStream)
{
fixed3 normal=normalize(UnityObjectToWorldNormal(cross((input[1].vertex - input[0].vertex),(input[2].vertex-input[0].vertex))));
…
#pragma geometry geom
然后又是找了一下相关的实现,大致就三种方法计算法线
- 在 Geometry shader 里面计算
- 对于波浪算法一般都会提供法线的计算方式.
- 波浪放到 CPU 里面计算
第一个方法用不了
第二种的话如果不是lowpoly风格的话,正弦波水面是可以通过求偏导数来得到法线的,可是这样得到的水面法线是一个光滑的波浪函数的求导结果,同一个面上顶点的法线都不相同,完全不是lowpoly风格的水面了。
第三种用js来运算顶点高度和法线的做法也感觉不太合理。
那只能按自己的野路子来实现了,最直观的想法就是在vertex shader里有什么办法可以得到组成三角形的另外两个顶点的水面高度,那就可以计算法线了。
那我就用了个最简单粗暴的方法,首先水面模型是自己创建顶点数据生成的,组成水面的每个正方形包括两个三角形六个顶点,那其实最简单做法我可以通过传入一个值来区分这六个点,那每个点都可以算出另外两个点的位置了。(用六个顶点没有用四个顶点重复索引来实现,是因为虽然顶点的位置是一样但是法线不一样)
具体实现的方法是比如由六个顶点 (0,0,0),(0,0,1),(1,0,1)和(0,0,0),(1,0,1),(1,0,0)来组成的一个正方形

然后使用(0,0,1),(1,0,0),(-1,0,-1)和(1,0,1),(0,0,-1),(-1,0,0)六个值一一对应上面的顶点来计算组成三角形的另外两个点的位置
比如顶点(1,0,1)使用(-1,0,-1)来计算三角带上另外两个点的位置,
第一个点的计算方法就是直接相加x=x1+x2,z=z1+z2,得到(0,0,0)
第二个点是x=x1+z2 , z=z1+z2-x2得(1-1,0,1-1+1)也就是(0,0,1)
再例如顶点(0,0,0)使用(0,0,1)来计算,
另外两个点的计算就是(0+0,0,0+1)和(0+1,0,0+1-0),得到(0,0,1)和(1,0,1)
具体到shader里的写法类似是这样
vec3 v_position1 = vec3(v_position.x + In.normal.x, v_position.y, v_position.z + In.normal.z); vec3 v_position2 = vec3(v_position.x + In.normal.z, v_position.y, v_position.z + (In.normal.z- In.normal.x));
v_position.y += calcHeight(v_position, In.normal.y); v_position1.y += calcHeight(v_position1, In.normal.y); v_position2.y += calcHeight(v_position2, In.normal.y);
v_normal = normalize(cross(v_position1 - v_position, v_position2 - v_position));
这样虽然每个顶点的shader计算都多跑了两次另外两个顶点的正弦波高度计算,但是感觉可能效率是要比cpu计算顶点高度和法线效率要好的。
相对位置数据只用到了x和z的信息,然后之前的法线信息都是直接传入的(0,1,0)其实完全没用上,干脆组合起来当做法线信息传进去了。
最后效果差不多是这样的
lowpoly.rar (1.4 MB)



