筆記①提到,要計算泡沫數據需要臨近頂點的位移數據,因此為了避免重復計算頂點位移我們需要在正式渲染水面之前就先計算好頂點數據。
生成緩存
將水面修改為隨玩家移動而移動后,每一幀需要根據玩家當前位置和水面大小以及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);
可以注意到此時泡沫在移動方向背后留下的類似拖影的痕跡,使用混合的緩存可以一定程度模擬泡沫逐漸消散的過程。