Unity中的PBR(基于物理的渲染)

什么是PBR?为什么它改变了游戏图形?

基于物理的渲染(Physically Based Rendering,简称PBR)是现代计算机图形学中的一种渲染方法,它通过模拟真实世界中的光线物理行为来创建更加逼真的视觉效果。与传统渲染方法相比,PBR不是基于艺术家的直觉和经验,而是基于物理定律。

PBR的核心优势:

  1. 物理准确性:在不同光照条件下保持一致的视觉表现
  2. 艺术工作流简化:材质参数直观,易于理解和调整
  3. 跨平台一致性:在不同设备和光照环境下表现稳定
  4. 真实感增强:更好地模拟金属、粗糙度、菲涅尔效应等物理现象

PBR理论基石:理解关键概念

1. 能量守恒定律

在PBR中,出射光的总能量不能超过入射光能量。这意味着材质不能自发地产生光能,反射的光越强,折射/吸收的光就越少。

2. 双向反射分布函数(BRDF)

BRDF描述了光线如何从特定方向入射并从另一方向反射。PBR中常用的BRDF模型包括:

  • Cook-Torrance BRDF:最常用的微表面模型
  • Disney BRDF:用于电影制作的实用模型

3. 微表面理论

表面由许多微小的镜面组成,这些微观结构的分布决定了材质的宏观外观:

  • 粗糙度:微表面法线的随机程度
  • 法线分布函数:描述微表面法线分布的函数

4. 菲涅尔效应

光线以不同角度照射表面时,反射率会发生变化。掠射角(接近90度)时反射率接近100%。

PBR材质的关键参数

1. 反照率/基础色(Albedo)

  • 非金属:RGB颜色值(sRGB空间)
  • 金属:反射的RGB颜色值
  • 不应包含光照信息(阴影、高光等)

2. 金属度(Metallic)

  • 0.0(非金属)到1.0(纯金属)
  • 非金属:漫反射+镜面反射
  • 金属:只有镜面反射,无漫反射

3. 粗糙度(Roughness)

  • 0.0(完全光滑)到1.0(完全粗糙)
  • 控制高光的锐利程度和扩散范围

4. 法线贴图(Normal Map)

  • 增加表面细节而不增加几何复杂度
  • 存储切线空间法线信息

5. 环境光遮蔽(Ambient Occlusion)

  • 模拟表面裂隙和凹陷处的阴影效果

在Unity中实现PBR:逐步指南

步骤1:设置PBR材质

csharp 复制代码
using UnityEngine;

[CreateAssetMenu(fileName = "NewPBRMaterial", menuName = "Materials/PBR Material")]
public class PBRMaterialData : ScriptableObject
{
    [Header("Albedo")]
    public Color albedoColor = Color.white;
    public Texture2D albedoMap;
    
    [Header("Metallic & Roughness")]
    [Range(0, 1)] public float metallic = 0.0f;
    [Range(0, 1)] public float roughness = 0.5f;
    public Texture2D metallicRoughnessMap;
    
    [Header("Normal Map")]
    public Texture2D normalMap;
    [Range(0, 1)] public float normalStrength = 1.0f;
    
    [Header("Ambient Occlusion")]
    public Texture2D aoMap;
    [Range(0, 1)] public float aoStrength = 1.0f;
    
    [Header("Emission")]
    public Color emissionColor = Color.black;
    public Texture2D emissionMap;
    [Range(0, 10)] public float emissionIntensity = 1.0f;
}

步骤2:创建自定义PBR着色器

