您當前的位置:首頁 > 攝影

【學習筆記】Unity 基於GPU FFT海洋的實現-實踐篇

作者:由 稻草人 發表于 攝影時間:2019-12-11

本文是繼【學習筆記】Unity 基於GPU FFT海洋的實現-理論篇 的實現。

這是最後得到的結果

【學習筆記】Unity 基於GPU FFT海洋的實現-實踐篇

https://www。zhihu。com/video/1188154326164643840

對於實現的話就比較簡單了,只需要照著公式抄一遍就可以了。在本次實現中我們將使用Compute Shader在計算頻譜和FFT。為了理清思路和便於debug,整個工程程式碼寫得比較野蠻==其中很多計算是可以放到一起的,也建立了很多的RenderTexture(實際並用不了那麼多),同時還使用了比較大的資料型別等。

首先我們需要先生成高斯隨機數

//計算高斯隨機變數

numthreads

8

8

1

)]

void

ComputeGaussianRandom

uint3

id

SV_DispatchThreadID

{

float2

g

=

gaussian

id

xy

);

GaussianRandomRT

id

xy

=

float4

g

0

0

);

}

//計算高斯隨機數

float2

gaussian

float2

id

{

//均勻分佈隨機數

rngState

=

wangHash

id

y

*

N

+

id

x

);

float

x1

=

rand

();

float

x2

=

rand

();

x1

=

max

1

e

-

6

f

x1

);

x2

=

max

1

e

-

6

f

x2

);

//計算兩個相互獨立的高斯隨機數

float

g1

=

sqrt

-

2。0

f

*

log

x1

))

*

cos

2。0

f

*

PI

*

x2

);

float

g2

=

sqrt

-

2。0

f

*

log

x1

))

*

sin

2。0

f

*

PI

*

x2

);

return

float2

g1

g2

);

}

//隨機種子

uint

wangHash

uint

seed

{

seed

=

seed

^

61

^

seed

>>

16

);

seed

*=

9

seed

=

seed

^

seed

>>

4

);

seed

*=

0x27d4eb2d

seed

=

seed

^

seed

>>

15

);

return

seed

}

//計算均勻分佈隨機數[0,1)

float

rand

()

{

// Xorshift演算法

rngState

^=

rngState

<<

13

);

rngState

^=

rngState

>>

17

);

rngState

^=

rngState

<<

5

);

return

rngState

/

4294967296。0

f

;;

}

我們將使用wangHash來生成隨機數種子,然後使用Xorshift演算法來生成均勻分佈的隨機數。可以參考這裡Quick And Easy GPU Random Numbers In D3D11 ,然後對得到均勻分佈的隨機數,透過Box-Muller轉換,將得到高斯隨機數

r_0=sin(2\pi u_0)\sqrt{-2log(u_1)}

r_1=cos(2\pi u_0)\sqrt{-2log(u_1)}

u_0

u_1

是兩個相互獨立的均勻分佈的隨機數,

r_0

r_1

是兩個相互獨立的高斯隨機數。可參考這裡GPU Gems 3 :Chapter 37 。

隨機數只需要計算一次就好了,然後我們在計算高度頻譜

//生成高度頻譜

numthreads

8

8

1

)]

void

CreateHeightSpectrum

uint3

id

SV_DispatchThreadID

{

float2

k

=

float2

2。0

f

*

PI

*

id

x

/

N

-

PI

2。0

f

*

PI

*

id

y

/

N

-

PI

);

float2

gaussian

=

GaussianRandomRT

id

xy

]。

xy

float2

hTilde0

=

gaussian

*

sqrt

abs

phillips

k

*

DonelanBannerDirectionalSpreading

k

))

/

2。0

f

);

float2

hTilde0Conj

=

gaussian

*

sqrt

abs

phillips

-

k

*

DonelanBannerDirectionalSpreading

-

k

))

/

2。0

f

);

hTilde0Conj

y

*=

-

1。0

f

float

omegat

=

dispersion

k

*

Time

float

c

=

cos

omegat

);

float

s

=

sin

omegat

);

float2

h1

=

complexMultiply

hTilde0

float2

c

s

));

float2

h2

=

complexMultiply

hTilde0Conj

float2

c

-

s

));

float2

HTilde

=

h1

+

h2

HeightSpectrumRT

id

xy

=

float4

HTilde

0

0

);

}

phillips譜

//計算phillips譜

float

phillips

float2

k

{

float

kLength

=

length

k

);

kLength

=

max

0。001

f

kLength

);

// kLength = 1;

float

kLength2

=

kLength

*

kLength

float

kLength4

=

kLength2

*

kLength2

float

windLength

=

length

WindAndSeed

xy

);

float

l

=

windLength

*

windLength

/

G

float

l2

=

l

*

l

float

damping

=

0。001

f

float

L2

=

l2

*

damping

*

damping

//phillips譜

return

A

*

exp

-

1。0

f

/

kLength2

*

l2

))

/

kLength4

*

exp

-

kLength2

*

L2

);

}

Donelan-Banner方向拓展

//Donelan-Banner方向拓展

float

DonelanBannerDirectionalSpreading

