FFT and game development (e)

FFT and game development (e)

Analog waves, can be understood as a pile of superimposed sine waves of arbitrary direction, the sine wave spectrum (amplitude and phase) can vary over time.

First put the results show:

By iFFT, the spectrum may be converted to a high degree field waves.

\[h(\overrightarrow x, t) = \sum_{\overrightarrow k} \tilde h (\overrightarrow k, t) e^{2\pi \overrightarrow k \cdot \overrightarrow x} \]

We have waves from the frequency domain to the time domain tool waves, so the waves in the frequency domain is how come it?

Philips spectrum (Phillips Spectrum)

Statistics by the ocean, the waves can get the spectrum changes as a function of time:

\[\tilde h(\overrightarrow k, t) = \tilde{h_0}(\overrightarrow k) e^{it\sqrt{gk}} + \tilde{h_0^*}(-\overrightarrow k) e^{-it\sqrt{gk}} \]

\ [\ Begin {aligned} where: & L is the distance between two neighboring spatial points \\ & k_x = \ frac {2 \ pi m} {L}, - \ frac {N} {2} \ leq m < \ frac {N} {2} \\ & k_z = \ frac {2 \ pi n} {L}, - \ frac {N} {2} \ leq n <\ frac {N} {2} \\ & \ overrightarrow k = (k_x, k_z) \\ & k = | \ overrightarrow k | \\ \\ addition: & \ tilde {h_0} (\ overrightarrow k) = \ frac {1} {\ sqrt 2} (\ xi_r + \ xi_i ) \ sqrt {P_h (\ overrightarrow k)} \\ & P_h (\ overrightarrow k) = A \ frac {e ^ {- \ frac {g ^ 2} {k ^ 2V ^ 4}}} {k ^ 4} | \ overrightarrow k \ cdot \ overrightarrow \ omega | ^ 2 \\ \ overrightarrow \ omega: & wind \\ V: & Winds \\ \ end {aligned} \]

You can see, this function is very huge complex, but the good news is we can make it run on the GPU.

HLSL achieve

Get the spectrum of the waves, you can begin to implement the hands, HLSL achieve the following:

#pragma kernel iFFT2x
#pragma kernel iFFT2y
#pragma kernel GenerateSpectrum
#pragma kernel GenerateTwinRandomGaussian
#pragma kernel GenerateSpectrumStepOne
#pragma kernel GenerateSpectrumStepTwo

static const uint FFT_STAGES = 8;
static const uint FFT_DIMENSION = 1 << FFT_STAGES;
static const uint FFT_BUTTERFLYS = FFT_DIMENSION >> 1;

static const float PI = 3.14159265;
groupshared float2 pingPongArray[FFT_DIMENSION * 2];

uint ReverseBits(uint index, uint count) {
	return reversebits(index) >> (32 - count);
}

float2 ComplexMultiply(float2 a, float2 b) {
	return float2(a.x * b.x - a.y * b.y, a.y * b.x + a.x * b.y);
}

void ButterFlyOnce(float2 input0, float2 input1, float2 twiddleFactor, out float2 output0, out float2 output1) {
	float2 t = ComplexMultiply(twiddleFactor, input1);
	output0 = input0 + t;
	output1 = input0 - t;
}

float2 Euler(float theta) {
	float2 ret;
	sincos(theta, ret.y, ret.x);
	return ret;
}

Texture2D<float2> SrcTex;
RWTexture2D<float2> DstTex;

