本文由zhangbaochong原創,轉載請注明出處http://www.cnblogs.com/zhangbaochong/p/5579289.html
在之前的場景繪制中我們都是給每個頂點指定了單一顏色 ,然后由系統插值計算各個部分顏色,最終顯示出來。接下來我們將學習dx11中比較有意思的一部分——光照,通過光照和材質的相互作用來實現更真實的場景。
1. 光照
1.1 光照效果
簡單舉個例子,看龍書中的一張圖:
a圖沒加光照看起來像一個2D圖形,而加了光照的b圖則看起來像一個3D圖形。由此可見,光照在3D渲染方面是尤為重要的,通常借助光照可以讓場景顯得更加真實。
1.2 材質
材質可以說是決定光如何作用在物體表面的一種屬性。例如,光在物體表面反射和吸收的顏色,它的反射率、透明度、光滑程度等屬性組成了一個物體表面的材質。在我們下面的示例一般只考慮光的反射和吸收顏色以及光滑程度用來計算全反射,暫不考慮其它因素。
2.法線
2.1面法線(face normal)
什么是面法線呢?A face normal is a vector that describesthe direction a polygon is facing。
計算面法線也很簡單,在面內找到三點,取得兩個向量作×積然后單位化即可。
2.2 頂點法線(vertex normal)
在directx中我們需要知道頂點的法線,法線決定了光照射平面的角度。光線會被應用到每個頂點,並且根據面法線和光照方向的點積去調整光線顏色的強度。那么怎么計算頂點法線呢?下面的圖介紹的很清楚了:
2.3 法線變換
在一個頂點進行空間變換時,法線通常也需要進行變換。但是注意:頂點和法線的變換矩陣並不相同!
對於一個頂點的變換矩陣A,其對應的法線變換時A的逆矩陣的轉置。
2.4 朗伯余弦定律
當一個面元的輻射亮度和光亮度在其表面上半球的所有方向相等時,並符合I(θ) = INcosθ時稱為朗伯余弦定律。
I(θ)----面元在θ角(與表面法線夾角)方向及其法線方向的輻射強度
IN----面元在θ角方向及其法線方向的發光亮度
3. 光照計算處理的三部分
光照計算處理共有三個部分:環境光(ambient)、漫反射光(diffuse)以及全反射光(又稱高光,specular)。
3.1 環境光(ambient)
在現實當中,光照是一個很復雜的物理現象。一個物體所接受的光,除了直接來自光源的部分外,還包括光源經過環境中其他各個物體的反射而來的部分。而在圖形學中,我們默認的光照模型為局部光模型,即一個頂點的光照計算只跟該點信息與光源信息有關,而不考慮環境中其他物體的影響,比如陰影等。
3.2 漫反射光(diffuse)
光照射在物體表面后,其反射光沿隨機方向均勻的分布,即"漫反射”。反射光的強度與光照方向與表面法線的夾角theta相關,滿足比例關系:I = Io * cos(theta)。由於反射光方向隨機,因此該部分的計算與觀察點無關,而只與光線方向與法線相關。
3.3 全反射光(specular)
光線照射在光滑物體表面后,在特定方向上會有很強的反射,即發生全反射。全反射光主要集中在一個近似圓錐角的范圍內。如下圖所示:
n為法線,l為光線入射方向,r為全反射方向,E為觀察點,因此v為視角方向。全反射光進入眼睛的強度與v和r的角度theta有關,隨着該角度增大,全反射強度下降,其下降輻度與物體表面光滑程序相關。因此,對於該部分光的計算,除了需要光線方向、法線等信息外,還與觀察點的位置有很大關系。具體計算公式在本文后面會詳細給出。
4. 光源模型
4.1 平行光
平行光是最簡單的一種光照模型,光照方向不變而且光照強度不隨空間位置改變,可以用平行光來模擬日常生活中的太陽光。
c++程序中平行光的定義:
struct DirectionalLight { DirectionalLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient;//環境光 XMFLOAT4 Diffuse;//漫反射光 XMFLOAT4 Specular;//高光 XMFLOAT3 Direction;//光照方向 float Pad; // Pad the last float so we can set an array of lights if we wanted.用於與HLSL中“4D向量”對齊規則匹配 };
4.2 點光源
c++程序中點光源的定義:
struct PointLight { PointLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular; // Packed into 4D vector: (Position, Range) XMFLOAT3 Position;//光源位置 float Range; //光照范圍 // Packed into 4D vector: (A0, A1, A2, Pad) XMFLOAT3 Att; //衰減系數 float Pad; // Pad the last float so we can set an array of lights if we wanted. };
4.3 聚光燈
c++程序中聚光燈定義:
struct SpotLight { SpotLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular; // Packed into 4D vector: (Position, Range) XMFLOAT3 Position;//光照位置 float Range; //光照范圍 // Packed into 4D vector: (Direction, Spot) XMFLOAT3 Direction;//光照方向 float Spot; //光照強度系數 // Packed into 4D vector: (Att, Pad) XMFLOAT3 Att; //衰減系數 float Pad; // Pad the last float so we can set an array of lights if we wanted. };
4.4 HLSL中三種光源的定義
1 struct DirectionalLight 2 { 3 float4 Ambient; 4 float4 Diffuse; 5 float4 Specular; 6 float3 Direction; 7 float pad; 8 }; 9 10 struct PointLight 11 { 12 float4 Ambient; 13 float4 Diffuse; 14 float4 Specular; 15 16 float3 Position; 17 float Range; 18 19 float3 Att; 20 float pad; 21 }; 22 23 struct SpotLight 24 { 25 float4 Ambient; 26 float4 Diffuse; 27 float4 Specular; 28 29 float3 Position; 30 float Range; 31 32 float3 Direction; 33 float Spot; 34 35 float3 Att; 36 float pad; 37 };
4.5 材質
材質同樣有環境光、漫反射光和高光三種成分,此外還有一個材質的鏡面反射系數即表示光滑程度。
c++程序中定義:
struct Material { Material() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular;//w表示高光強度 XMFLOAT4 Reflect; };
HLSL定義:
struct Material { float4 Ambient; float4 Diffuse; float4 Specular; float4 Reflect; };
5. 光照計算
光照計算無疑是最重要的部分,這一部分在HLSL中實現。
5.1 平行光
void ComputeDirectionalLight(Material mat, //材質 DirectionalLight L, //平行光 float3 normal, //頂點法線 float3 toEye, //頂點到眼睛的向量 out float4 ambient, //計算結果:環境光 out float4 diffuse, //計算結果:漫反射光 out float4 spec) //計算結果:高光 { // 結果初始化為0 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); spec = float4(0.0f, 0.0f, 0.0f, 0.0f); // 光線方向 float3 lightVec = -L.Direction; // 環境光直接計算 ambient = mat.Ambient * L.Ambient; // 計算漫反射系數 //光線、法線方向歸一化 float diffuseFactor = dot(lightVec, normal); // 頂點背向光源不再計算 [flatten] if (diffuseFactor > 0.0f) { float3 v = reflect(-lightVec, normal); float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); //計算漫反射光 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; //計算高光 spec = specFactor * mat.Specular * L.Specular; } }
5.2 點光源
1 void ComputePointLight(Material mat, //材質 2 PointLight L, //點光源 3 float3 pos, //頂點位置 4 float3 normal, //頂點法線 5 float3 toEye, //頂點到眼睛的向量 6 out float4 ambient, //計算結果:環境光 7 out float4 diffuse, //計算結果:漫反射光 8 out float4 spec) //計算結果:高光 9 { 10 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); 11 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); 12 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); 13 14 //光照方向:頂點到光源 15 float3 lightVec = L.Position - pos; 16 17 //頂點到光源距離 18 float d = length(lightVec); 19 20 //超過范圍不再計算 21 if (d > L.Range) 22 return; 23 24 //歸一化光照方向 25 lightVec /= d; 26 27 //計算環境光 28 ambient = mat.Ambient * L.Ambient; 29 30 //漫反射系數 31 float diffuseFactor = dot(lightVec, normal); 32 33 [flatten] 34 if (diffuseFactor > 0.0f) 35 { 36 float3 v = reflect(-lightVec, normal); 37 float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); 38 //計算漫反射光 39 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; 40 //計算高光 41 spec = specFactor * mat.Specular * L.Specular; 42 } 43 44 // 計算衰減 45 float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d)); 46 47 diffuse *= att; 48 spec *= att; 49 }
5.3 聚光燈
1 void ComputeSpotLight(Material mat, //材質 2 SpotLight L, //聚光燈 3 float3 pos, //頂點位置 4 float3 normal, //頂點法線 5 float3 toEye, //頂點到眼睛向量 6 out float4 ambient, //計算結果:環境光 7 out float4 diffuse, //計算結果:漫反射光 8 out float4 spec) //計算結果:高光 9 { 10 //初始化結果 11 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); 12 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); 13 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); 14 15 //光照方向:頂點到光源 16 float3 lightVec = L.Position - pos; 17 18 //頂點到光源距離 19 float d = length(lightVec); 20 21 //距離大於光照方向不再計算 22 if (d > L.Range) 23 return; 24 25 //歸一化光照方向 26 lightVec /= d; 27 28 //計算環境光 29 ambient = mat.Ambient * L.Ambient; 30 31 32 //計算漫反射系數 33 float diffuseFactor = dot(lightVec, normal); 34 35 [flatten] 36 if (diffuseFactor > 0.0f) 37 { 38 float3 v = reflect(-lightVec, normal); 39 float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); 40 //漫反射光 41 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; 42 //高光 43 spec = specFactor * mat.Specular * L.Specular; 44 } 45 46 //聚光衰減系數 47 float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot); 48 49 //衰減系數 50 float att = spot / dot(L.Att, float3(1.0f, d, d*d)); 51 52 ambient *= spot; 53 diffuse *= att; 54 spec *= att; 55 }
6.程序中使用的shader文件
#include "LightHelper.fx" cbuffer cbPerFrame { DirectionalLight gDirLight; PointLight gPointLight; SpotLight gSpotLight; float3 gEyePosW; //觀察點 }; cbuffer cbPerObject { float4x4 gWorld; float4x4 gWorldInvTranspose;//世界矩陣的逆矩陣的轉置 float4x4 gWorldViewProj; Material gMaterial; }; struct VertexIn { float3 PosL : POSITION; //頂點坐標 float3 NormalL : NORMAL; //頂點法線 }; struct VertexOut { float4 PosH : SV_POSITION; //投影后的坐標 float3 PosW : POSITION; //世界變換后的坐標 float3 NormalW : NORMAL; //世界變換后的頂點法線 }; VertexOut VS(VertexIn vin) { VertexOut vout; vout.PosW = mul(float4(vin.PosL, 1.0f), gWorld).xyz; vout.NormalW = mul(vin.NormalL, (float3x3)gWorldInvTranspose); vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj); return vout; } float4 PS(VertexOut pin) : SV_Target { //插值運算有可能使法線不再單位化,重新單位化法線 pin.NormalW = normalize(pin.NormalW); //頂點到觀察點向量,歸一化 float3 toEyeW = normalize(gEyePosW - pin.PosW); //初始化顏色值全部為0 float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); //每個光源計算后得到的環境光、漫反射光、高光 float4 A, D, S; //每個光源計算后將ADS更新到最終結果中 ComputeDirectionalLight(gMaterial, gDirLight, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; ComputePointLight(gMaterial, gPointLight, pin.PosW, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; ComputeSpotLight(gMaterial, gSpotLight, pin.PosW, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; float4 litColor = ambient + diffuse + spec; //最終顏色透明度使用漫反射光的 litColor.a = gMaterial.diffuse.a; return litColor; } technique11 LightTech { pass P0 { SetVertexShader(CompileShader(vs_5_0, VS())); SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_5_0, PS())); } }
7.程序運行結果
由於代碼比較多,這里就不給出了,有興趣的可以下載看看注釋也比較詳細
源碼下載:http://files.cnblogs.com/files/zhangbaochong/LightDemo.zip