float2

k

{

float

betaS

float

omegap

=

0。855

f

*

G

/

length

WindAndSeed

xy

);

float

ratio

=

dispersion

k

/

omegap

if

ratio

<

0。95

f

{

betaS

=

2。61

f

*

pow

ratio

1。3

f

);

}

if

ratio

>=

0。95

f

&&

ratio

<

1。6

f

{

betaS

=

2。28

f

*

pow

ratio

-

1。3

f

);

}

if

ratio

>

1。6

f

{

float

epsilon

=

-

0。4

f

+

0。8393

f

*

exp

-

0。567

f

*

log

ratio

*

ratio

));

betaS

=

pow

10

epsilon

);

}

float

theta

=

atan2

k

y

k

x

-

atan2

WindAndSeed

y

WindAndSeed

x

);

return

betaS

/

max

1

e

-

7

f

2。0

f

*

tanh

betaS

*

PI

*

pow

cosh

betaS

*

theta

),

2

));

}

float

dispersion

float2

k

{

return

sqrt

G

*

length

k

));

}

這裡並沒有什麼好說的,基本就是照著公式抄==。Donelan-Banner方向拓展公式為

【學習筆記】Unity 基於GPU FFT海洋的實現-實踐篇

圖擷取自Empirical Directional Wave Spectra for Computer Graphics,

\omega

角頻率,

\theta

是波相對於風的角度,

\omega_p

是峰值頻率

=0.855g/U

g

是重力加速度,

U

是平均風速。

得到了高度頻譜,就可以使用他來計算我們的偏移頻譜

//生成偏移頻譜

numthreads

8

8

1

)]

void

CreateDisplaceSpectrum

uint3

id

SV_DispatchThreadID

{

float2

k

=

float2

2

*

PI

*

id

x

/

N

-

PI

2

*

PI

*

id

y

/

N

-

PI

);

k

/=

max

0。001

f

length

k

));

float2

HTilde

=

HeightSpectrumRT

id

xy

]。

xy

float2

KxHTilde

=

complexMultiply

float2

0

-

k

x

),

HTilde

);

float2

kzHTilde

=

complexMultiply

float2

0

-

k

y

),

HTilde

);

DisplaceXSpectrumRT

id

xy

=

float4

KxHTilde

0

0

);

DisplaceZSpectrumRT

id

xy

=

float4

kzHTilde

0

0

);

}

至此就得到了我們想要的所有頻譜,然後分別來對他們進行FFT就可以了

//橫向FFT計算,只針對第m-1階段,最後一階段需要特殊處理

numthreads

8

8

1

)]

void

FFTHorizontal

uint3

id

SV_DispatchThreadID

{

int2

idxs

=

id

xy

idxs

x

=

floor

id

x

/

Ns

*

2。0

f

))

*

Ns

+

id

x

%

Ns

float

angle

=

2。0

f

*

PI

*

id

x

/

Ns

*

2。0

f

));

float2

w

=

float2

cos

angle

),

sin

angle

));

float2

x0

=

InputRT

idxs

]。

xy

float2

x1

=

InputRT

int2

idxs

x

+

N

*

0。5

f

idxs

y

)]。

xy

float2

output

=

x0

+

float2

w

x

*

x1

x

-

w

y

*

x1

y

w

x

*

x1

y

+

w

y

*

x1

x

);

OutputRT

id

xy

=

float4

output

0

0

);

}

這裡只截取了一個,對於最後一個階段和縱向FFT,程式碼大同小異,其實也可以寫到一起。

當FFT計算完後,就可以生成我們的偏移紋理,這裡使用了幾個引數來控制他的偏移程度。

//生成偏移紋理

numthreads

8

8

1

)]

void

TextureGenerationDisplace

uint3

id

SV_DispatchThreadID

{

float

y

=

length

HeightSpectrumRT

id

xy

]。

xy

/

N

*

N

*

HeightScale

//高度

float

x

=

length

DisplaceXSpectrumRT

id

xy

]。

xy

/

N

*

N

*

Lambda

//x軸偏移

float

z

=

length

DisplaceZSpectrumRT

id

xy

]。

xy

/

N

*

N

*

Lambda

//z軸偏移

HeightSpectrumRT

id

xy

=

float4

y

y

y

0

);

DisplaceXSpectrumRT

id

xy

=

float4

x

x

x

0

);

DisplaceZSpectrumRT

id

xy

=

float4

z

z

z

0

);

DisplaceRT

id

xy

=

float4

x

y

z

0

);

}

最後根據偏移紋理,來計算法線和泡沫,計算方法就和我們上一節所講的那樣。

//生成法線和泡沫紋理

numthreads

8

8

1

)]

void

TextureGenerationNormalBubbles

uint3

id

SV_DispatchThreadID

{

//計算法線

float

uintLength

=

OceanLength

/

N

-

1。0

f

);

//兩點間單位長度

//獲取當前點,周圍4個點的uv座標

uint2

uvX1

=

uint2

