Unity海洋shader笔记②

笔记①提到,要计算泡沫数据需要临近顶点的位移数据,因此为了避免重复计算顶点位移我们需要在正式渲染水面之前就先计算好顶点数据。

生成缓存

将水面修改为随玩家移动而移动后,每一帧需要根据玩家当前位置和水面大小以及LOD等级等数据计算顶点的位移。这里可以直接采用Graphics.Blit()生成一个RenderTexture,也可以采用其他的办法。我刚刚接触到一个很有趣的Compute Shader粒子小项目,虽然没有完全搞懂Compute Shader的原理,不过先可以学会如何运用起来。送入Buffer的是waveSampler结构体数组,结构体包含了预先在初始化中计算好的uv值和保存顶点位移的Vector3变量。接下来就是在Compute Shader中计算顶点位移,并把缓存数据传入GPU。

[numthreads(1024,1,1)]
void Update(uint3 id : SV_DispatchThreadID)
{
    float xPos = _playerLocation.x + ( - displacementSample[id.x].z + 0.5) * 600.0;
    float zPos = _playerLocation.z + (- displacementSample[id.x].x + 0.5) * 600.0;
    float height = tex2Dlod(_heightMap, float4(xPos/1200 +0.25,zPos/1200 + 0.25,0,0));
    float attenuationFac = 1;    
    attenuationFac = saturate(pow((1.0 - height),_ShoreWaveAttenuation));
    float3 worldPos = float3(xPos,0,zPos);
    displacementSample[id.x].displacement = VerticesDisplacement(worldPos, attenuationFac);
}
生成的位移缓存(XYZ→RGB可视化)

对于泡沫的计算我们读取到顶点位移后计算雅可比行列式即可,较小的J值会产生泡沫。

int x1 = floor(i.uv.x * (_resolution - 1)); int x2 = ceil(i.uv.x * (_resolution - 1));
int z1 = floor(i.uv.y * (_resolution - 1)); int z2 = ceil(i.uv.y * (_resolution - 1));
int index1 = x1 * _resolution + z1;
int index2 = x1 * _resolution + z2;
int index3 = x2 * _resolution + z1;
int index4 = x2 * _resolution + z2;
float dxdz = (displacementSample[index1].displacement.x - displacementSample[index2].displacement.x
              + displacementSample[index3].displacement.x - displacementSample[index4].displacement.x) * 0.5 * 0.782;
float dxdx = (displacementSample[index1].displacement.x - displacementSample[index3].displacement.x
              + displacementSample[index2].displacement.x - displacementSample[index4].displacement.x) * 0.5 * 0.782;               
float dzdx = (displacementSample[index1].displacement.z - displacementSample[index3].displacement.z
              + displacementSample[index2].displacement.z - displacementSample[index4].displacement.z) * 0.5 * 0.782;
float dzdz = (displacementSample[index1].displacement.z - displacementSample[index2].displacement.z
              + displacementSample[index3].displacement.z - displacementSample[index4].displacement.z) * 0.5 * 0.782;
float J = dxdx * dzdz - dxdz * dzdx;

我的想法是用当前帧缓存和上一帧的缓存叠加后得到最终的数据,于是在脚本Update()中我重新开了一个RTlastFoamTexture用于保存当前帧的混合得到数据,并在下一次Update()中用于下次混合。

Graphics.Blit(lastFoamTexture, foamTexture, foamMaterial);
Graphics.CopyTexture(foamTexture,lastFoamTexture);
return saturate((-J - 0.02) * 2) + lastFrameFoam * 0.99 - 0.005;

不过这里我注意到了一个问题,如果玩家此时移动速度较快的话混合得到的数据会出现类似“漂移”效果。(生成的图片灰度经过了一定的夸张处理)



原因其实很简单,是因为在新的帧中对上一帧的缓存采样时采用的是当前帧的uv,我们只需要根据玩家位置的变动大小对这个uv差进行补偿即可,改动后该现象消失。

uvOffset.x  = (lastFrameLocation - mainCam.transform.position).x/600;
uvOffset.y  = (lastFrameLocation - mainCam.transform.position).z/600;
foamMaterial.SetVector("_uvOffset",uvOffset);

可以注意到此时泡沫在移动方向背后留下的类似拖影的痕迹,使用混合的缓存可以一定程度模拟泡沫逐渐消散的过程。

泡沫渲染(待完成)


Github: https://github.com/techizgit/UnityPlayground

你可能感兴趣的:(Unity海洋shader笔记②)