void iFFT2(uint2 id, bool horizontal)
{
	uint butterFlyID = horizontal ? id.x : id.y;
	uint index0 = butterFlyID * 2;
	uint index1 = butterFlyID * 2 + 1;
	if (horizontal) {
		pingPongArray[index0] = SrcTex[uint2(ReverseBits(index0, FFT_STAGES), id.y)];
		pingPongArray[index1] = SrcTex[uint2(ReverseBits(index1, FFT_STAGES), id.y)];
	} else {
		pingPongArray[index0] = SrcTex[uint2(id.x, ReverseBits(index0, FFT_STAGES))];
		pingPongArray[index1] = SrcTex[uint2(id.x, ReverseBits(index1, FFT_STAGES))];
	}

	uint2 offset = uint2(0, FFT_DIMENSION);
	[unroll]
	for (uint s = 1; s <= FFT_STAGES; s++) {
		GroupMemoryBarrierWithGroupSync();
		// 每个stage中独立的FFT的宽度
		uint m = 1 << s;
		uint halfWidth = m >> 1;
		// 属于第几个iFFT
		uint nFFT = butterFlyID / halfWidth;
		// 在iFFT中属于第几个输入
		uint k = butterFlyID % halfWidth;
		index0 = k + nFFT * m;
		index1 = index0 + halfWidth;
		if (s != FFT_STAGES) {
			ButterFlyOnce(
				pingPongArray[offset.x + index0], pingPongArray[offset.x + index1],
				Euler(2 * PI * k / m),
				pingPongArray[offset.y + index0], pingPongArray[offset.y + index1]);
			offset.xy = offset.yx;
		} else {
			float2 output0;
			float2 output1;
			ButterFlyOnce(
				pingPongArray[offset.x + index0], pingPongArray[offset.x + index1],
				Euler(2 * PI * k / m),
				output0, output1);
			output0 /= FFT_DIMENSION;
			output1 /= FFT_DIMENSION;
			if (horizontal) {
				DstTex[uint2(index0, id.y)] = output0;
				DstTex[uint2(index1, id.y)] = output1;
			} else {
				DstTex[uint2(id.x, index0)] = output0;
				DstTex[uint2(id.x, index1)] = output1;
			}
		}
	}
}

[numthreads(FFT_BUTTERFLYS, 1, 1)]
void iFFT2x(uint3 id : SV_DispatchThreadID) {
	iFFT2(id.xy, true);
}

[numthreads(1, FFT_BUTTERFLYS, 1)]
void iFFT2y(uint3 id : SV_DispatchThreadID) {
	iFFT2(id.xy, false);
}

const static float G = 9.8;

float Pow2(float x) { return x * x; }
float Pow4(float x) { return x * x * x * x; }

float2 H0(float2 k_v, float k, float2 w, float V, float2 xi, float sqrtA) {
	// P_h(\overrightarrow k) = A\frac{e^{-1/(kL)^2}}{k^4} |\overrightarrow k \cdot \overrightarrow \omega| ^ 2
	// L = \frac{V^2}{g}
	float sqrtPh = sqrtA * exp(-Pow2(G / (k * V * V)) / 2) * abs(dot(k_v, w)) / Pow2(k);
	return xi * sqrtPh / sqrt(2);
}

float2 PhillipsSpectrum(
	float2 k_v,
	float sqrtA, // 常数
	float t, // 时间
	float2 w, // 风向
	float V, // 风速
	float2 xi) // 随机数
{
	// 这部分可以预计算
	float k = length(k_v);
	float2 h0 = H0(k_v, k, w, V, xi, sqrtA);
	float2 h0_adj = H0(-k_v, k, w, V, xi, sqrtA) * float2(1, -1);

	// \tilde{h_0}(\overrightarrow k) e^{it\sqrt{gk}} +	\tilde{ h_0^* }(-\overrightarrow k) e^ { -it\sqrt{ gk } }
	half tsqrtGk = t * sqrt(G * k);
	float2 h = ComplexMultiply(h0, Euler(tsqrtGk)) + ComplexMultiply(h0_adj, Euler(-tsqrtGk));

	h.x = isnan(h.x) ? 0 : h.x;
	h.y = isnan(h.y) ? 0 : h.y;
	return h;
}

float RadicalInverse_VdC(uint bits) {
	bits = (bits << 16u) | (bits >> 16u);
	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
	return bits * 2.3283064365386963e-10f;
}

RWTexture2D<float2> SpectrumTex;
float _Time;
float _SqrtAmplitude;
float2 _WindDirection;
float _WindSpeed;
float _PatchLength;


[numthreads(4, 4, 1)]
void GenerateSpectrum(uint3 id : SV_DispatchThreadID) {
	float2 rand = float2(RadicalInverse_VdC(id.x + 1), RadicalInverse_VdC(id.y + 1));
	float2 xi;
	sincos(2 * PI * rand.y, xi.x, xi.y);
	xi *= sqrt(-2 * log(rand.x));

	float2 mn = (float2)id.xy - FFT_DIMENSION / 2;
	float L = _PatchLength;
	float2 k_v = 2 * PI * mn / L;

	SpectrumTex[id.xy] = 
		PhillipsSpectrum(k_v, _SqrtAmplitude, _Time, _WindDirection, _WindSpeed, xi);
}

RWTexture2D<float2> TwinRandomGaussianTexOutput;

[numthreads(4,4,1)]
void GenerateTwinRandomGaussian(uint3 id : SV_DispatchThreadID) {
	float2 rand = float2(RadicalInverse_VdC(id.x + 1), RadicalInverse_VdC(id.y + 1));
	float2 xi;
	sincos(2 * PI * rand.y, xi.x, xi.y);
	xi *= sqrt(-2 * log(rand.x));
	TwinRandomGaussianTexOutput[id.xy] = xi;
}

Texture2D<float2> TwinRandomGaussianTexInput;
RWTexture2D<float4> SpectrumStepOneOutput;

[numthreads(4,4,1)]
void GenerateSpectrumStepOne(uint3 id : SV_DispatchThreadID) {
	float sqrtA = _SqrtAmplitude;
	float2 w = _WindDirection;
	float V = _WindSpeed;

	float2 xi = TwinRandomGaussianTexInput[id.xy];

	float2 mn = (float2)id.xy - FFT_DIMENSION / 2;
	float L = _PatchLength;
	float2 k_v = 2 * PI * mn / L;

	float k = length(k_v);

	float2 h0 = H0(k_v, k, w, V, xi, sqrtA);
	float2 h0_adj = H0(-k_v, k, w, V, xi, sqrtA) * float2(1, -1);

	SpectrumStepOneOutput[id.xy] = float4(h0, h0_adj);
}

Texture2D<float4> SpectrumStepOneInput;
RWTexture2D<float2> SpectrumStepTwoOutput;

[numthreads(4, 4, 1)]
void GenerateSpectrumStepTwo(uint3 id : SV_DispatchThreadID) {
	float4 input = SpectrumStepOneInput[id.xy];
	float2 h0 = input.xy;
	float2 h0_adj = input.zw;

	float t = _Time;

	float2 mn = (float2)id.xy - FFT_DIMENSION / 2;
	float L = _PatchLength;
	float2 k_v = 2 * PI * mn / L;

	float k = length(k_v);

	half tsqrtGk = t * sqrt(G * k);
	float2 h = ComplexMultiply(h0, Euler(tsqrtGk)) + ComplexMultiply(h0_adj, Euler(-tsqrtGk));

	h.x = isnan(h.x) ? 0 : h.x;
	h.y = isnan(h.y) ? 0 : h.y;
	
	SpectrumStepTwoOutput[id.xy] = h;
}

There are several points to note at:

  1. Hammersley Sequence originally wanted to use to do the random number generator on the GPU, but this discovery there were some repetitive pattern, or on the CPU side to do so.
  2. Phillips Spectrum generation may take time parameter t, precomputed portion (GenerateSpectrumStepOne), reducing the amount of calculation needs to be done in each frame.

Reference material

  1. TESSENDORF, J., 2001. Simulating ocean waters. In SIGGRAPH course notes (course 47), ACM SIGGRAPH
  2. fft sea simulation (a)

Guess you like

Origin www.cnblogs.com/hamwj1991/p/12638381.html
FFT
Recommended