第七章 现代游戏中的真实感渲染:Unity光照系统全面解析
第七章 现代游戏中的真实感渲染:Unity光照系统全面解析
7.1 人类视觉感知与虚拟环境中的光照原理
要在Unity中创建真实感的场景,我们首先需要理解人类如何感知现实世界中的光线。本节将探讨光照的基本原理及其在游戏引擎中的实现方式,为后续章节奠定理论基础。
7.1.1 光照物理学与虚拟光源类型
光是电磁辐射的一种形式,它使我们能够看到周围的世界。在现实中,光从光源发出,在空间中传播,与物体表面交互,最终进入我们的眼睛,形成我们所见的图像。Unity中的虚拟光照系统试图模拟这一过程。
在Unity 2021.3.8f1c1 LTS中,支持以下几种基本光源类型:
- 定向光 (Directional Light):模拟来自无限远处的平行光线,如太阳光。
- 点光源 (Point Light):从一个点向所有方向发射光线,如灯泡。
- 聚光灯 (Spot Light):从一个点发射到特定方向的圆锥形光线,如手电筒。
- 区域光 (Area Light):从一个矩形面积发射光线,只在烘焙光照时可用。
csharp
// 在C#脚本中创建和控制不同类型的光源using UnityEngine;public class LightController : MonoBehaviour{ // 引用不同类型的光源 public Light directionalLight; public Light pointLight; public Light spotLight; public Light areaLight; // 光源参数控制 [Range(0, 10)] public float intensity = 1.0f; [Range(1000, 10000)] public float temperature = 6500f; // 色温(开尔文) void Start() { // 确保光源存在 if(directionalLight == null) { GameObject dlObj = new GameObject(\"Directional Light\"); directionalLight = dlObj.AddComponent(); directionalLight.type = LightType.Directional; dlObj.transform.rotation = Quaternion.Euler(50, -30, 0); } if(pointLight == null) { GameObject plObj = new GameObject(\"Point Light\"); pointLight = plObj.AddComponent(); pointLight.type = LightType.Point; plObj.transform.position = new Vector3(3, 2, 0); } // 配置光源属性 SetupLights(); } void Update() { // 实时更新光源属性 UpdateLights(); } void SetupLights() { // 设置基本属性 directionalLight.intensity = intensity; directionalLight.useColorTemperature = true; directionalLight.colorTemperature = temperature; directionalLight.shadows = LightShadows.Soft; pointLight.intensity = intensity * 2; pointLight.range = 10; pointLight.color = Color.yellow; pointLight.shadows = LightShadows.Soft; if(spotLight != null) { spotLight.type = LightType.Spot; spotLight.intensity = intensity * 3; spotLight.range = 15; spotLight.spotAngle = 30; spotLight.color = Color.blue; spotLight.shadows = LightShadows.Soft; } if(areaLight != null) { // 区域光仅在烘焙时有效 areaLight.type = LightType.Rectangle; areaLight.intensity = intensity * 2; areaLight.color = Color.white; areaLight.areaSize = new Vector2(5, 5); } } void UpdateLights() { // 随时间变化光照强度,创建动态效果 float pulsatingIntensity = intensity * (1.0f + 0.2f * Mathf.Sin(Time.time * 2.0f)); // 更新光源属性 directionalLight.intensity = pulsatingIntensity; pointLight.intensity = pulsatingIntensity * 2; if(spotLight != null) spotLight.intensity = pulsatingIntensity * 3; }}
在着色器中,我们需要理解和使用Unity提供的光源数据:
glsl
// 在着色器中获取和使用光源信息Shader \"Custom/LightInteraction\"{ Properties { _MainTex (\"Texture\", 2D) = \"white\" {} _Glossiness (\"Smoothness\", Range(0,1)) = 0.5 _Metallic (\"Metallic\", Range(0,1)) = 0.0 } SubShader { Tags { \"RenderType\"=\"Opaque\" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; half _Glossiness; half _Metallic; struct Input { float2 uv_MainTex; float3 worldPos; // 获取世界坐标用于光照计算 }; void surf (Input IN, inout SurfaceOutputStandard o) { // 基本纹理采样 fixed4 c = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; // 演示:可以根据世界位置调整表面属性 // 例如,根据高度创建垂直渐变效果 float heightFactor = saturate((IN.worldPos.y + 1) / 2); // 假设范围在-1到1 o.Albedo *= lerp(fixed3(0.7, 0.7, 0.7), fixed3(1, 1, 1), heightFactor); // 表面属性还可以随时间变化 float timeFactor = sin(_Time.y) * 0.5 + 0.5; o.Emission = c.rgb * timeFactor * 0.2; // 轻微的自发光效果 } ENDCG } FallBack \"Diffuse\"}
7.1.2 光线与表面材质的相互作用
当光线击中物体表面时,会发生几种主要的相互作用:吸收、反射和透射。物体的材质属性决定了这些相互作用的性质。
吸收:物体表面吸收一部分光线,这决定了物体的颜色。例如,红色物体会吸收大部分非红色波长的光线,反射红色波长的光线。
反射:未被吸收的光线会从表面反射出去。反射可以是漫反射(朝各个方向均匀散射)或镜面反射(以特定角度反射)。
透射:一些材质(如玻璃)允许光线穿过,可能会发生折射。
csharp
// 实现简单的表面材质属性编辑器using UnityEngine;using UnityEditor;public class MaterialPropertyEditor : MonoBehaviour{ public Material targetMaterial; [Range(0, 1)] public float albedo = 0.8f; // 漫反射系数 [Range(0, 1)] public float metallic = 0.0f; // 金属度 [Range(0, 1)] public float smoothness = 0.5f; // 光滑度 [Range(0, 1)] public float transparency = 0.0f; // 透明度 [ColorUsage(true, true)] public Color emissionColor = Color.black; // 自发光颜色 void Update() { if(targetMaterial != null) { // 更新材质属性 Color baseColor = targetMaterial.GetColor(\"_Color\"); targetMaterial.SetColor(\"_Color\", new Color(baseColor.r, baseColor.g, baseColor.b, 1 - transparency)); targetMaterial.SetFloat(\"_Metallic\", metallic); targetMaterial.SetFloat(\"_Glossiness\", smoothness); // 控制自发光 if(emissionColor != Color.black) { targetMaterial.EnableKeyword(\"_EMISSION\"); targetMaterial.SetColor(\"_EmissionColor\", emissionColor); } else { targetMaterial.DisableKeyword(\"_EMISSION\"); } // 设置渲染模式 if(transparency > 0) { targetMaterial.SetFloat(\"_Mode\", 3); // Transparent targetMaterial.SetInt(\"_SrcBlend\", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); targetMaterial.SetInt(\"_DstBlend\", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); targetMaterial.SetInt(\"_ZWrite\", 0); targetMaterial.DisableKeyword(\"_ALPHATEST_ON\"); targetMaterial.EnableKeyword(\"_ALPHABLEND_ON\"); targetMaterial.DisableKeyword(\"_ALPHAPREMULTIPLY_ON\"); targetMaterial.renderQueue = 3000; } else { targetMaterial.SetFloat(\"_Mode\", 0); // Opaque targetMaterial.SetInt(\"_SrcBlend\", (int)UnityEngine.Rendering.BlendMode.One); targetMaterial.SetInt(\"_DstBlend\", (int)UnityEngine.Rendering.BlendMode.Zero); targetMaterial.SetInt(\"_ZWrite\", 1); targetMaterial.DisableKeyword(\"_ALPHATEST_ON\"); targetMaterial.DisableKeyword(\"_ALPHABLEND_ON\"); targetMaterial.DisableKeyword(\"_ALPHAPREMULTIPLY_ON\"); targetMaterial.renderQueue = -1; } } }}#if UNITY_EDITOR[CustomEditor(typeof(MaterialPropertyEditor))]public class MaterialPropertyEditorInspector : Editor{ public override void OnInspectorGUI() { MaterialPropertyEditor editor = (MaterialPropertyEditor)target; // 显示默认Inspector DrawDefaultInspector(); // 添加应用按钮 if(GUILayout.Button(\"创建材质副本\")) { if(editor.targetMaterial != null) { Material newMaterial = new Material(editor.targetMaterial); string path = EditorUtility.SaveFilePanelInProject(\"保存材质\", \"New Material\", \"mat\", \"请选择保存材质的位置\"); if(!string.IsNullOrEmpty(path)) { AssetDatabase.CreateAsset(newMaterial, path); AssetDatabase.SaveAssets(); editor.targetMaterial = newMaterial; } } } }}#endif
7.1.3 色彩科学与色彩空间
色彩是人类视觉感知的一个重要方面。在游戏和计算机图形学中,我们通常使用RGB颜色模型来表示颜色。然而,为了获得真实的渲染结果,理解色彩空间非常重要。
Unity支持两种主要的色彩空间:
- Gamma色彩空间:传统的色彩空间,适合旧平台
- Linear色彩空间:更准确的物理表示,适合现代渲染技术
csharp
// 色彩空间工具类using UnityEngine;public class ColorSpaceUtility : MonoBehaviour{ // 将颜色从Gamma空间转换到Linear空间 public static Color GammaToLinear(Color gammaColor) { return new Color( Mathf.Pow(gammaColor.r, 2.2f), Mathf.Pow(gammaColor.g, 2.2f), Mathf.Pow(gammaColor.b, 2.2f), gammaColor.a ); } // 将颜色从Linear空间转换到Gamma空间 public static Color LinearToGamma(Color linearColor) { return new Color( Mathf.Pow(linearColor.r, 1/2.2f), Mathf.Pow(linearColor.g, 1/2.2f), Mathf.Pow(linearColor.b, 1/2.2f), linearColor.a ); } // 检查当前项目的色彩空间 public static void CheckColorSpace() { string colorSpace = (QualitySettings.activeColorSpace == ColorSpace.Linear) ? \"Linear\" : \"Gamma\"; Debug.Log(\"当前项目色彩空间: \" + colorSpace); if(QualitySettings.activeColorSpace == ColorSpace.Gamma) { Debug.LogWarning(\"为了获得更好的光照和颜色,建议在项目设置中切换到Linear色彩空间。\"); } } void Start() { CheckColorSpace(); // 示例:创建测试颜色 Color testColor = new Color(0.5f, 0.25f, 0.75f); Color linearColor = GammaToLinear(testColor); Debug.Log(\"原始Gamma颜色: \" + testColor); Debug.Log(\"转换为Linear颜色: \" + linearColor); Debug.Log(\"转换回Gamma颜色: \" + LinearToGamma(linearColor)); }}
在着色器中处理色彩空间:
glsl
// 色彩空间处理示例Shader \"Custom/ColorSpaceHandling\"{ Properties { _Color (\"Color\", Color) = (1,1,1,1) _MainTex (\"Albedo (RGB)\", 2D) = \"white\" {} } SubShader { Tags { \"RenderType\"=\"Opaque\" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; fixed4 _Color; struct Input { float2 uv_MainTex; }; // 自定义Gamma到Linear转换函数 float3 GammaToLinearSpace(float3 gammaColor) { return pow(gammaColor, 2.2); } // 自定义Linear到Gamma转换函数 float3 LinearToGammaSpace(float3 linearColor) { return pow(linearColor, 1.0/2.2); } void surf (Input IN, inout SurfaceOutputStandard o) { // 采样纹理 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; // 在Unity中,采样纹理后的颜色已经在正确的色彩空间中 // 但如果需要手动处理,可以使用上面的转换函数 // 示例:手动颜色处理(通常不需要) #ifdef UNITY_COLORSPACE_GAMMA // 在Gamma空间中工作 o.Albedo = c.rgb; #else // 在Linear空间中工作 // 注意:这只是演示,Unity已经处理好了这部分 o.Albedo = c.rgb; #endif o.Alpha = c.a; } ENDCG } FallBack \"Diffuse\"}
7.1.4 基于物理的光照渲染模型
基于物理的渲染(PBR)是现代游戏引擎中实现真实感光照的关键技术。BRDF(双向反射分布函数)是PBR的核心概念,它描述了光线如何与表面交互并反射到不同方向。
Unity的Standard着色器使用了基于物理的光照模型,主要基于以下参数:
- 反照率(Albedo):表面的基本颜色
- 金属度(Metallic):定义表面是金属还是非金属
- 光滑度(Smoothness):控制表面的粗糙或光滑程度
- 法线(Normal):定义表面微观几何形状
csharp
// PBR材质属性分析器using UnityEngine;public class PBRMaterialAnalyzer : MonoBehaviour{ public Material targetMaterial; void OnGUI() { if(targetMaterial == null) return; GUILayout.BeginArea(new Rect(10, 10, 300, 500)); GUILayout.Label(\"PBR材质分析\", EditorStyles.boldLabel); string shaderName = targetMaterial.shader.name; GUILayout.Label(\"着色器: \" + shaderName); // 检查是否是PBR着色器 bool isPBR = shaderName.Contains(\"Standard\") ||shaderName.Contains(\"Lit\") ||shaderName.Contains(\"PBR\"); GUILayout.Label(\"是否PBR: \" + (isPBR ? \"是\" : \"否\")); if(isPBR) { // 显示PBR属性 if(targetMaterial.HasProperty(\"_Color\")) { Color albedo = targetMaterial.GetColor(\"_Color\"); GUILayout.Label(string.Format(\"反照率: R:{0:F2} G:{1:F2} B:{2:F2}\", albedo.r, albedo.g, albedo.b)); } if(targetMaterial.HasProperty(\"_Metallic\")) { float metallic = targetMaterial.GetFloat(\"_Metallic\"); GUILayout.Label(\"金属度: \" + metallic.ToString(\"F2\")); } if(targetMaterial.HasProperty(\"_Glossiness\") || targetMaterial.HasProperty(\"_Smoothness\")) { float smoothness = targetMaterial.HasProperty(\"_Glossiness\") ? targetMaterial.GetFloat(\"_Glossiness\") : targetMaterial.GetFloat(\"_Smoothness\"); GUILayout.Label(\"光滑度: \" + smoothness.ToString(\"F2\")); // 估算粗糙度 float roughness = 1 - smoothness; GUILayout.Label(\"粗糙度: \" + roughness.ToString(\"F2\")); } if(targetMaterial.HasProperty(\"_BumpMap\")) { Texture normalMap = targetMaterial.GetTexture(\"_BumpMap\"); GUILayout.Label(\"法线贴图: \" + (normalMap != null ? \"已设置\" : \"未设置\")); } // 检查是否使用自发光 bool hasEmission = targetMaterial.IsKeywordEnabled(\"_EMISSION\"); GUILayout.Label(\"自发光: \" + (hasEmission ? \"启用\" : \"禁用\")); if(hasEmission && targetMaterial.HasProperty(\"_EmissionColor\")) { Color emission = targetMaterial.GetColor(\"_EmissionColor\"); float intensity = (emission.r + emission.g + emission.b) / 3.0f; GUILayout.Label(\"自发光强度: \" + intensity.ToString(\"F2\")); } } GUILayout.EndArea(); }}
在着色器中实现基础BRDF模型:
glsl
// 简化的基于物理的光照模型Shader \"Custom/SimplifiedPBR\"{ Properties { _Color (\"Color\", Color) = (1,1,1,1) _MainTex (\"Albedo (RGB)\", 2D) = \"white\" {} _MetallicTex (\"Metallic\", 2D) = \"black\" {} _Metallic (\"Metallic\", Range(0,1)) = 0.0 _Glossiness (\"Smoothness\", Range(0,1)) = 0.5 _BumpMap (\"Normal Map\", 2D) = \"bump\" {} _BumpScale (\"Normal Scale\", Range(0,2)) = 1.0 [HDR] _EmissionColor (\"Emission Color\", Color) = (0,0,0,1) _EmissionMap (\"Emission\", 2D) = \"black\" {} } SubShader { Tags { \"RenderType\"=\"Opaque\" } LOD 200 CGPROGRAM #pragma surface surf SimplePBR fullforwardshadows #pragma target 3.0 sampler2D _MainTex; sampler2D _MetallicTex; sampler2D _BumpMap; sampler2D _EmissionMap; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; half _Glossiness; half _Metallic; half _BumpScale; fixed4 _Color; half3 _EmissionColor; // 自定义光照模型实现简化的PBR half4 LightingSimplePBR(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) { half3 h = normalize(lightDir + viewDir); half nDotL = max(0, dot(s.Normal, lightDir)); half nDotH = max(0, dot(s.Normal, h)); half nDotV = max(0, dot(s.Normal, viewDir)); half vDotH = max(0, dot(viewDir, h)); // 计算漫反射项 half3 diffuse = s.Albedo * nDotL; // 计算镜面反射项 half roughness = 1 - s.Gloss; half roughnessSqr = roughness * roughness; // 简化的Cook-Torrance BRDF // D项:法线分布函数(GGX) half alpha = roughnessSqr; half alphaSqr = alpha * alpha; half denom = nDotH * nDotH * (alphaSqr - 1.0) + 1.0; half D = alphaSqr / (3.14159 * denom * denom); // F项:菲涅尔项简化 half3 F0 = lerp(0.04, s.Albedo, s.Specular); // 根据金属度混合 half3 F = F0 + (1.0 - F0) * pow(1.0 - vDotH, 5.0); // G项:几何遮挡项简化 half k = (roughness + 1.0) * (roughness + 1.0) / 8.0; half G1_L = nDotL / (nDotL * (1.0 - k) + k); half G1_V = nDotV / (nDotV * (1.0 - k) + k); half G = G1_L * G1_V; // 组合BRDF half3 specular = (D * F * G) / (4.0 * nDotL * nDotV + 0.001); // 根据金属度混合漫反射和镜面反射 half3 kD = (1.0 - F) * (1.0 - s.Specular); // 漫反射比例 half3 color = (kD * diffuse + specular) * _LightColor0.rgb * atten; return half4(color, s.Alpha); } void surf (Input IN, inout SurfaceOutput o) { // 采样纹理 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; fixed4 m = tex2D(_MetallicTex, IN.uv_MainTex); // 设置表面属性 o.Albedo = c.rgb; o.Specular = lerp(m.r, _Metallic, 0.5); // 金属度 o.Gloss = _Glossiness; // 光滑度 o.Alpha = c.a; // 法线贴图 fixed3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); normal.xy *= _BumpScale; o.Normal = normalize(normal); // 自发光 fixed3 emission = tex2D(_EmissionMap, IN.uv_MainTex).rgb * _EmissionColor.rgb; o.Emission = emission; } ENDCG } FallBack \"Diffuse\"}
7.2 Unity的核心光照模型与渲染路径
Unity提供了多种内置的光照模型,适用于不同的渲染需求。了解这些模型及其工作原理,对于创建真实感的场景至关重要。
7.2.1 环境光和全局光照系统
环境光代表场景中的间接照明,它为没有直接受到其他光源影响的区域提供基本照明。在Unity中,环境光可以通过多种方式设置,包括固定颜色、天空盒、环境光探针和光照贴图。
csharp
// 环境光控制器using UnityEngine;public class AmbientLightController : MonoBehaviour{ public enum AmbientMode { Color, Skybox, Gradient } [Header(\"环境光设置\")] public AmbientMode ambientMode = AmbientMode.Skybox; public Color ambientColor = new Color(0.2f, 0.2f, 0.2f); public Color ambientSkyColor = new Color(0.5f, 0.7f, 1.0f); public Color ambientEquatorColor = new Color(0.5f, 0.5f, 0.5f); public Color ambientGroundColor = new Color(0.3f, 0.3f, 0.3f); public float ambientIntensity = 1.0f; [Header(\"光照探针设置\")] public bool useReflectionProbes = true; public Material skyboxMaterial; void Start() { ApplySettings(); } void ApplySettings() { // 设置环境光模式 switch(ambientMode) { case AmbientMode.Color: RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat; RenderSettings.ambientLight = ambientColor; break; case AmbientMode.Skybox: RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox; if(skyboxMaterial != null) RenderSettings.skybox = skyboxMaterial; break; case AmbientMode.Gradient: RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight; RenderSettings.ambientSkyColor = ambientSkyColor; RenderSettings.ambientEquatorColor = ambientEquatorColor; RenderSettings.ambientGroundColor = ambientGroundColor; break; }