Unity光线传播体积(LPV)技术实现详解

一、LPV技术概述

光线传播体积(Light Propagation Volumes)是一种实时全局光照技术,通过将场景中的间接光信息存储在3D网格中,实现动态物体的间接光照效果。

核心优势:

  • 实时性能:相比传统光照贴图,支持动态场景

  • 硬件友好:适合GPU并行计算

  • 中等质量:提供比SSAO更好的间接光效果

  • 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀

二、LPV实现原理

1. 技术流程

graph TD
    A[场景捕捉] --> B[RSM生成]
    B --> C[光照注入]
    C --> D[传播计算]
    D --> E[最终渲染]

2. 关键数据结构

struct SHCoefficients {
    Vector4[] coefficients; // 球谐系数数组
    const int Bands = 2;    // 使用二阶球谐
};

三、核心实现代码

1. 反射阴影图(RSM)生成

void CreateRSM(Camera lightCamera) {
    RenderTexture rsmFlux = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGBHalf);
    RenderTexture rsmNormal = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGB2101010);
    
    lightCamera.targetTexture = rsmFlux;
    Shader.SetGlobalTexture("_RSM_Flux", rsmFlux);
    Shader.SetGlobalTexture("_RSM_Normal", rsmNormal);
}

2. 光照注入阶段

// 光照注入Compute Shader
#pragma kernel InjectLight

RWTexture3D<float4> LPVGrid;
Texture2D<float4> RSM_Flux;
Texture2D<float4> RSM_Normal;

[numthreads(8,8,1)]
void InjectLight (uint3 id : SV_DispatchThreadID) {
    float4 flux = RSM_Flux[id.xy];
    float3 normal = RSM_Normal[id.xy].xyz;
    
    // 计算球谐投影
    SHCoefficients sh = ProjectToSH(flux.rgb, normal);
    
    // 写入LPV网格
    LPVGrid[id.xyz] = float4(sh.coefficients[0], 1.0);
}

3. 传播计算

// 传播Compute Shader
#pragma kernel PropagateLight

RWTexture3D<float4> LPVGrid;
int3 gridSize;

[numthreads(4,4,4)]
void PropagateLight (uint3 id : SV_DispatchThreadID) {
    if(any(id >= gridSize)) return;
    
    // 收集6邻域贡献
    float4 accum = 0;
    for(int i=0; i<6; i++) {
        int3 neighbor = id + GetOffset(i);
        if(any(neighbor < 0) || any(neighbor >= gridSize)) continue;
        accum += LPVGrid[neighbor] * 0.1666; // 均分权重
    }
    
    // 写入更新后的光照
    LPVGrid[id.xyz] = accum;
}

四、渲染应用

1. 最终着色器

Shader "Custom/LPVReceiver" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            sampler3D _LPV_Grid;
            float3 _LPV_GridSize;
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD0;
            };
            
            float4 frag(v2f i) : SV_Target {
                // 计算网格坐标
                float3 gridCoord = (i.worldPos - _LPV_MinBounds) / _LPV_CellSize;
                
                // 三线性采样
                float4 sh = tex3D(_LPV_Grid, gridCoord / _LPV_GridSize);
                
                // 重建光照
                float3 irradiance = EvalSH(sh);
                
                return float4(irradiance, 1.0);
            }
            ENDCG
        }
    }
}

五、性能优化

1. 分辨率控制

网格分辨率 质量 性能影响
32x32x32 0.5ms
64x64x64 2.1ms
128x128x128 8.4ms

2. 迭代次数优化

void UpdateLPV() {
    // 首帧完整计算
    if(firstFrame) {
        ExecuteFullPropagation(4);
    } 
    // 后续帧增量更新
    else {
        ExecuteIncrementalPropagation(1);
    }
}

六、完整项目参考


通过LPV技术,开发者可以在Unity中实现中等质量的实时全局光照效果,特别适合需要动态光照的场景。关键点在于合理平衡网格分辨率和传播迭代次数,以达到性能与质量的平衡。