【从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|))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);
复制代码- hlsl
- float4 cubemapColor = PLATFORM_SAMPLE_TEXTURECUBE(_Cubemap, sampler_Cubemap, reflectDir);
复制代码- 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
- 确定采样面:
- 计算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中的环境反射和高级材质效果提供了强大的支持,通过合理使用可以显著提升场景的真实感和视觉质量
<blockquote>
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |