Metal學習筆記九:光照基礎

光和陰影是使場景流行的重要要求。通過一些著色器藝術,您可以突出重要的對象、描述天氣和一天中的時間并設置場景的氣氛。即使您的場景由卡通對象組成,如果您沒有正確地照亮它們,場景也會變得平淡無奇。

最簡單的光照方法之一是 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 &params,constant Light *lights,float3 baseColor);

在這里,您將定義一個將返回 float3 的 C++ 函數。
在 Shaders 組中,創建一個名為 Lighting.metal 的新 Metal 文件。將其添加到target。
? 添加此新功能:

#import "Lighting.h"
float3 phongLighting(float3 normal,float3 position,constant Params &params,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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/896646.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/896646.shtml
英文地址,請注明出處:http://en.pswp.cn/news/896646.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JAVA學習筆記038——bean的概念和常見注解標注

什么是bean? Bean 就是 被 Spring 管理的對象&#xff0c;就像工廠流水線上生產的“標準產品”。這些對象不是你自己 new 出來的&#xff0c;而是由 Spring 容器&#xff08;一個超級工廠&#xff09;幫你創建、組裝、管理。 由 Component、Service、Controller 等注解標記的…

start DL from stratch (2)!!!

start DL from stratch &#xff08;2&#xff09;!!! 一、CPU and GPUcpuGPU安培架構愛達洛夫萊斯架構 二、使用conda創建一個新的虛擬環境三、autodl操作先知Linux復習目錄文件和數據上傳對于整個鏡像的操作守護進程Tips 四、autodl租用創建實例<big>沒有所需要的版本的…

機器學習:線性回歸,梯度下降

線性回歸模型 (Linear Regression Model) 梯度下降算法 (Gradient Descent Algorithm) 的數學公式

論文筆記-NeurIPS2017-DropoutNet

論文筆記-NeurIPS2017-DropoutNet: Addressing Cold Start in Recommender Systems DropoutNet&#xff1a;解決推薦系統中的冷啟動問題摘要1.引言2.前言3.方法3.1模型架構3.2冷啟動訓練3.3推薦 4.實驗4.1實驗設置4.2在CiteULike上的實驗結果4.2.1 Dropout率的影響4.2.2 實驗結…

nvm的學習

學習 nvm&#xff08;Node Version Manager&#xff09; 是掌握 Node.js 開發的關鍵技能之一。以下是系統的學習路徑和實戰指南&#xff0c;涵蓋從基礎到進階的內容&#xff1a; 一、基礎入門 1. nvm 的核心作用 多版本共存&#xff1a;安裝和管理多個 Node.js 版本&#xff…

GPT-4.5實際性能評測:實際探索

摘要 經過數萬輪嚴格測試&#xff0c;GPT-4.5的性能并未超越其前代產品GPT-4。此前發布的《GPT-4.5 一手實測&#xff1a;垃圾》一文中存在不準確描述&#xff0c;在此向讀者致歉。盡管GPT-4.5在價格上有所提升且響應速度較慢&#xff0c;但測試結果顯示其模型素質并未達到預期…

從UNIX到Linux:操作系統進化史與開源革命

從UNIX到Linux&#xff1a;操作系統進化史與開源革命 一、操作系統&#xff1a;數字世界的基石 1.1 什么是操作系統&#xff1f; 操作系統&#xff08;OS&#xff09;是計算機系統的核心管理者&#xff0c;承擔著三大核心使命&#xff1a; 硬件指揮官&#xff1a;直接管理C…

如何修改安全帽/反光衣檢測AI邊緣計算智能分析網關V4的IP地址?

TSINGSEE青犀推出的智能分析網關V4&#xff0c;是一款集成了BM1684芯片的高性能AI邊緣計算智能硬件。其內置的高性能8核ARM A53處理器&#xff0c;主頻可高達2.3GHz&#xff0c;INT8峰值算力更是達到了驚人的17.6Tops。此外&#xff0c;該硬件還預裝了近40種AI算法模型&#xf…

【全棧開發】----Mysql基本配置與使用

本篇是在已下載Mysql的情況下進行的&#xff0c;若還未下載或未創建Mysql服務&#xff0c;請轉到這篇: 2024 年 MySQL 8.0.40 安裝配置、Workbench漢化教程最簡易&#xff08;保姆級&#xff09;_mysql8.0.40下載安裝教程-CSDN博客 本文對于mysql的操作均使用控制臺sql原生代碼…

C++ primer plus 第四節 復合類型

本章內容包括: ? 創建和使用數組 ? 創建和使用 c-風格字符串 ? 創建和使用 string 類字符串 ? 使用方法getline( )和 get( )讀取字符串 ? 混合輸入字符串和數字 ? 創建和使用結構 ? 創建和使用共用休 ? 創建和使用枚舉 ? 創建和使用指針 ? 使用 new和delete 管理動態…

Java中的泛型類 --為集合的學習做準備

學習目標 ● 掌握在集合中正確使用泛型 ● 了解泛型類、泛型接口、泛型方法 ● 了解泛型上下限 ● 了解基本的使用場景 1.有關泛型 1.1泛型的概念 泛型&#xff08;Generics&#xff09;是Java中引入的參數化類型機制&#xff0c;允許在定義類、接口或方法時使用類型參數&a…

VUE3+Vite使用TailwindCSS【若依前后端分離框架】

參考&#xff1a;https://tailwind.nodejs.cn/docs/guides/vite#vue 和 https://blog.csdn.net/hjl_and_djj/article/details/144694485依次運行命令&#xff1a; cnpm install -D tailwindcss3.4.17 postcss autoprefixernpx tailwindcss init -p修改配置文件tailwind.config.…

FFmpeg入門:最簡單的音頻播放器

FFmpeg入門&#xff1a;最簡單的音頻播放器 歡迎大家來到FFmpeg入門的第二章&#xff0c;今天只做一個最簡單的FFmpeg音頻播放器&#xff1b;同樣&#xff0c;話不多說&#xff0c;先上流程圖 流程圖 以上流程和視頻播放器的解碼過程基本上是一致的&#xff1b; 不同點在于 S…

在Ubuntu下,源碼編譯安裝Python

在Ubuntu下&#xff0c;源碼編譯安裝Python 知識點 知識點1&#xff1a;在 Linux 系統里&#xff0c;/usr 目錄通常用于存放一些共享的、只讀的程序和數據&#xff0c;是系統安裝軟件的一個重要位置。而 /usr/src 目錄一般是用來存放系統源代碼以及一些軟件包的源代碼的地方 …

《每天讀一個JDK源碼》之HashMap解讀

&#x1f4cc;《每天讀一個JDK源碼》之HashMap解讀 &#x1f517;源碼定位&#xff1a;java.util.HashMap&#xff08;建議IDE對照閱讀&#xff09; 今天我們來破解Java集合框架中最精妙的藝術品——HashMap&#xff01;它不僅是面試必考題&#xff08;出現率99%&#xff09;&…

【Java項目】基于SpringBoot的Java學習平臺

【Java項目】基于SpringBoot的Java學習平臺 技術簡介&#xff1a;采用Java技術、SpringBoot框架、MySQL數據庫等實現。系統基于B/S架構&#xff0c;前端通過瀏覽器與后端數據庫進行信息交互&#xff0c;后端使用SpringBoot框架和MySQL數據庫進行數據處理和存儲&#xff0c;實現…

使用ChatGPT-Deep Reaserch兩步給出文獻綜述!

文獻綜述是學術論文寫作中不可或缺的一部分&#xff0c;它不僅是對已有研究的梳理和總結&#xff0c;更是為后續研究奠定理論基礎的關鍵步驟。通過文獻綜述研究者能夠全面了解當前研究領域的現狀、主要觀點和研究方法&#xff0c;從而找到自己研究的切入點和創新點。這一過程需…

java基礎知識(理論篇)

一、java介紹 1.1Java語言 Java 是一種廣泛使用的、通用的、面向對象的編程語言&#xff0c;Java 的設計目標是“一次編寫&#xff0c;到處運行”&#xff0c;這也這意味著 Java 程序可以在任何支持 Java 的平臺&#xff08;如 Windows、Linux、macOS 等&#xff09;上運行。 …

金融賦能紹興紡織 民生銀行助力外貿中小微企業“走出去”

在浙江紹興&#xff0c;紡織業作為一張熠熠生輝的產業名片&#xff0c;承載著深厚的歷史底蘊與蓬勃的發展活力。這里依傍長三角經濟圈&#xff0c;交通網絡縱橫交錯&#xff0c;將原材料產地與廣闊市場緊密相連&#xff1b;產業集群高度成熟&#xff0c;上下游產業鏈完備&#…

綜合實驗處理表格

新建excel表格&#xff0c;輸入信息&#xff0c;另存為csv文件。 利用notepad打開csv文件&#xff0c;可以觀察格式 目標&#xff1a;通過編程處理文件&#xff0c;實現對數據的處理&#xff0c;成績求和以及評價 對數據逐行處理&#xff0c;讀一行&#xff0c;處理一行&#…