028-环境光遮蔽

环境光遮蔽

环境光遮蔽(Ambient Occlusion, AO)是现代3D渲染中一种重要的全局光照近似技术,它模拟了环境光被周围几何体遮挡的效果,增强了场景的深度感和真实感。本章将深入探讨环境光遮蔽的原理、实现方法以及在DirectX 12中的应用。

21.1 通过投射光线实现环境光遮蔽

环境光遮蔽的核心思想是计算表面点接收环境光的能力。当一个点被周围几何体部分遮挡时,它接收到的环境光将减少。传统的环境光遮蔽计算方法是通过投射光线来实现的。

21.1.1 环境光遮蔽的物理基础

从物理上讲,环境光遮蔽是指一个表面点能"看到"多少比例的天空半球。如果一个点位于完全开放的空间,它可以接收到来自各个方向的环境光;如果它位于角落或狭缝中,则部分方向被遮挡,接收到的环境光减少。

对于表面上的点P,其环境光遮蔽值可以通过以下积分计算:

AO(P) = (1/π) ∫ V(P, ω) (n·ω) dω

其中:

  • ω是半球Ω上的方向
  • V(P, ω)是可见性函数,如果方向ω没有被遮挡则为1,否则为0
  • n是表面法线
  • (n·ω)是余弦项,考虑了光照的Lambert定律

21.1.2 光线追踪方法

最直接的环境光遮蔽计算方法是使用光线追踪:

cpp

// 使用光线追踪计算环境光遮蔽
float CalculateAO_RayTracing(
    const Vector3& position,
    const Vector3& normal,
    const Scene& scene,
    int numSamples,
    float maxDistance)
{
    float occlusion = 0.0f;
    
    // 创建局部坐标系(TBN矩阵)
    Vector3 tangent, bitangent;
    CreateOrthonormalBasis(normal, tangent, bitangent);
    
    // 投射多个随机方向的光线
    for (int i = 0; i < numSamples; ++i)
    {
        // 在半球上生成随机方向
        Vector3 sampleDir = GetHemisphereSample(i, numSamples);
        
        // 将采样方向从切线空间转换到世界空间
        Vector3 rayDir = 
            sampleDir.x * tangent +
            sampleDir.y * bitangent +
            sampleDir.z * normal;
        
        // 创建光线并检测交点
        Ray ray(position + normal * 0.001f, rayDir); // 添加小偏移避免自交
        float hitDistance;
        bool hit = scene.Intersect(ray, hitDistance);
        
        // 如果光线在最大距离内命中物体,计算遮蔽贡献
        if (hit && hitDistance < maxDistance)
        {
            // 遮蔽贡献随距离减弱
            float attenuation = 1.0f - (hitDistance / maxDistance);
            occlusion += attenuation;
        }
    }
    
    // 归一化遮蔽值
    occlusion /= static_cast<float>(numSamples);
    
    // 返回可及率(1 - 遮蔽率)
    return 1.0f - occlusion;
}

// 创建正交基底
void CreateOrthonormalBasis(
    const Vector3& normal,
    Vector3& tangent,
    Vector3& bitangent)
{
    // 根据法线创建正交坐标系
    if (fabs(normal.x) > fabs(normal.z))
    {
        tangent = Vector3(-normal.y, normal.x, 0.0f);
    }
    else
    {
        tangent = Vector3(0.0f, -normal.z, normal.y);
    }
    
    tangent = Normalize(tangent);
    bitangent = Cross(normal, tangent);
}

// 生成半球采样方向
Vector3 GetHemisphereSample(int i, int numSamples)
{
    // 使用低差异序列如Hammersley或Halton序列
    float u = HaltonSequence(i, 2);
    float v = HaltonSequence(i, 3);
    
    // 转换为半球坐标
    float phi = 2.0f * PI * u;
    float cosTheta = sqrt(1.0f - v);
    float sinTheta = sqrt(v);
    
    return Vector3(
        cos(phi) * sinTheta,
        sin(phi) * sinTheta,
        cosTheta
    );
}

这种方法虽然准确,但在实时应用中计算成本过高。因此,实时渲染通常使用屏幕空间方法(如SSA

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值