您當前的位置:首頁 > 繪畫

D3D12學習筆記五:PBR光照入門

作者:由 Wonderful筆記 發表于 繪畫時間:2021-03-30

本節講述一下如何用D3D12實現最簡單的PBR光照,是我個人根據 LearnOpenGL CN 網站的 PBR光照一節中搬過來的,在龍書上並沒有這節,所以我把這節的例子程式碼上傳到了:

https://

github。com/wondeful18/D

3D12_PBR

,可以把整個目錄下載下來,並且在PBR1目錄找到sln檔案並雙擊開啟,在VS2017上編譯透過,大家可以對照著程式碼來除錯理解。

PBR的概念過於複雜,我就不嘗試解釋了,包括那些複雜的數學公式和推理等,完全理解這些概念應該挺耗費髮際線的,為了保護髮型,所以這裡我是直接用程式碼和圖示來試圖逐步理解它們。

我們從shader程式碼得出,PBR最重要的是這個公式:

D3D12學習筆記五:PBR光照入門

如上圖,這個公式最重要的就是 DFG部分,這部分我們會重點講解一下:

D:正太分佈

這個函式是鏡面光照的關鍵相當於上一節的粗糙度函式,光滑的物體,光會集中在一點,並且亮度越大,而粗糙的物體,光會分散一些,亮度也相對沒有那麼大,如下圖:

D3D12學習筆記五:PBR光照入門

如上圖是一個粗糙度在0。1到1。0的光照效果。正太分佈的公式如下:

D3D12學習筆記五:PBR光照入門

如上圖,正太分佈主要傳入表面法線向量,中間向量和表面粗糙度的平方,並算出一個值,它的函式影象如下:

D3D12學習筆記五:PBR光照入門

如上圖,粗糙度越小,光照越集中在一個小的角度,反射光的強度越大,粗糙度越大,則光越分散。它的shader程式碼如下:

// ——————————————————————————————————————

// 正態分佈函式:估算在受到表面粗糙度的影響下,取向方向與中間向量一致的微平面的數量。 DFG的D

float

DistributionGGX

float3

N

float3

H

float

roughness

{

float

a

=

roughness

*

roughness

float

a2

=

a

*

a

float

NdotH

=

saturate

dot

N

H

));

float

NdotH2

=

NdotH

*

NdotH

float

nom

=

a2

float

denom

=

NdotH2

*

a2

-

1。0f

+

1。0f

);

denom

=

3。14159265359f

*

denom

*

denom

return

nom

/

max

denom

0。0000001

);

}

我們修改一下例子程式的shader程式碼,讓只顯示D正太分佈函式的值,如下:

float

NDF

=

DistributionGGX

N

H

gRoughness

);

//Lo += (kD * albedo / PI + specular) * radiance * NdotL;

Lo

+=

float3

NDF

NDF

NDF

));

// 強制只顯示正太分佈函式結果

效果如下:

D3D12學習筆記五:PBR光照入門

如上圖,正太分佈函式已經把鏡面光照的高光部分顯示出來了。

F:菲涅爾函式

菲涅爾函式在上一節也講過了,主要模擬的是在很小的角度觀察水面時,反射光較小,在很大的角度觀察水面時,反射光較大,如下圖:

D3D12學習筆記五:PBR光照入門

這裡用的菲涅爾函式也和上一節基本一致,不過用的是 中間向量H 和 觀察向量V的夾角,如下圖:

D3D12學習筆記五:PBR光照入門

公式如下:

D3D12學習筆記五:PBR光照入門

它的函式影象如下:

D3D12學習筆記五:PBR光照入門

它的程式碼如下:

// ——————————————————————————————————————

// 菲涅爾方程 當光線碰撞到一個表面的時候,菲涅爾方程會根據觀察角度告訴我們被反射的光線所佔的百分比

float3

fresnelSchlick

float

cosTheta

float3

F0

{

float

OneSubCos

=

1。f

-

cosTheta

return

F0

+

1。f

-

F0

*

OneSubCos

*

OneSubCos

*

OneSubCos

*

OneSubCos

*

OneSubCos

}

我們修改一下例子程式的shader程式碼,讓只顯示F菲涅爾函式的值,如下:

float3

F

=

fresnelSchlick

saturate

dot

H

V

)),

F0

);

//Lo += (kD * albedo / PI + specular) * radiance * NdotL;

Lo

+=

F

// 強制只顯示菲涅爾的結果

D3D12學習筆記五:PBR光照入門

如上圖,球形的外圍,比中間亮一些。

當然。如果像上一節一樣,我把傳給菲涅爾方程的中間向量H和觀察向量V的夾角改成:法線N和V的夾角後,會更加明顯一些:

D3D12學習筆記五:PBR光照入門

不過,這裡我依照LearnOpengl的教程,繼續用中間向量H和觀察向量V的夾角。

G:幾何遮蔽函式

最後讓我們來看看DFG的最後一個G幾何遮蔽函式,它主要是用來模擬微平面的一個點有沒有被遮住,它的大概效果如下:

D3D12學習筆記五:PBR光照入門

如上圖,粗糙度大的物體,被遮蔽的地方會多一些,它的程式碼如下:

// ——————————————————————————————————————

// 幾何函式 GGX與Schlick-Beckmann近似的結合體

float

GeometrySchlickGGX

float

NdotV

float

roughness

{

float

r

=

roughness

+

1。0

);

float

k

=

r

*

r

/

8。0

float

nom

=

NdotV

float

denom

=

NdotV

*

1。0

-

k

+

k

return

nom

/

denom

}

// ——————————————————————————————————————

// 幾何函式 史密斯法(Smith’s method) 將觀察方向(幾何遮蔽(Geometry Obstruction))

// 和光線方向向量(幾何陰影(Geometry Shadowing))都考慮進去

float

GeometrySmith

float3

N

float3

V

float3

L

float

roughness

{

// 觀察方向

float

NdotV

=

max

dot

N

V

),

0。0

);

// 光線方向

float

NdotL

=

max

dot

N

L

),

0。0

);

// 觀察方向的幾何遮蔽

float

ggx2

=

GeometrySchlickGGX

NdotV

roughness

);

// 光線方向的幾何陰影

float

ggx1

=

GeometrySchlickGGX

NdotL

roughness

);

return

ggx1

*

ggx2

}

它的函式圖形如下,由於它是一個雙角度變數的二元方程,所以函式圖形是一個3D曲面:

D3D12學習筆記五:PBR光照入門

如上圖,當觀察角度X,以及燈光角度Y都在0附近時,幾何遮蔽最少,亮度最亮,值最接近1。0。

我們修改一下例子程式的shader程式碼,讓只顯示G幾何遮蔽函式的值,如下:

float

G

=

GeometrySmith

N

V

L

gRoughness

);

//Lo += (kD * albedo / PI + specular) * radiance * NdotL;

Lo

+=

float3

G

G

G

);

// 強制只顯示幾何遮蔽值

效果如下:

D3D12學習筆記五:PBR光照入門

如上圖,在只顯示幾何遮蔽的情況下,這個球也有一些立體感,中間凸出的部分亮一些。

把DFG三種效果最終組合起來的shader程式碼如下:

// 累計的光照

float3

Lo

=

float3

0。f

0。f

0。f

);

// 目前有4盞點燈

for

int

i

=

0

i

<

4

++

i

{

float3

LightPos

=

gLights

i

]。

Position

float3

L

=

normalize

LightPos

-

pin

PosW

);

float3

H

=

normalize

V

+

L

);

float

dis

=

length

LightPos

-

pin

PosW

);

// 距離衰減

float

attenuation

=

1。f

/

dis

*

dis

);

float3

radiance

=

gLights

i

]。

Strength

*

attenuation

// 正太分佈函式D

float

NDF

=

DistributionGGX

N

H

gRoughness

);

// 菲涅爾函式F

float3

F

=

fresnelSchlick

saturate

dot

H

V

)),

F0

);

// 幾何遮蔽函式G

float

G

=

GeometrySmith

N

V

L

gRoughness

);

float3

nominator

=

NDF

*

G

*

F

float

denominator

=

4

*

max

dot

N

V

),

0。0

*

max

dot

N

L

),

0。0f

);

// 鏡面光部分的值

float3

specular

=

nominator

/

max

denominator

0。001

);

// 鏡面光佔比為菲涅爾結果

float3

kS

=

F

// 散射光佔比:為了保持能量守恆,散射光佔比 和 鏡面光佔比之和不能超過1。0

float3

kD

=

float3

1。f

1。f

1。f

-

kS

// 金屬度越高,散射越少

kD

=

kD

*

1。f

-

gMetallic

);

float

NdotL

=

max

dot

N

L

),

0。0f

);

// 新增到最終Lo

Lo

+=

kD

*

albedo

/

PI

+

specular

*

radiance

*

NdotL

}

它的最終效果如下:

D3D12學習筆記五:PBR光照入門

如上圖,從左到右粗糙度遞增,從上到下金屬度遞減。

我們的PBR入門教程就到這裡,未來我們會先學習如何在D3D12中載入紋理,以及法線貼圖之類,後續我們還會顯示附帶粗糙度貼圖和金屬度貼圖等更加逼真的PBR效果,敬請期待!