【光照】Unity如何在Cubemap中采樣反射信息?
【從UnityURP開始探索游戲渲染】專欄-直達
介紹與發展歷史
Cubemap(立方體貼圖)是一種由六個獨立的正方形紋理組成的集合,它將多個紋理組合起來映射到一個單一紋理。Cubemap包含6個2D紋理,每個2D紋理代表立方體的一個面,形成一個有貼圖的立方體。
Cubemap技術起源于早期的3D圖形學,最初用于實現天空盒效果。隨著硬件性能的提升和圖形API的發展,Cubemap逐漸被廣泛應用于環境映射、反射和折射等高級渲染效果中。
應用領域
Cubemap在Unity中主要有以下應用場景:
- ?環境反射?:用于模擬金屬、玻璃等具有反射屬性物體的反射效果
- ?天空盒?:創建環境背景,提供場景的全局光照信息
- ?折射效果?:模擬透明材質的折射現象
- ?全局光照?:作為間接光照的來源之一
采樣反射信息原理
基本原理
Cubemap采樣的核心原理是使用方向向量進行索引和采樣。想象一個1×1×1的單位立方體,中心位于原點。當從原點發出一個方向向量時,該向量會與立方體的某個面相交,交點處的紋理就是采樣結果。
反射計算原理
- 反射效果的計算過程如下:
- 計算相機指向物體表面點的向量(視線向量)
- 根據表面法線計算反射向量
- 使用反射向量在Cubemap中進行采樣(URP中使用宏
SAMPLE_TEXTURECUBE實現采樣,這個宏實際調用PLATFORM_SAMPLE_TEXTURECUBE宏來處理不同圖形API的采樣方法,下面有詳細講原理講解。) - 將采樣顏色與物體本身的顏色混合
數學上,反射向量R可以通過以下公式計算:
R = I - 2 * dot(N, I) * N
其中I是入射向量(視線向量取反),N是表面法線。
PLATFORM_SAMPLE_TEXTURECUBE的實現原理
PLATFORM_SAMPLE_TEXTURECUBE是Unity URP中用于跨平臺Cubemap采樣的關鍵宏,它封裝了不同圖形API(Direct3D、OpenGL、Metal)下Cubemap采樣的實現差異,為開發者提供統一的采樣接口。
數學原理基礎
Cubemap是一個由6個2D紋理組成的立方體,每個面代表一個方向(±X, ±Y, ±Z)。使用反射向量作為方向索引,可以獲取立方體相應位置的紋理顏色
1. 方向向量確定采樣面
Cubemap采樣基于3D方向向量,通過以下步驟確定采樣面:
數學表達式:
主面 = max(|x|, |y|, |z|)
if (主面 == |x|)
if (x > 0) → +X面
else → -X面
else if (主面 == |y|)
if (y > 0) → +Y面
else → -Y面
else
if (z > 0) → +Z面
else → -Z面
## **2. 方向向量到UV坐標轉換**
確定采樣面后,將3D方向向量轉換為2D UV坐標的數學過程:
對于+X面(右面):
u = 0.5 * (1 - (z / |x|))
v = 0.5 * (1 - (y / |x|))
對于-Y面(下面):
u = 0.5 * (1 - (x / |y|))
v = 0.5 * (1 + (z / |y|))
其他面的轉換類似,但需要考慮不同面的坐標系差異。
## **URP中的具體實現**
### **PLATFORM_SAMPLE_TEXTURECUBE宏定義**
在URP Core.hlsl中,該宏通常定義為:
```c
hlsl
#define PLATFORM_SAMPLE_TEXTURECUBE(textureName, samplerName, coord3) \
SampleTexture(textureName, samplerName, coord3)
實際采樣函數會根據平臺不同而有所區別,但對外提供統一接口。
反射向量計算示例
在URP Shader中計算反射向量并采樣Cubemap的完整過程:
- 計算視線向量(從表面點到相機):
hlsl
float3 viewDir = GetWorldSpaceViewDir(positionWS);
- 計算反射向量:
hlsl
float3 reflectDir = reflect(-viewDir, normalWS);
- 采樣Cubemap:
hlsl
float4 cubemapColor = PLATFORM_SAMPLE_TEXTURECUBE(_Cubemap, sampler_Cubemap, reflectDir);
- 解碼HDR顏色(如果需要):
hlsl
float3 reflection = DecodeHDREnvironment(cubemapColor, _Cubemap_HDR);
- 混合反射顏色與表面顏色:
hlsl
float3 finalColor = lerp(diffuseColor, reflection, _ReflectAmount);
不同圖形API的實現差異
Direct3D
- 面順序:+X, -X, +Y, -Y, +Z, -Z
- UV坐標系:左上角為(0,0),右下角為(1,1)
- 需要特別注意Y軸的朝向
OpenGL
- 面順序:+X, -X, +Y, -Y, +Z, -Z
- UV坐標系:左下角為(0,0),右上角為(1,1)
- 通常需要翻轉Y軸坐標
Metal
- 使用MTLTextureTypeCube類型
- 面順序與Direct3D類似
- 采樣時需要考慮坐標系轉換
數學示例:具體采樣過程
假設有一個方向向量(0.5, -0.3, 0.8),計算其在Cubemap中的采樣位置:
- 確定主分量:
- |x|=0.5, |y|=0.3, |z|=0.8 → 主分量為z
- 確定采樣面:
- z=0.8 > 0 → +Z面
- 計算UV坐標:
- u = 0.5 * (1 + (x/|z|)) = 0.5 * (1 + (0.5/0.8)) ≈ 0.8125
- v = 0.5 * (1 - (y/|z|)) = 0.5 * (1 - (-0.3/0.8)) ≈ 0.6875
- 在+Z面紋理上采樣(0.8125, 0.6875)處的顏色
性能優化考慮
-
?粗糙度與Mipmap?:根據表面粗糙度選擇適當的Mipmap級別,粗糙表面使用更高層級的Mipmap
float perceptualRoughness = 1.0 - _Smoothness; float mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness); float4 envColor = SAMPLE_TEXTURECUBE_LOD(_Cubemap, sampler_Cubemap, reflectDir, mip);
URP中的PLATFORM_SAMPLE_TEXTURECUBE宏通過統一這些復雜操作,使開發者能夠專注于材質效果本身,而無需關心底層平臺差異
實現示例
以下是一個簡單的反射Cubemap的Shader實現:
-
定義Shader屬性,包括顏色、反射顏色、反射強度和Cubemap紋理
-
頂點著色器計算世界空間的位置、法線、視線方向和反射方向
-
片元著色器計算環境光、漫反射和反射顏色
-
使用lerp函數混合漫反射和反射顏色,控制反射強度
-
最終輸出混合后的顏色
-
URPReflection.shader
Shader "Custom/URPReflection" { Properties { _BaseColor("Base Color", Color) = (1,1,1,1) _ReflectColor("Reflection Color", Color) = (1,1,1,1) _ReflectAmount("Reflect Amount", Range(0,1)) = 0.5 _Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} _Smoothness("Smoothness", Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; }; struct Varyings { float4 positionHCS : SV_POSITION; float3 positionWS : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 viewDirWS : TEXCOORD2; float3 reflectDir : TEXCOORD3; }; CBUFFER_START(UnityPerMaterial) half4 _BaseColor; half4 _ReflectColor; half _ReflectAmount; half _Smoothness; CBUFFER_END TEXTURECUBE(_Cubemap); SAMPLER(sampler_Cubemap); Varyings vert(Attributes IN) { Varyings OUT; // 頂點變換 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); // 計算視線方向(從表面點到相機) OUT.viewDirWS = GetWorldSpaceViewDir(OUT.positionWS); // 計算反射方向 float3 viewDir = normalize(OUT.viewDirWS); OUT.reflectDir = reflect(-viewDir, normalize(OUT.normalWS)); return OUT; } half4 frag(Varyings IN) : SV_Target { // 標準化法線和反射方向 float3 normalWS = normalize(IN.normalWS); float3 reflectDir = normalize(IN.reflectDir); // 計算漫反射光照 Light light = GetMainLight(); float3 lightDir = normalize(light.direction); float NdotL = saturate(dot(normalWS, lightDir)); half3 diffuse = _BaseColor.rgb * light.color * NdotL; // 采樣Cubemap half perceptualRoughness = 1.0 - _Smoothness; half mip = PerceptualRoughnessToMipmapLevel(perceptualRoughness); half4 cubemapColor = SAMPLE_TEXTURECUBE_LOD(_Cubemap, sampler_Cubemap, reflectDir, mip); half3 reflection = DecodeHDREnvironment(cubemapColor, unity_SpecCube0_HDR) * _ReflectColor.rgb; // 混合漫反射和反射 half3 color = lerp(diffuse, reflection, _ReflectAmount); return half4(color, 1.0); } ENDHLSL } }
Cubemap生成方法
在Unity中可以通過以下步驟生成Cubemap:
- 創建空物體作為觀察位置
- 在Project視圖右鍵創建Legacy/Cubemap
- 使用腳本調用Camera.RenderToCubemap方法生成
- 確保Cubemap勾選Readable選項以便腳本讀取
Cubemap技術為Unity中的環境反射和高級材質效果提供了強大的支持,通過合理使用可以顯著提升場景的真實感和視覺質量
【從UnityURP開始探索游戲渲染】專欄-直達
(歡迎點贊留言探討,更多人加入進來能更加完善這個探索的過程,??)

Cubemap是游戲渲染中常用的技術,由6個2D紋理組成立方體,用于環境映射、反射和折射效果。其核心原理是利用方向向量進行紋理采樣,通過反射公式R=I-2*dot(N,I)*N計算反射向量。Unity URP通過PLATFORM_SAMPLE_TEXTURECUBE宏統一不同圖形API的采樣差異,簡化開發流程。實現時需考慮平臺特性(如Direct3D/OpenGL的坐標系差異)和性能優化(如粗糙度與Mipmap級別匹配)。示例Shader展示了如何將Cubemap反射與表面顏色混合,創建真實感材質效果。
浙公網安備 33010602011771號