((

id

x

-

1。0

f

+

N

%

N

id

y

);

uint2

uvX2

=

uint2

((

id

x

+

1。0

f

+

N

%

N

id

y

);

uint2

uvZ1

=

uint2

id

x

id

y

-

1。0

f

+

N

%

N

);

uint2

uvZ2

=

uint2

id

x

id

y

+

1。0

f

+

N

%

N

);

//以當前點為中心,獲取周圍4個點的偏移值

float3

x1D

=

DisplaceRT

uvX1

]。

xyz

//在x軸 第一個點的偏移值

float3

x2D

=

DisplaceRT

uvX2

]。

xyz

//在x軸 第二個點的偏移值

float3

z1D

=

DisplaceRT

uvZ1

]。

xyz

//在z軸 第一個點的偏移值

float3

z2D

=

DisplaceRT

uvZ2

]。

xyz

//在z軸 第二個點的偏移值

//以當前點為原點,構建周圍4個點的座標

float3

x1

=

float3

x1D

x

-

uintLength

x1D

yz

);

//在x軸 第一個點的座標

float3

x2

=

float3

x2D

x

+

uintLength

x2D

yz

);

//在x軸 第二個點的座標

float3

z1

=

float3

z1D

xy

z1D

z

-

uintLength

);

//在z軸 第一個點的座標

float3

z2

=

float3

z1D

xy

z1D

z

+

uintLength

);

//在z軸 第二個點的座標

//計算兩個切向量

float3

tangentX

=

x2

-

x1

float3

tangentZ

=

z2

-

z1

//計算法線

float3

normal

=

normalize

cross

tangentZ

tangentX

));

//計算泡沫

float3

ddx

=

x2D

-

x1D

float3

ddz

=

z2D

-

z1D

//雅可比行列式

float

jacobian

=

1。0

f

+

ddx

x

*

1。0

f

+

ddz

z

-

ddx

z

*

ddz

x

jacobian

=

saturate

max

0

BubblesThreshold

-

saturate

jacobian

))

*

BubblesScale

);

NormalRT

id

xy

=

float4

normal

0

);

BubblesRT

id

xy

=

float4

jacobian

jacobian

jacobian

0

);

}

這樣我們就有了所有的資料,接下來進行渲染就可以了。在頂點著色器根據偏移紋理 進行頂點偏移。片源著色器就進行了簡單的燈光計算,如果想要更真實的物理效果可以參考這篇論文Real-time Realistic Ocean Lighting using Seamless Transitions from Geometry to BRDF

v2f

vert

appdata

v

{

v2f

o

o

uv

=

TRANSFORM_TEX

v

uv

_Displace

);

float4

displcae

=

tex2Dlod

_Displace

float4

o

uv

0

0

));

v

vertex

+=

float4

displcae

xyz

0

);

o

pos

=

UnityObjectToClipPos

v

vertex

);

o

worldPos

=

mul

unity_ObjectToWorld

v

vertex

)。

xyz

return

o

}

fixed4

frag

v2f

i

SV_Target

{

fixed3

normal

=

UnityObjectToWorldNormal

tex2D

_Normal

i

uv

)。

rgb

);

fixed

bubbles

=

tex2D

_Bubbles

i

uv

)。

r

fixed3

lightDir

=

normalize

UnityWorldSpaceLightDir

i

worldPos

));

fixed3

viewDir

=

normalize

UnityWorldSpaceViewDir

i

worldPos

));

fixed3

reflectDir

=

reflect

-

viewDir

normal

);

// reflectDir *= sign(reflectDir。y);

//取樣反射探頭

half4

rgbm

=

UNITY_SAMPLE_TEXCUBE_LOD

unity_SpecCube0

reflectDir

0

);

half3

sky

=

DecodeHDR

rgbm

unity_SpecCube0_HDR

);

//菲涅爾

fixed

fresnel

=

saturate

_FresnelScale

+

1

-

_FresnelScale

*

pow

1

-

dot

normal

viewDir

),

5

));

half

facing

=

saturate

dot

viewDir

normal

));

fixed3

oceanColor

=

lerp

_OceanColorShallow

_OceanColorDeep

facing

);

fixed3

ambient

=

UNITY_LIGHTMODEL_AMBIENT

rgb

//泡沫顏色

fixed3

bubblesDiffuse

=

_BubblesColor

rbg

*

_LightColor0

rgb

*

saturate

dot

lightDir

normal

));

//海洋顏色

fixed3

oceanDiffuse

=

oceanColor

*

_LightColor0

rgb

*

saturate

dot

lightDir

normal

));

fixed3

halfDir

=

normalize

lightDir

+

viewDir

);

fixed3

specular

=

_LightColor0

rgb

*

_Specular

rgb

*

pow

max

0

dot

normal

halfDir

)),

_Gloss

);

fixed3

diffuse

=

lerp

oceanDiffuse

bubblesDiffuse

bubbles

);

fixed3

col

=

ambient

+

lerp

diffuse

sky

fresnel

+

specular

return

fixed4

col

1

);

}

邏輯程式碼並沒有粘,因為他實在是太簡單了。。。。。

原始碼已經上傳到了Github上,如果有什麼理解或錯誤的地方,望大佬告訴我。。。。。

標簽: id  xy  0f  偏移  float4