光和陰影是使場景流行的重要要求。通過一些著色器藝術,您可以突出重要的對象、描述天氣和一天中的時間并設置場景的氣氛。即使您的場景由卡通對象組成,如果您沒有正確地照亮它們,場景也會變得平淡無奇。
最簡單的光照方法之一是 Phong 反射模型。它以 Bui Tong Phong 的名字命名,他在 1975 年發表了一篇論文,擴展了舊的光照模型。這個想法不是嘗試復制光線和反射物理學,而是生成看起來逼真的圖片。
這種模型已經流行了 40 多年,是開始使用幾行代碼來學習如何偽造光照的好地方。所有計算機圖像都是假的,但有更現代的實時渲染方法可以模擬光的物理特性。
在第11章“地圖和材質”中,您將了解基于物理的渲染(PBR),這是您的渲染器最終將使用的光照技術。PBR 是一種更逼真的光照模型,但 Phong 易于理解和入門。
starter項目
打開本節的starter項目。
起始項目的文件現在位于合理的分組中。在 Game 組中,項目包含一個新的游戲控制器類,該類進一步分離了場景更新和渲染。Renderer 現在獨立于 GameScene。GameController 初始化并擁有 Renderer 和 GameScene。在每一幀上,作為 MetalView 的代理,GameController 首先更新場景,然后將其傳遞給 Renderer 進行繪制。
在 GameScene.swift 中,新場景包含一個球體和一個指示場景旋轉的 3D 小工具。
Utility 組中的 DebugLights.swift 包含一些代碼,您稍后將使用這些代碼來調試光源的位置。點光源將繪制為點,太陽的方向將繪制為線條。
熟悉代碼,構建并運行項目。
為了圍繞球體旋轉并充分欣賞您的光照,相機是 ArcballCamera 類型。按 1(在 Alpha 鍵上方)將攝像機設置為前視圖,按 2 將攝像機重置為默認視圖。GameScene 包含用于此功能的按鍵代碼。
您可以看到球體顏色非常平坦。在本章中,您將添加著色光照和鏡面反射高光。
顏色表示
在本書中,您將學習必要的基礎知識,以渲染光線、顏色和簡單的著色。然而,光的物理學是一個龐大而迷人的話題,有許多書籍和很大一部分互聯網專門用于講解它。您可以在本章 resources 目錄中的 references.markdown 中找到進一步的閱讀內容。
在現實世界中,不同波長的光的反射賦予對象顏色。吸收所有光的對象表面是黑色的。在計算機世界中,像素顯示顏色。像素越多,分辨率越好,這使得生成的圖像更清晰。每個像素都由子像素組成。這些是預先確定的單一顏色,紅色、綠色或藍色。通過打開和關閉這些子像素,根據顏色深度,屏幕可以顯示人眼可見的大部分顏色。
在 Swift 中,您可以使用該像素的 RGB 值來表示顏色。例如,float3(1, 0, 0) 是紅色像素,float3(0, 0, 0) 是黑色,float3(1, 1, 1) 是白色。
從著色的角度來看,您可以通過將兩個值相乘來將紅色表面與灰色光照組合在一起:
let result = float3(1.0, 0.0, 0.0) * float3(0.5, 0.5, 0.5) 結果是 (0.5, 0, 0),這是一個較深的紅色著色。
對于簡單的 Phong 光照,我們可以使用表面的斜率。物體表面離光源越傾斜,表面就越暗。
法線
表面的斜率可以確定表面反射光線的程度。
在下圖中,點 A 正對著太陽,將接收到最多的光;點B不那么直接朝向太陽,但仍會接收到一些光線;點 C 完全背對太陽,接收不到任何光線。
注: 在現實世界中,光線從一個表面反射到另一個表面;如果房間內有任何光線,則物體會有一些反射,這些反射會柔和地照亮所有其他物體的背面。這是全局光照。Phong 光照模型單獨照亮每個對象,稱為局部光照。
圖中的虛線與表面相切。切線是最能描述曲線在某一點處斜率的直線。
從圓中出來的線與切線成直角。這些稱為表面法線,您第一次遇到這些法線是在第 7 章 “片段函數”中。
光照類型
計算機圖形學中有幾個標準的光源選項,每個選項都起源于現實世界。
? Directional Light:沿單個方向發送光線。太陽是定向光。
? Point Light:像燈泡一樣向各個方向發送光線。
? Spotlight:向圓錐體定義的有限方向發送光線。手電筒或臺燈將是聚光燈。
定向光
一個場景中可以有許多光照。事實上,在工作室攝影中,只有一個燈光是非常不尋常的。通過將燈光放入場景中,可以控制陰影落下的位置和黑暗程度。在本章中,您將向場景添加多個燈光。
您將創建的第一種光照是太陽。太陽是向各個方向發射光線的點光源,但對于計算機建模,您可以將其視為定向光。它是一個遙遠的強大光源。當光線到達地球時,光線似乎是平行的。在陽光明媚的日子里在外面檢查一下——你所看到的一切都有它的影子,朝著同一個方向移動。
要定義光源類型,您需要創建一個 GPU 和 CPU 都可以讀取的 Light 結構體,以及一個描述 GameScene 光照的 SceneLighting 結構體。
? 在 Shaders 組中,打開 Common.h,在 #endif 之前,創建您將使用的光源類型的枚舉:
typedef enum {unused = 0,Sun = 1,Spot = 2,Point = 3,Ambient = 4
} LightType;
? 在此之下,添加定義光源的結構體:
typedef struct {LightType type;vector_float3 position;vector_float3 color;vector_float3 specularColor;float radius;vector_float3 attenuation;float coneAngle;vector_float3 coneDirection;float coneAttenuation;
} Light;
此結構體保存光的位置和顏色。在學習本章時,您將了解其他屬性。
? 在 Game 組中創建一個新的 Swift 文件,并將其命名為 SceneLighting.swift。然后,添加以下內容:
struct SceneLighting {static func buildDefaultLight() -> Light {var light = Light()light.position = [0, 0, 0]light.color = [1, 1, 1]light.specularColor = [0.6, 0.6, 0.6]light.attenuation = [1, 0, 0]light.type = Sunreturn light}
}
此文件將保存 GameScene 的光照。您將擁有多個光源,buildDefaultLight() 將創建一個基本光源。
? 在 SceneLighting 中為太陽定向光源創建一個屬性:
let sunlight: Light = {var light = Self.buildDefaultLight()light.position = [1, 2, -2]return light
}()
position 在世界空間中。這會在場景的右側,球體的前方放置一個光源。球體將放置在世界的原點處。
? 創建一個數組來保存您即將創建的各種光源:
var lights: [Light] = []
? 添加初始化器:
init() {lights.append(sunlight)
}
您將在初始化器中添加場景的所有燈光。
? 打開 GameScene.swift,并將 lighting 屬性添加到 GameScene:
let lighting = SceneLighting()
您將在 fragment 函數中執行所有光照著色,因此您需要將光源數組傳遞給該函數。Metal Shading Language 沒有動態數組功能,因此無法找出數組中的項目數。您將此值傳遞給 Params 中的片段著色器。
? 打開 Common.h,并將這些屬性添加到 Params 中:
uint lightCount;
vector_float3 cameraPosition;
稍后您將需要 Camera Position 屬性。
在 Common.h 中向 BufferIndices 添加新索引:
LightBuffer = 13
?您將使用此索引將光照詳細信息發送到 fragment 函數。
? 打開 Renderer.swift,并將其添加到 updateUniforms(scene:) 中:
params.lightCount = UInt32(scene.lighting.lights.count)
您將能夠在片段著色器函數中訪問此值。
? 在 draw(scene:in:) 中,在 scene.models 中 model 之前,添加以下內容:
var lights = scene.lighting.lights
renderEncoder.setFragmentBytes(&lights,length: MemoryLayout<Light>.stride * lights.count,index: LightBuffer.index)
在這里,您將索引13緩沖區中的燈光數組發送到 fragment 函數。
您現在已經在 Swift 端設置了一個太陽光。您將在 fragment 函數中執行所有實際的光照計算,并了解有關光照屬性的更多信息。
Phong反射模型
在 Phong 反射模型中,有三種類型的光照反射。您將計算每個顏色,然后將它們相加以生成最終顏色。
? 漫反射:理論上,照射到表面的光線會以圍繞該點的表面法線反射的角度反射。然而,表面在微觀上是粗糙的,因此光線會向各個方向反射,如上圖所示。這將產生漫反射顏色,其中光強度與入射光與曲面法線之間的角度成正比。在計算機圖形學中,這個模型被稱為朗伯反射率,以 1777 年去世的約翰·海因里希·蘭伯特 (Johann Heinrich Lambert) 的名字命名。在現實世界中,這種漫反射通常適用于暗淡、粗糙的表面,但具有最朗伯特性的表面是人造的:Spectralon (https://en.wikipedia.org/wiki/Spectralon),用于光學元件。
? Specular:表面越光滑,越閃亮,并且光線從表面反射的方向就越少。鏡子完全從表面法線反射,沒有偏轉。閃亮的物體會產生可見的鏡面高光,渲染鏡面反射可以讓觀眾了解物體的表面類型,無論汽車是舊車殘骸還是剛從銷售地新鮮出爐。
? 環境光:在現實世界中,光線會到處反射,因此被遮蔽對象很少是全黑的。這是環境反射。
表面顏色由自發光物體表面顏色加上環境光、漫反射和鏡面反射的貢獻組成。對于漫反射和鏡面反射,要找出表面在特定點應接收多少光,您只需找出入射光方向與表面法線之間的角度即可。
點乘
幸運的是,有一個簡單的數學運算來發現兩個向量之間的角度,稱為點積。
和:
其中 ||A||表示向量 A 的長度(或大小)。
更幸運的是,simd 和 Metal Shading Language 都有一個函數 dot()來獲取點積,因此您不必記住公式。
除了找出兩個向量之間的角度外,您還可以使用點積來檢查兩個向量是否指向同一方向。
將兩個向量的大小調整為單位向量 — 即長度為 1 的向量。您可以使用 normalize() 函數執行此操作。如果兩個單位向量平行且指向同一個方向,則點積結果將為 1。如果它們是平行的但方向相反,則結果將為 -1。如果它們成直角(正交),則結果將為 0。
看上圖,如果黃色(太陽)向量垂直向下,藍色(法線)向量垂直向上,則點積將為 -1。該值是兩個向量之間的余弦角。余弦的優點在于它們的值始終是介于 -1 和 1 之間,因此您可以使用此范圍來確定光線在某個點的亮度。
以下面示例為例:
?
太陽從天而降,方向矢量為 [2, -2, 0]。向量 A 是 [-2, 2, 0] 的法向量。這兩個向量指向相反的方向,因此當您將向量轉換為單位向量(歸一化它們)時,它們的點積將為 -1。
向量 B 是 [0.3, 2, 0] 的法向量。太陽光是定向光,因此使用相同的方向向量。歸一化后,太陽光和 B 的點積為 -0.59。
此 Playground 代碼演示了計算。
注意:第 8 行之后的結果表明,使用浮點時應始終小心,因為結果永遠不會精確。切勿使用表達式,例如 if (x == 1.0) - 應始終使用<= 或 >=檢查。
在片段著色器中,您將能夠獲取這些值,并使用點乘乘以片段顏色,以獲得片段的亮度。
漫反射
從太陽光中著色,并不取決于攝像機的位置。旋轉場景時,將旋轉世界,包括太陽。太陽的位置將位于世界空間中,您需要將模型的法線放入同一世界空間,以便能夠根據太陽光方向計算點積。事實上,我們可以選擇任何空間,只需要把兩個點乘的向量變換到同一個空間即可。
為了能夠在 fragment 函數中評估表面的斜率,您需要重新定位 vertex 函數中的法線,其方式與之前重新定位頂點位置的方式大致相同。您需要將法線添加到頂點描述符中,以便頂點函數可以處理它們。
? 打開 ShaderDefs.h,并將這些屬性添加到 VertexOut 中:
float3 worldPosition;
float3 worldNormal;
它們將持有世界空間中的頂點位置和頂點法線。
計算法線的新位置與頂點位置計算略有不同。MathLibrary.swift 包含一個 matrix 方法,用于從另一個矩陣創建普通矩陣。這個法線矩陣是一個 3×3 矩陣,因為首先,您將在不需要投影的世界空間中進行光照,其次,平移對象不會影響法線的斜率。因此,您不需要第四個 W 維度。但是,如果沿一個方向(非線性)縮放對象,則對象的法線不再是正交的,因此此方法將不起作用。只要你決定你的引擎不允許非線性縮放,那么你可以使用模型矩陣左上角的 3×3 部分,這就是你在這里要做的。
? 打開 Common.h 并將此矩陣屬性添加到 Uniforms:?
matrix_float3x3 normalMatrix;
這將在世界空間中保存法線矩陣。
? 在 Game 組中,打開 Rendering.swift,在render(encoder:uniforms:params:)中設置 uniforms.modelMatrix:后添加這個
uniforms.normalMatrix = uniforms.modelMatrix.upperLeft
這將從模型矩陣創建法線矩陣。
? 打開 Vertex.metal,在 vertex_main 中,分配position后,添加以下內容:
float4 worldPosition = uniforms.modelMatrix * in.position;
在轉換為相機和投影空間之前,您可以持有頂點的世界位置。
? 定義 out 時,填充 VertexOut 屬性:
.worldPosition = worldPosition.xyz / worldPosition.w,
.worldNormal = uniforms.normalMatrix * in.normal
光柵器按position執行透視除法,如第 6 章 “坐標空間”中所述。要確保處理所有縮放問題,請在此處對 worldPosition除以 w。
在本章的前面部分,您將 LightBuffer索引緩沖區中Renderer 的 lights 數組發送到fragment 函數,但您尚未更改 fragment 函數以接收該數組。
? 打開 Fragment.metal 并將以下內容添加到 fragment_main 的參數列表中:
constant Light *lights [[buffer(LightBuffer)]],
使用c++創建著色函數
通常,您需要從多個文件訪問 C++ 函數。光照函數是您可能希望分離出來的一些函數的一個很好的示例,因為您可以擁有各種光照模型,這些模型可能會調用一些相同的代碼。
要從多個 .metal 文件中調用函數:
1. 使用要創建的函數的名稱設置頭文件。
2. 創建一個新的 .metal 文件并導入頭文件,如果您打算使用該文件中的結構體,則還要導入橋接頭文件 Common.h。
3. 在此新文件中創建光照函數。
4. 在現有的 .metal 文件中,導入新的頭文件并使用光照函數。
在 Shaders 組中,創建一個名為 Lighting.h 的新 Header File。不要將其添加到target中。
? 在 #endif /* 之前添加此函數頭 Lighting_h */:
#import "Common.h"
float3 phongLighting(float3 normal,float3 position,constant Params ¶ms,constant Light *lights,float3 baseColor);
在這里,您將定義一個將返回 float3 的 C++ 函數。
在 Shaders 組中,創建一個名為 Lighting.metal 的新 Metal 文件。將其添加到target。
? 添加此新功能:
#import "Lighting.h"
float3 phongLighting(float3 normal,float3 position,constant Params ¶ms,constant Light *lights,float3 baseColor) {return float3(0);
}
您創建一個返回float3 零值的新函數。您將在 phongLighting 中構建代碼來計算這個最終的光照值。
? 打開 Fragment.metal,#import “Common.h” 替換為:
#import "Lighting.h"
現在,您將能夠在此文件中使用 phongLighting。
? 在 fragment_main 中,替換 return float4(baseColor, 1);為:
float3 normalDirection = normalize(in.worldNormal);
float3 color = phongLighting(normalDirection,in.worldPosition,params,lights,
baseColor );
return float4(color, 1);
在這里,您將世界法線設置為單位向量,并使用必要的參數調用新的光照函數。
如果您現在構建并運行該應用程序,您的模型將呈現為黑色,因為這是您當前從 phongLighting 返回的顏色。
? 打開 Lighting.metal,并替換 return float3(0);為:
float3 diffuseColor = 0;
float3 ambientColor = 0;
float3 specularColor = 0;
for (uint i = 0; i < params.lightCount; i++) {Light light = lights[i];switch (light.type) {
case Sun: {
break; }
case Point: {
break; }
case Spot: {
break; }case Ambient: {
break; }case unused: {
break; }
} }
return diffuseColor + specularColor + ambientColor;
?這里是您計算所有光照的代碼框架。您將累積得到最終的片段顏色,由漫反射、鏡面反射和環境光組成。
? 在 case Sun 的break前面,添加以下內容:
// 1
float3 lightDirection = normalize(-light.position);
// 2
float diffuseIntensity =saturate(-dot(lightDirection, normal));
// 3
diffuseColor += light.color * baseColor * diffuseIntensity;
瀏覽此代碼:?
1. 將光線的方向設為單位向量。
2. 計算兩個向量的點積。當片段完全指向光線時,點積將為 -1。讓這個值為正數,更易于進一步計算,因此您取點積的負值。saturate 通過截斷負數來確保該值介于 0 和 1 之間。這為您提供了表面的斜率,從而提供了漫反射的強度。
3. 將基礎顏色乘以漫反射強度,以獲得漫反射著色。如果有多個太陽光,則 diffuseColor 將累積漫反射著色。
? 構建并運行應用程序。
您可以通過從 phongLighting 返回中間計算來對結果進行健全性檢查。下圖顯示了前視圖中的 normal 和 diffuseIntensity。
注意:要在應用程序中獲取前視圖,請在運行時按 Alpha 鍵上方的“1”。“2” 將重置為默認視圖。
Utility 組中的 DebugLights.swift 和 DebugLights.metal 具有一些調試方法,以便您可以直觀地了解光源的位置。
? 打開 DebugLights.swift,并刪除文件頂部和底部的 /* 和 */。在本章中添加代碼之前,此文件不會編譯,但現在可以編譯。
? 打開 Renderer.swift,在 draw(scene:in:) 的末尾,在 renderEncoder.endEncoding() 之前,添加以下內容:
DebugLights.draw(lights: scene.lighting.lights,encoder: renderEncoder,uniforms: uniforms)
此代碼將顯示線條以可視化太陽光的方向。
? 構建并運行應用程序。
紅線顯示平行太陽光方向矢量。旋轉場景時,可以看到最亮的部分是面向太陽的部分。
注意:調試方法使用 .line 作為渲染類型。遺憾的是,線寬在 GPU 上是不可配置的,它們可能會在某些角度,因線條太細而無法渲染時消失。
這個著色效果令人愉悅,但不準確。看看球體的背面。球體的背面是黑色的;但是,您可以看到綠色環繞的頂部是亮綠色的,因為它朝上。在現實世界中,周圍環境會被球體阻擋,因此處于陰影中。但是,您目前沒有考慮遮擋,只有在第 13 章 “陰影” 中掌握陰影后才考慮這個問題。
環境反射
在現實世界中,顏色很少是純黑色。到處都是光線反射。要模擬這種情況,您可以使用環境光照。您將找到場景中燈光的平均顏色,并將其應用于場景中的所有表面。
? 打開 SceneLighting.swift,并添加一個 ambient light 屬性:
let ambientLight: Light = {var light = Self.buildDefaultLight()light.color = [0.05, 0.1, 0]light.type = Ambientreturn light
}()
此光照略帶綠色。
? 將以下內容添加到 init() 的末尾:
lights.append(ambientLight)
? 打開 Lighting.metal,case Ambient的break上面,添加以下內容:
ambientColor += light.color;
? 構建并運行應用程序。場景現在呈綠色,就像有綠燈在周圍反射一樣。
鏡面反射
在 Phong 反射模型中,最后但并非最不重要的一點是鏡面反射。您現在有機會在球體上涂上一層閃亮的清漆。鏡面高光取決于觀察者的位置。如果您經過一輛閃亮的汽車,您只會在某些角度看到高光。
光線進入 (L) 并被繞著法線 (N)反射 (R) 。如果觀察者 (V) 位于反射 (R) 周圍的特定圓錐體內,則觀察者將看到鏡面高光。該圓錐體是指數光澤度參數。表面越閃亮,鏡面反射高光就越小、越強烈。
在本例中,觀察者是您的相機,因此您需要將相機坐標再次傳遞到 fragment 函數。之前,您在 params 中設置了一個 cameraPosition 屬性,您將使用它來傳遞相機位置。
? 打開 Renderer.swift,然后在 updateUniforms(scene:) 中添加以下內容:
params.cameraPosition = scene.camera.position
scene.camera.position 已在世界空間中,并且您已將參數傳遞給 fragment 函數,因此您無需在此處執行進一步操作。
? 打開 Lighting.metal,然后在 phongLighting 中,將以下變量添加到函數頂部:
float materialShininess = 32;
float3 materialSpecularColor = float3(1, 1, 1);
它們包含光澤度因子和鏡面反射顏色的表面材質屬性。由于這些是表面屬性,您應該從每個模型的材質中獲取這些值,您將在下一章中執行此操作。
? 在 case Sun的break前面添加以下內容:
if (diffuseIntensity > 0) {// 1 (R)float3 reflection =reflect(lightDirection, normal);
// 2 (V)float3 viewDirection =normalize(params.cameraPosition);// 3float specularIntensity =pow(saturate(dot(reflection, viewDirection)),materialShininess);specularColor +=light.specularColor * materialSpecularColor* specularIntensity;
}
瀏覽此代碼:
1. 為了計算鏡面反射顏色,您需要 (L) ight、(R) eflection、(N) ormal 和 (V)iew。您已經有 (L) 和 (N),因此在這里使用 Metal Shading Language 函數 reflect 來獲取 (R)。
2. 您需要 (V) 片段和相機之間的視圖向量。
3. 現在,您計算鏡面反射強度。您可以使用點積找到反射和視圖之間的角度,使用 saturate 將結果限制在 0 和 1 之間,并使用 pow 將結果提高到光澤度。然后,您可以使用此強度來確定片段的鏡面反射顏色。
? 構建并運行應用程序以查看您完成的照明。
?
嘗試將材質光澤度從 2 更改為 1600。在第11章“地圖和材質”中,您將了解如何從模型中讀取材質和紋理屬性以更改其顏色和照明。
您已經為太陽光創建了足夠逼真的光照情況。您可以使用點光源和聚光燈為場景添加更多變化。
點光源
在太陽光中,您將位置轉換為平行方向向量。與太陽光相反,而點光源則向所有方向發射光線。
燈泡只會照亮一定半徑的區域,超過該半徑后,一切都是黑暗的。因此,您還將指定光線不會無限傳播的衰減。
光線衰減可以突然或逐漸發生。衰減的原始 OpenGL 公式為:
其中 x 是常數衰減因子,y 是線性衰減因子,z 是二次衰減因子。
該公式給出了彎曲的衰減。您將用 float3 表示 xyz。完全沒有衰減將是 float3(1, 0, 0) — 將 x、y 和 z 代入公式得到值 1。
? 打開 SceneLighting.swift,并向 SceneLighting 添加點光源屬性:
let redLight: Light = {var light = Self.buildDefaultLight()light.type = Pointlight.position = [-0.8, 0.76, -0.18]light.color = [1, 0, 0]light.attenuation = [0.5, 2, 1]return light
}()
聚光
在本章中,您將創建的最后一種光源類型是聚光燈。這會向有限的方向發送光線。想想手電筒,光線從一個小點發出,但當它照射到地面時,它是一個更大的橢圓。
您可以定義圓錐體角度以包含具有圓錐體方向的光線。我們還要定義一個衰減冪次來控制橢圓邊緣的光照衰減。
打開 SceneLighting.swift,添加一個新的光照對象:
lazy var spotlight: Light = {var light = buildDefaultLight()light.position = [0.4, 0.8, 1]light.color = [1, 0, 1]light.attenuation = float3(1, 0.5, 0)light.type = Spotlightlight.coneAngle = Float(40).degreesToRadianslight.coneDirection = [-2, 0, -1.5]light.coneAttenuation = 12return light
}()
本光照和點光源有點類似,不過增加了圓錐角度,方向,以及圓錐衰減因子。
在init(metalView) 添加這個光照到光照數組中。
lights.append(spotlight)
打開 Lighting.metal,然后在 phongLighting 中,在 case Spot 的 break 上方添加以下代碼:
// 1
float d = distance(light.position, position);
float3 lightDirection = normalize(light.position - position);
// 2
float3 coneDirection = normalize(light.coneDirection);
float spotResult = dot(lightDirection, -coneDirection);
// 3
if (spotResult > cos(light.coneAngle)) {float attenuation = 1.0 / (light.attenuation.x +light.attenuation.y * d + light.attenuation.z * d * d);
// 4attenuation *= pow(spotResult, light.coneAttenuation);float diffuseIntensity =saturate(dot(lightDirection, normal));float3 color = light.color * baseColor * diffuseIntensity;color *= attenuation;diffuseColor += color;
}
此代碼與點光源代碼非常相似。瀏覽注釋:
1. 計算距離和方向,就像計算點光源一樣。這束光可能在聚光錐體外。
2. 計算該光線方向與聚光源指向的方向之間的余弦角(即點積)。
3. 如果該結果超出圓錐角,則忽略該射線。否則,計算點光源的衰減。指向同一方向的向量的點積為 1.0。
4. 使用 coneAttenuation 作為冪次來計算聚光源邊緣的衰減。
? 構建并運行應用程序。
?
嘗試更改各種衰減值。錐體角度為 5°,然后衰減向量為(1, 0, 0),錐體衰減因子為 1000 將產生非常小的聚焦柔光;而 20°的錐體角度和 1 的錐體衰減因子將產生銳利的圓形光。
參考
https://zhuanlan.zhihu.com/p/391592709
https://zhuanlan.zhihu.com/p/392622099