[DirectX11]Gerstner波 实现简单的水面模拟

上一篇文章中,介绍了一个简单数值方法来模拟圆形扩散波的效果,但是这种方法对于自然中像海浪一样的波

就无能为力了。所以,这篇文章介绍用Gerstner波来模拟水面波纹效果。

一、Gerstner波介绍

Gerstner波是一种动态模拟海面幅度的方法,已有200多年的历史,后被用于计算机图形学。


首先,我们介绍一下几个常用的描述波函数的物理量,便于理解马上要介绍的Gerstner波方程。

A:amplitude 振幅,波相较于平衡位置的最大偏移量。

ω:角速度。

λ:波长

k:波数 (2π/λ)

K:波矢量(wavevector)其大小为波数,方向为波传播的方向,在3D波方程中,是一个2D向量

现在给出Gerstner波的方程,以参数方程的形式给出:


x=x0-(K/k)*Asin(K*x0-ωt)

y=Acos(K*x0-ωt)


肯定有朋友会问,明明是3D的波方程,为何只有两个变量xy。大家请注意,第一个式子中,加粗的部分,上面

已经介绍过,K是一个2D向量,方向表示在x-z平面波传播的方向,所以第一个加粗的部分(包括x,x0)都不是一个标量,而是一个二元量,表示的是在x-z平面上的具体位置,x=(x,z),x0=(x0,z0)。这样第一个式子得出的结果也自然是一个2D向量(K,x0点乘结果为一个标量)。

(x0,z0)又代表什么呢?这个坐标表示的是某一个顶点原本在x-z平面的位置。这里需要解释一下,总所周知,我

们模拟水面,经常是先在x-z平面上构建一个平面网格,然后修改每个顶点的y值,如下图:

Gerstner波会根据固定的(x0,z0)位置,计算出新的在x-z平面的位置(也就是第一个式子的结果,x)。Gerstner波

的特征是 波峰较窄,波谷较宽,这符合自然中水波的特征。而从第二个公式我们可以看出,y是一个余弦函数,波峰波谷是一样的,所以波峰窄,波谷宽的特征便是通过对(x0,z0)进行一定量偏移造成的,如下图:

[DirectX11]Gerstner波 实现简单的水面模拟_第1张图片

多个Gerstner波叠加

x=x0-∑(K/k)*Asin(K*x0-ωt)

y=∑Acos(K*x0-ωt)

多个波叠加也是非常好理解的,第一个公式叠加的是偏移量,所以x0不需要进行叠加。

注意

在选择Gerstner波的时候,是k,A是有一定限制的:

kA<1时,kA越大,波峰越窄。

kA>1时,会在波峰处产生环。如下图:

[DirectX11]Gerstner波 实现简单的水面模拟_第2张图片

图片来自:《Simulating Ocean Water》  Jerry Tessendorf 

二、Gerstner波的DirectX11实现

和上篇文章一样,我们同样适用DirectX11提供的Compute Shader进行并行计算。我们首先适用一个Buffer存放

网格数据,然后用一个RWStructuredBuffer存放计算后的数据。具体的HLSL代码片段如下:

StructuredBuffer gOriVertices;
RWStructuredBuffer gOutput;

[numthreads(N, N, 1)]
void csPos(int3 dispatchID:SV_DispatchThreadID)
{
	int x = dispatchID.x;
	int z = dispatchID.y;

	float x0 = gOriVertices[z*gWidth + x].pos.x;
	float z0 = gOriVertices[z*gWidth + x].pos.z;

	float2 offset = float2(0.0f, 0.0f);
	float height = 0;
	float k = 0;
	float p;
	[unroll]
	for (uint i = 0; i < gWaveNum; ++i)
	{
		float2 waveVector = float2(gWaveVectors[i * 2 + 0], gWaveVectors[i * 2 + 1]);
		p = dot(waveVector, float2(x0, z0)) - gOmegas[i] * gTime;

		offset += normalize(waveVector)*gAmplitudes[i] * sin(p);
		height += gAmplitudes[i] * cos(p);
	}
	gOutput[z*gWidth + x].pos = float3(x0-offset.x, height, z0-offset.y );


}
计算法线的HLSL片段:

[numthreads(N, N, 1)]
void csVector(int3 dispatchID:SV_DispatchThreadID)
{
	int x = dispatchID.x;
	int z = dispatchID.y;

	if (x == 0 || x == (gWidth - 1) || z == 0 || z == (gDepth - 1))
	{
		return;
	}

	float3 left = gOutput[z*gWidth + x - 1].pos;
		float3 right = gOutput[z*gWidth + x + 1].pos;
		float3 top = gOutput[(z - 1)*gWidth + x].pos;
		float3 bottom = gOutput[(z + 1)*gWidth + x].pos;

		float3 tangent = normalize(right - left);
		float3 binormal = normalize(bottom - top);
		gOutput[z*gWidth + x].normal = normalize(cross(tangent, binormal));
	gOutput[z*gWidth + x].tangent = tangent;
}


DirectX11部分的详细代码就不放出了,没有什么难度,对DirectX11不是很熟悉的朋友,可以参考 此书。Dx11的

经典入门书籍,有DirectX龙书之称。

三、小结

多个适当的Gerstner波叠加,能够制造出较为真实的水面,但是如果需要电影级的水面波纹效果,Gerstner波可

能力不从心了。所以这个时候,就是FFT(快速傅里叶变化)发挥作用的,可能下篇文章会介绍利用FFT实现水面波纹的模拟(本人数学基础不是很好。需要补补课。。)

最后附几张截图:

[DirectX11]Gerstner波 实现简单的水面模拟_第3张图片

[DirectX11]Gerstner波 实现简单的水面模拟_第4张图片

[DirectX11]Gerstner波 实现简单的水面模拟_第5张图片


简陋见解,不足之处,欢迎交流、拍砖。

参考资料:

Simulating Ocean Water》  Jerry Tessendorf 

JohnHany博客文:水面的简单渲染 – Gerstner波

================================The End==============================






















你可能感兴趣的:([DirectX11]Gerstner波 实现简单的水面模拟)