【Virtual Globe 渲染技术笔记】7 GPU 光线投射

GPU 光线投射(GPU Ray Casting)

GPU 专为光栅化三角形而设计,速度极快。
传统椭球渲染流程是:

  1. 用细分算法生成大量三角形
  2. GPU 并行光栅化
  3. 着色器计算颜色

这套流程虽然高效,但仍存在固有问题:

  • 任何细分都不是完美的,各有优劣;
  • 细分过粗 → 曲面失真;过细 → 性能/内存开销大,需要复杂的视点相关 LOD;
  • 现代 GPU 算力增长远超内存带宽,频繁向总线发送大量三角形会拖慢性能。

光线追踪:另一种思路

光栅化从三角形到像素;
光线追踪从像素出发,逆着视线发射射线,求与物体的交点,再计算光照。

对椭球而言,射线与隐式曲面的交点有解析解,因此无需三角形网格即可渲染。
优势:

  • 无限级细节:放大时不会出现“网格”或“锯齿”曲面;
  • 无拓扑缺陷:没有极区细长三角形、IDL 跨越等问题;
  • 内存极低:只存储椭球参数,无需巨型顶点缓冲。

如何在 GPU 上“光栅化”光线追踪?

现代 GPU 原生支持光栅化,但我们可以“借壳”:

  1. 构造椭球的轴对齐包围盒(12 个三角形)。

  2. 用光栅管线渲染这个盒子,前向面剔除,保证相机在盒内时仍能看见地球。
    在这里插入图片描述

  3. 片元着色器里,针对每个像素发射射线:

    • 起点:相机位置 ce_cameraEye
    • 方向:从相机到片元世界坐标 normalize(worldPos - ce_cameraEye)
  4. 用解析公式判断射线与椭球是否相交。

    • 需要椭球半径 (a,b,c)
    • 为提高效率,CPU 预先计算 1/radii² 作为 uniform 传入。
  5. 若相交,计算交点、法线、纹理坐标,用本章前面 LightIntensity() 等函数着色;
    若不相交,discard 片元(下图 绿色/红色示例)。
    在这里插入图片描述


完整片元着色器流程

  • Listing 1 只判断相交,输出纯色。
// List-1 Base GLSL fragment shader for ray casting.
in vec3 worldPosition;
out vec3 fragmentColor;

uniform vec3 ce_cameraEye;
uniform vec3 u_globeOneOverRadiiSquared;

struct Intersection
{
    bool Intersects;
    float Time; // Time of intersection along ray
};

Intersection RayIntersectEllipsoid(vec3 rayOrigin, vec3 rayDirection, vec3 oneOverEllipsoidRadiiSquared)
{
    // ...
}

void main()
{
    vec3 rayDirection = normalize(worldPosition - ce_cameraEye);
    Intersection i = RayIntersectEllipsoid(ce_cameraEye, rayDirection, u_globeOneOverRadiiSquared);
    fragmentColor = vec3(i.Intersects ? 1.0 : 0.0, !i.Intersects ? 1.0 : 0.0, 0.0);
}

  • Listing 2 计算交点位置、法线、纹理坐标,完成光照与纹理。
// List 2 Shading or discarding a fragment based on a ray cast.
vec3 GeodeticSurfaceNormal(vec3 positionOnEllipsoid, vec3 oneOverEllipsoidRadiiSquared)
{
    return normalize(positionOnEllipsoid * oneOverEllipsoidRadiiSquared);
}

void main()
{
    vec3 rayDirection = normalize(worldPosition - ce_cameraEye);
    Intersection i = RayIntersectEllipsoid(ce_cameraEye, rayDirection, u_globeOneOverRadiiSquared);
    
    if (i.Intersects)
    {
        vec3 position = ce_cameraEye + (i.Time * rayDirection);
        vec3 normal = GeodeticSurfaceNormal(position, u_globeOneOverRadiiSquared);
        vec3 toLight = normalize(ce_cameraLightPosition - position);
        vec3 toEye = normalize(ce_cameraEye - position);
        
        float intensity = LightIntensity(normal, toLight, toEye, ce_diffuseSpecularAmbientShininess);
        fragmentColor = intensity * texture(ce_texture0, ComputeTextureCoordinates(normal)).rgb;
    }
    else
    {
        discard;
    }
}

  • Listing 3 把交点深度写入 gl_FragDepth,解决深度缓冲区错误问题。
// List 3 Computing depth for a world-space position.
float ComputeWorldPositionDepth(vec3 position)
{
    vec4 v = ce_modelViewPerspectiveMatrix * vec4(position, 1.0);
    v.z /= v.w;
    v.z = (v.z + 1.0) * 0.5;
    return v.z;
}


性能与优化

  • overdraw:包围盒外片元被丢弃,看似浪费,但现代 GPU 的动态分支wavefront 一致性让开销很小。
  • 更紧包围:用视口对齐凸多边形代替轴对齐盒,可进一步减少空射线,在顶点/片元负载间权衡。
  • 无细分 → 无三角形传输瓶颈,显存占用极低。

局限与展望

  • 椭球有封闭解,但通用场景需要复杂加速结构(BVH、KD-Tree),在动态场景下 GPU 实现困难;
  • 软阴影、抗锯齿、多光源会导致射线数爆炸;
  • GPU 光线追踪仍是研究热点。

尽管如此,GPU 光线投射椭球已能无缝嵌入光栅管线,成为细分网格的有力替代方案。


参考:

  • Cozi, Patrick; Ring, Kevin. 3D Engine Design for Virtual Globes. CRC Press, 2011.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值