shader 复制代码
Shader "Custom/PBRShader"
{
    Properties
    {
        // 基础属性
        _Color ("Albedo", Color) = (1,1,1,1)
        _MainTex ("Albedo Map", 2D) = "white" {}
        
        // 金属度和粗糙度
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Roughness ("Roughness", Range(0,1)) = 0.5
        _MetallicRoughnessMap ("Metallic Roughness Map", 2D) = "white" {}
        
        // 法线贴图
        [Normal] _BumpMap ("Normal Map", 2D) = "bump" {}
        _BumpScale ("Normal Scale", Float) = 1.0
        
        // 环境光遮蔽
        _OcclusionMap ("Occlusion Map", 2D) = "white" {}
        _OcclusionStrength ("Occlusion Strength", Range(0.0, 1.0)) = 1.0
        
        // 自发光
        _EmissionColor ("Emission Color", Color) = (0,0,0)
        _EmissionMap ("Emission Map", 2D) = "white" {}
        
        // 高级参数
        [Toggle(_HEIGHTMAP)] _UseHeightMap ("Use Height Map", Float) = 0
        _Parallax ("Parallax Strength", Range(0, 0.1)) = 0.01
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0
        
        #include "UnityPBSLighting.cginc"
        
        sampler2D _MainTex;
        sampler2D _MetallicRoughnessMap;
        sampler2D _BumpMap;
        sampler2D _OcclusionMap;
        sampler2D _EmissionMap;
        
        fixed4 _Color;
        half _Metallic;
        half _Roughness;
        half _BumpScale;
        half _OcclusionStrength;
        fixed4 _EmissionColor;
        
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 viewDir;
            INTERNAL_DATA
        };
        
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 反照率
            fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = albedo.rgb;
            o.Alpha = albedo.a;
            
            // 金属度和粗糙度
            fixed4 metallicRoughness = tex2D(_MetallicRoughnessMap, IN.uv_MainTex);
            o.Metallic = _Metallic * metallicRoughness.b;
            o.Smoothness = 1.0 - (_Roughness * metallicRoughness.g);
            
            // 法线贴图
            o.Normal = UnpackScaleNormal(tex2D(_BumpMap, IN.uv_BumpMap), _BumpScale);
            
            // 环境光遮蔽
            fixed occlusion = tex2D(_OcclusionMap, IN.uv_MainTex).r;
            o.Occlusion = lerp(1.0, occlusion, _OcclusionStrength);
            
            // 自发光
            fixed3 emission = tex2D(_EmissionMap, IN.uv_MainTex).rgb * _EmissionColor.rgb;
            o.Emission = emission;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

步骤3:创建PBR材质管理器

csharp 复制代码
using UnityEngine;
using System.Collections.Generic;

public class PBRMaterialManager : MonoBehaviour
{
    public PBRMaterialData materialData;
    
    [Header("Material Instances")]
    public Material pbrMaterial;
    
    [Header("Runtime Controls")]
    [Range(0, 1)] public float metallicOverride = -1;
    [Range(0, 1)] public float roughnessOverride = -1;
    
    private MaterialPropertyBlock propertyBlock;
    private Renderer objectRenderer;
    
    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        propertyBlock = new MaterialPropertyBlock();
        
        ApplyMaterialData();
    }
    
    void Update()
    {
        if (Application.isPlaying)
        {
            UpdateMaterialProperties();
        }
    }
    
    void ApplyMaterialData()
    {
        if (materialData == null || objectRenderer == null) return;
        
        objectRenderer.GetPropertyBlock(propertyBlock);
        
        // 设置纹理
        if (materialData.albedoMap)
            propertyBlock.SetTexture("_MainTex", materialData.albedoMap);
        
        propertyBlock.SetColor("_Color", materialData.albedoColor);
        
        if (materialData.metallicRoughnessMap)
            propertyBlock.SetTexture("_MetallicRoughnessMap", materialData.metallicRoughnessMap);
        
        propertyBlock.SetFloat("_Metallic", materialData.metallic);
        propertyBlock.SetFloat("_Roughness", materialData.roughness);
        
        if (materialData.normalMap)
        {
            propertyBlock.SetTexture("_BumpMap", materialData.normalMap);
            propertyBlock.SetFloat("_BumpScale", materialData.normalStrength);
        }
        
        if (materialData.aoMap)
        {
            propertyBlock.SetTexture("_OcclusionMap", materialData.aoMap);
            propertyBlock.SetFloat("_OcclusionStrength", materialData.aoStrength);
        }
        
        if (materialData.emissionMap)
            propertyBlock.SetTexture("_EmissionMap", materialData.emissionMap);
        
        propertyBlock.SetColor("_EmissionColor", 
            materialData.emissionColor * materialData.emissionIntensity);
        
        objectRenderer.SetPropertyBlock(propertyBlock);
    }
    
    void UpdateMaterialProperties()
    {
        objectRenderer.GetPropertyBlock(propertyBlock);
        
        if (metallicOverride >= 0)
            propertyBlock.SetFloat("_Metallic", metallicOverride);
        
        if (roughnessOverride >= 0)
            propertyBlock.SetFloat("_Roughness", roughnessOverride);
        
        objectRenderer.SetPropertyBlock(propertyBlock);
    }
    
    // 运行时修改材质属性
    public void SetMetallic(float value)
    {
        metallicOverride = Mathf.Clamp01(value);
    }
    
    public void SetRoughness(float value)
    {
        roughnessOverride = Mathf.Clamp01(value);
    }
    
    public void SetAlbedoColor(Color color)
    {
        materialData.albedoColor = color;
        ApplyMaterialData();
    }
}

步骤4:实现IBL(基于图像的照明)

csharp 复制代码
using UnityEngine;

[ExecuteInEditMode]
public class IBLManager : MonoBehaviour
{
    [Header("IBL Settings")]
    public Cubemap environmentCube;
    public Texture2D brdfLUT;
    
    [Range(0, 1)] public float reflectionIntensity = 1.0f;
    [Range(0, 1)] public float diffuseIntensity = 1.0f;
    
    [Header("Dynamic Updates")]
    public bool updateInRealtime = false;
    public float updateInterval = 0.1f;
    
    private float lastUpdateTime;
    
    void OnEnable()
    {
        UpdateIBL();
    }
    
    void Update()
    {
        if (updateInRealtime && Time.time - lastUpdateTime > updateInterval)
        {
            UpdateIBL();
            lastUpdateTime = Time.time;
        }
    }
    
    void UpdateIBL()
    {
        if (environmentCube)
        {
            Shader.SetGlobalTexture("_EnvCube", environmentCube);
            Shader.SetGlobalFloat("_ReflectionIntensity", reflectionIntensity);
        }
        
        if (brdfLUT)
        {
            Shader.SetGlobalTexture("_BRDFLUT", brdfLUT);
        }
        
        Shader.SetGlobalFloat("_DiffuseIntensity", diffuseIntensity);
    }
    
    // 生成简单的环境贴图
    public void CaptureEnvironment()
    {
        if (environmentCube == null)
        {
            environmentCube = new Cubemap(512, TextureFormat.RGBA32, false);
        }
        
        Camera cam = GetComponent<Camera>();
        if (cam == null)
        {
            GameObject go = new GameObject("IBL Capture Camera");
            cam = go.AddComponent<Camera>();
            cam.enabled = false;
        }
        
        Vector3[] directions = {
            Vector3.forward, Vector3.back,
            Vector3.right, Vector3.left,
            Vector3.up, Vector3.down
        };
        
        Vector3[] ups = {
            Vector3.up, Vector3.up,
            Vector3.up, Vector3.up,
            Vector3.forward, Vector3.back
        };
        
        for (int i = 0; i < 6; i++)
        {
            cam.transform.position = transform.position;
            cam.transform.rotation = Quaternion.LookRotation(directions[i], ups[i]);
            cam.RenderToCubemap(environmentCube, 1 << i);
        }
        
        environmentCube.Apply();
        UpdateIBL();
    }
}

高级PBR功能实现

1. 视差映射(Parallax Mapping)

csharp 复制代码
// 在着色器中添加视差映射功能
Shader "Custom/PBRParallax"
{
    // ... 基础属性 ...
    
    Properties
    {
        // 视差映射
        _ParallaxMap ("Height Map", 2D) = "white" {}
        _Parallax ("Parallax Scale", Range(0, 0.1)) = 0.01
        _MinLayers ("Min Layers", Float) = 8
        _MaxLayers ("Max Layers", Float) = 32
    }
    
    SubShader
    {
        // ... 其他代码 ...
        
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 视差偏移计算
            float3 viewDir = normalize(IN.viewDir);
            float2 parallaxOffset = ParallaxOffset(_Parallax, _Parallax, IN.viewDir);
            float2 uv = IN.uv_MainTex + parallaxOffset;
            
            // 使用偏移后的UV坐标采样纹理
            fixed4 albedo = tex2D(_MainTex, uv) * _Color;
            // ... 其他采样 ...
        }
    }
}

2. 次表面散射模拟(SSS)

csharp 复制代码
Shader "Custom/PBRSubsurface"
{
    Properties
    {
        // ... 基础PBR属性 ...
        
        // 次表面散射
        _SubsurfaceColor ("Subsurface Color", Color) = (1,1,1,1)
        _SubsurfacePower ("Subsurface Power", Range(0, 10)) = 5.0
        _SubsurfaceDistortion ("Distortion", Range(0, 1)) = 0.0
        _SubsurfaceScale ("Scale", Range(0, 10)) = 1.0
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf StandardSubsurface
        #pragma target 3.0
        
        struct SurfaceOutputStandardSubsurface
        {
            fixed3 Albedo;
            fixed3 Normal;
            fixed3 Emission;
            fixed Metallic;
            fixed Smoothness;
            fixed Occlusion;
            fixed Alpha;
            fixed3 SubsurfaceColor;
        };
        
        // 自定义光照模型
        half4 LightingStandardSubsurface(
            SurfaceOutputStandardSubsurface s,
            half3 lightDir,
            half3 viewDir,
            half atten)
        {
            // 标准PBR光照计算
            half3 halfVec = normalize(lightDir + viewDir);
            half NdotL = dot(s.Normal, lightDir);
            half NdotV = abs(dot(s.Normal, viewDir));
            half NdotH = dot(s.Normal, halfVec);
            
            // 添加次表面散射
            half3 subsurface = s.SubsurfaceColor * 
                pow(saturate(NdotL * 0.5 + 0.5), s._SubsurfacePower) *
                saturate(1.0 - NdotV);
            
            // 组合结果
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * NdotL * atten + subsurface;
            c.a = s.Alpha;
            return c;
        }
        
        // ... surf函数 ...
    }
}

PBR最佳实践和优化技巧

1. 纹理优化

csharp 复制代码
public class PBRTextureOptimizer : MonoBehaviour
{
    public enum TextureResolution
    {
        Low = 512,
        Medium = 1024,
        High = 2048,
        Ultra = 4096
    }
    
    public TextureResolution albedoResolution = TextureResolution.Medium;
    public TextureResolution normalResolution = TextureResolution.High;
    public TextureResolution otherResolution = TextureResolution.Medium;
    
    public bool generateMipMaps = true;
    public FilterMode filterMode = FilterMode.Trilinear;
    public TextureWrapMode wrapMode = TextureWrapMode.Repeat;
    
    public Texture2D OptimizeTexture(Texture2D source, TextureResolution resolution)
    {
        int targetSize = (int)resolution;
        
        RenderTexture rt = RenderTexture.GetTemporary(
            targetSize, targetSize, 0,
            RenderTextureFormat.Default, RenderTextureReadWrite.sRGB);
        
        Graphics.Blit(source, rt);
        
        Texture2D result = new Texture2D(targetSize, targetSize, 
            TextureFormat.RGBA32, generateMipMaps);
        
        RenderTexture.active = rt;
        result.ReadPixels(new Rect(0, 0, targetSize, targetSize), 0, 0);
        result.Apply(generateMipMaps);
        
        RenderTexture.ReleaseTemporary(rt);
        
        result.filterMode = filterMode;
        result.wrapMode = wrapMode;
        
        return result;
    }
}

2. LOD(细节层次)系统

csharp 复制代码
public class PBRMaterialLOD : MonoBehaviour
{
    [System.Serializable]
    public class LODLevel
    {
        public float distance;
        public Texture2D albedoMap;
        public Texture2D normalMap;
        public Texture2D metallicRoughnessMap;
        public float textureScale = 1.0f;
    }
    
    public LODLevel[] lodLevels;
    public Transform cameraTransform;
    public float updateInterval = 0.5f;
    
    private MaterialPropertyBlock propertyBlock;
    private Renderer objectRenderer;
    private float lastUpdateTime;
    private int currentLOD = 0;
    
    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        propertyBlock = new MaterialPropertyBlock();
        
        if (cameraTransform == null)
            cameraTransform = Camera.main.transform;
    }
    
    void Update()
    {
        if (Time.time - lastUpdateTime > updateInterval)
        {
            UpdateLOD();
            lastUpdateTime = Time.time;
        }
    }
    
    void UpdateLOD()
    {
        float distance = Vector3.Distance(
            transform.position, cameraTransform.position);
        
        int newLOD = 0;
        for (int i = 0; i < lodLevels.Length; i++)
        {
            if (distance >= lodLevels[i].distance)
            {
                newLOD = i;
            }
        }
        
        if (newLOD != currentLOD)
        {
            ApplyLOD(newLOD);
            currentLOD = newLOD;
        }
    }
    
    void ApplyLOD(int lodIndex)
    {
        if (lodIndex < 0 || lodIndex >= lodLevels.Length) return;
        
        LODLevel level = lodLevels[lodIndex];
        
        objectRenderer.GetPropertyBlock(propertyBlock);
        
        if (level.albedoMap)
            propertyBlock.SetTexture("_MainTex", level.albedoMap);
        
        if (level.normalMap)
            propertyBlock.SetTexture("_BumpMap", level.normalMap);
        
        if (level.metallicRoughnessMap)
            propertyBlock.SetTexture("_MetallicRoughnessMap", level.metallicRoughnessMap);
        
        propertyBlock.SetFloat("_TextureScale", level.textureScale);
        
        objectRenderer.SetPropertyBlock(propertyBlock);
    }
}

调试和可视化工具

csharp 复制代码
using UnityEngine;

public class PBRDebugVisualizer : MonoBehaviour
{
    public enum DebugMode
    {
        None,
        Albedo,
        Normal,
        Metallic,
        Roughness,
        AO,
        Specular,
        Final
    }
    
    public DebugMode debugMode = DebugMode.None;
    public bool showWireframe = false;
    public bool showUVs = false;
    
    private Material originalMaterial;
    private Material debugMaterial;
    private Renderer objectRenderer;
    
    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        originalMaterial = objectRenderer.sharedMaterial;
        
        CreateDebugMaterial();
    }
    
    void Update()
    {
        UpdateDebugView();
    }
    
    void CreateDebugMaterial()
    {
        debugMaterial = new Material(Shader.Find("Hidden/PBRDebug"));
    }
    
    void UpdateDebugView()
    {
        if (debugMode == DebugMode.None)
        {
            objectRenderer.sharedMaterial = originalMaterial;
        }
        else
        {
            objectRenderer.sharedMaterial = debugMaterial;
            debugMaterial.SetInt("_DebugMode", (int)debugMode);
            debugMaterial.SetInt("_ShowWireframe", showWireframe ? 1 : 0);
            debugMaterial.SetInt("_ShowUVs", showUVs ? 1 : 0);
        }
    }
    
    void OnDestroy()
    {
        if (debugMaterial != null)
            Destroy(debugMaterial);
    }
    
    // 调试着色器
    [ExecuteInEditMode]
    public class PBRDebugShader : MonoBehaviour
    {
        void OnRenderObject()
        {
            // 这里可以添加自定义的调试渲染代码
        }
    }
}

性能优化建议

  1. 纹理压缩:使用适当的纹理压缩格式(BC7 for RGB,BC5 for normals)
  2. 着色器变体管理:减少不必要的着色器变体
  3. 动态批处理:对使用相同材质的物体进行批处理
  4. GPU实例化:对大量相同物体使用GPU实例化
  5. LOD系统:实现材质和几何体的LOD

结论

PBR是现代游戏开发中不可或缺的技术,它通过物理准确的方法大大提升了渲染的真实感。在Unity中实现PBR需要理解其核心概念,并合理使用Unity提供的工具和自定义着色器。通过本文介绍的方法,你可以创建出既美观又高效的PBR材质系统。

记住,PBR不仅仅是技术实现,更是艺术工作流的革命。正确使用PBR管道,可以让美术师更加专注于艺术创作,而不用过多担心技术限制。

进一步学习资源

  • Unity官方文档:Universal RP和HDRP的PBR实现
  • 《Real-Time Rendering》第四版
  • Disney的PBR原理论文
  • Marmoset Toolbag的PBR教程

通过不断实践和优化,你将能够掌握PBR的精髓,为你的游戏或应用创造出令人惊叹的视觉效果。

相关推荐
CreasyChan2 小时前
3D游戏数学基础指南
游戏·3d·unity·数学基础
平行云20 小时前
Enscape × Paraverse | 从设计到一键发布、网页分享、实时交互的全新体验
unity·ue5·xr·3dsmax·webgl·实时云渲染·云桌面
老朱佩琪!1 天前
Unity迭代器模式
unity·设计模式·迭代器模式
程序猿多布1 天前
Unity 多语言系统实现
unity·多语言
CreasyChan1 天前
Unity中C#状态模式详解
unity·c#·状态模式
鹿野素材屋1 天前
动作游戏网游:帧同步下的动画同步
unity·游戏引擎
世洋Blog1 天前
数据驱动与MVC
unity·mvc
WMX10121 天前
Unity添加近身菜单-MRTK
unity·游戏引擎
在路上看风景2 天前
15. 纹理尺寸是4的倍数
unity