FFT 및 게임 개발 (E)

FFT 및 게임 개발 (E)

임의의 방향으로 중첩 정현파 더미 정현파 스펙트럼 (진폭 및 위상)은 시간에 따라 변할 수있는 아날로그 파가 이해 될 수있다.

첫 번째는 결과를 보여 넣어 :

IFFT에 의해, 스펙트럼이 높은 필드 전파로 변환 될 수있다.

\ [H (\ overrightarrow X, t) = \ 합 _ {\ overrightarrow K} \ 틸드 H (\ overrightarrow K, t) E ^ {2 \ PI \ overrightarrow K \ cdot \ overrightarrow X} \]

주파수 영역에서 파도가 그것을 어떻게 와서, 그래서 우리는 시간 도메인 도구 파도에 주파수 영역에서 파도가?

필립스 스펙트럼 (스펙트럼 십자)

바다로 통계, 파도는 시간의 함수로 스펙트럼의 변화를 얻을 수 있습니다 :

\ [\ 틸드 H (\ overrightarrow K, t) = \ 틸드 {h_0} (\ overrightarrow K) E ^ {그것 \ SQRT {GK}} + \ 틸드 {h_0 ^ *} (- \ overrightarrow K) E ^ { - 그것은 \ SQRT {GK}} \]

\ [시작 \ {정렬} 여기서 & L은 인접한 두 공간 점 사이의 거리이다 : \\ 및 k_x = \ FRAC {2 \ 파이 m} {L} - \ FRAC {N} {2} \ 당량 m < \ FRAC {N} {2} \\ 및 k_z = \ FRAC {2 \ 파이 N} {L} - \ FRAC {N} {2} \ 당량 N <\ FRAC {N} {2} \\ 및 \ overrightarrow K = (k_x, k_z) \\ & k = | \ overrightarrow K | \\ \\ 추 및 \ 틸드 h_0 {2} {SQRT \} (\ overrightarrow K) = \ {1} FRAC (\ 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 \ 오메가 | ^ 2 \\ \ overrightarrow \ 오메가 : \\ 바람 및 V :] \ {} 배향 바람 \\ \ 단부

이 기능은 복잡한 매우 거대하다, 볼 수 있지만 좋은 소식은 우리가 GPU에서 실행 할 수 있습니다.

달성 HLSL

파도의 스펙트럼을 얻을, 당신은 손을 구현하기 시작, HLSL는 다음을 달성 :

#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;
}

에서 주목해야 할 몇 가지 포인트가 있습니다 :

  1. Hammersley 순서는 원래 GPU에 난수 발생기를 수행하는 데 사용하고 싶어하지만,이 발견이 어떤 반복적 인 패턴이었다, 또는 CPU 측에서 그렇게 할 수 있습니다.
  2. 십자 스펙트럼 생성 시간 변수 t를 취할 수 있으며, 사전 계산 부 (GenerateSpectrumStepOne) 계산의 양을 감소시키는 것은 각각의 프레임에서 수행 될 필요가있다.

참고 자료

  1. TESSENDORF, J., 2001 년 시뮬레이션 바다 바다. 에서 SIGGRAPH 코스 노트 (물론 47), ACM SIGGRAPH
  2. FFT 바다 시뮬레이션 (A)

추천

출처www.cnblogs.com/hamwj1991/p/12638381.html