news 2026/7/19 23:55:32

(3) 片元着色器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(3) 片元着色器

片元着色器是真正“绘制”高斯形状的地方。对于包围盒内的每一个像素,我们需要计算它相对于椭圆中心的马氏距离(Mahalanobis Distance),并据此输出颜色和透明度。

这里有一个非常关键的细节:我们不需要使用概率密度函数中的归一化系数
1
2
π

det
(
Σ
)
。在 3DGS 中,我们的目标是 Alpha 混合(Alpha Blending)而非概率估计。高斯椭圆的“浓淡”是由我们单独学习的一个参数——不透明度(Opacity,
α
) 来控制的。如果强行加入归一化系数,反而会导致颜色变得极暗,因为协方差矩阵的行列式通常很小,会把颜色“压”没。

// 片元着色器 (Fragment Shader)
in vec2 v_position; // 相对于中心的实际偏移量

uniform mat2 u_inv_covariance; // 协方差矩阵的逆
uniform vec3 u_color;
uniform float u_opacity; // 基础不透明度

out vec4 frag_color;

void main() {
// 1. 计算马氏距离的平方
// 公式: d² = (x - μ)ᵀ * Σ⁻¹ * (x - μ)
// v_position 就是 (x - μ)
float d_squared = dot(v_position, u_inv_covariance * v_position);

// 2. 计算 Alpha // 注意:这里没有归一化系数,直接由基础不透明度控制 float alpha = u_opacity * exp(-0.5 * d_squared); // 3. 裁剪:如果透明度过低,直接丢弃,提升性能 if (alpha < 0.004) { // 0.004 约等于 1/255 discard; } // 4. 输出最终颜色 frag_color = vec4(u_color, alpha);

}
3. 补充说明
通过这套着色器组合,GPU 就能高效地将数百万个数学定义的高斯椭圆渲染到屏幕上。顶点着色器负责“画框”,片元着色器负责“填色”,最终呈现出平滑、连续的 3D 场景。

不过需要指出的是,上述代码主要侧重于原理的直观展示,属于一种理论化实现,旨在帮助读者理解 3DGS 的渲染逻辑。在实际的工业级工程应用中,为了追求极致的性能,实现方式可能有所不同;但其底层的数学原理——即利用二维高斯函数进行屏幕空间投影与混合——是完全一致的。

六、球谐系数
在上一节我们详细探讨了 3DGS 中每个高斯椭球的几何属性(位置、缩放、旋转),但一个逼真的 3D 场景显然不能只有形状,还必须有丰富的颜色和光照信息。回到第三章提到的 .ply 文件属性列表,你会发现除了 scale 和 rot 之外,还有一长串以 f_dc 和 f_rest 开头的属性:

property float f_dc_0
property float f_dc_1
property float f_dc_2
property float f_rest_0

property float f_rest_44
这些属性并非简单的 RGB 颜色值,而是球谐函数(Spherical Harmonics, SH)系数。在 3DGS 中,每个高斯椭球的颜色不是固定的,而是会随着观察角度的变化而变化,以此来模拟真实世界中光照与材质相互作用产生的复杂视觉效果(如镜面高光、菲涅尔效应等)。球谐函数正是实现这一“视角相关颜色”的核心数学工具。

  1. 傅里叶变换
    要解释什么是球谐函数,我们先了解一下类似的傅里叶变换。在数字信号处理中,傅里叶变换的核心思想是:任何复杂的波形,都可以被拆解成无数个频率、振幅各不相同的纯正弦波的叠加。

为了理解这个过程,我们需要先认识两个核心概念:

基函数 (Basis Functions):这些“纯正弦波”就是傅里叶变换的基函数。它们是构成复杂信号的“标准波纹”。
分解与重构:傅里叶变换的过程,就是用这些标准波纹去“测量”原始信号,计算出每个频率的波纹在信号中占多少“份量”(即系数)。
在数学上,这一过程可以用以下公式严谨地表达:

f
(
x
)



n

0

c
n

ϕ
n
(
x
)

(23)

其中:

f
(
x
)
是我们要分析的原始复杂信号。
ϕ
n
(
x
)
就是第
n
个基函数(即我们提到的“标准正弦波纹”)。
c
n
是对应的系数,代表了该基函数在原始信号中的权重或“份量”。
简单来说,傅里叶变换就是在一条直线上,用不同频率的正弦波作为“尺子”,去度量和表达一个复杂信号。

  1. 球谐函数
    现在,我们将傅里叶变换的思想从一维的“线”推广到三维空间中的“面”——一个单位球面。

在计算机图形学中,我们常常需要描述一个点周围的光照环境。光线可以从四面八方射来,这个光照信息是定义在一个球面上的函数,称为光照函数 (Lighting Function)。那么,如何表示这个复杂的球面函数呢?答案就是球谐函数 (Spherical Harmonics, SH)。

如果说傅里叶变换是用“平面正弦波”去拟合一条线上的信号,那么球谐变换就是用“球面波纹”去拟合一个球面上的信号。

基函数:球谐函数的基函数不再是正弦波,而是一系列定义在球面上的、具有不同频率和方向的“波纹图案”。
0 阶 SH:像一个纯色的球,代表整个球面的平均光照(环境光)。
1 阶 SH:像一个从黑到白渐变的球,能表示一个主方向的光照(比如太阳从上方照射)。
2 阶及更高:图案越来越复杂,能表示更精细的光照细节和反射。
因此,球谐函数本质上就是“球面上的傅里叶变换”。它用一组预定义的球面基函数,将复杂的环境光照压缩成一小撮系数。

在数学上,球谐函数的展开形式与傅里叶变换高度相似,其公式为:

f
(
θ
,
ϕ
)

n

l

0

l

m


l

c
m
l
Y
m
l
(
θ
,
ϕ
)

(24)

其中:

f
(
θ
,
ϕ
)
是定义在球面上的原始函数(如光照分布)。
Y
m
l
(
θ
,
ϕ
)
是球谐基函数,由阶数
l
和频率
m
共同决定。
c
m
l
是对应的球谐系数,代表了该基函数在原始球面函数中的权重。
进一步地,球谐基函数
Y
m
l
(
θ
,
ϕ
)
的通用数学定义由三个部分组成:

Y
m
l
(
θ
,
ϕ
)

K
m
l

P
m
l
(
cos
θ
)

e
i
m
ϕ

(25)

K
m
l
(归一化常数):这是一个为了保证数学严谨性而存在的系数,确保基函数在球面上的积分为 1。具体计算式为:
K
m
l


2
l
+
1
4
π
(
l

m
)
!
(
l
+
m
)
!
P
m
l
(
cos
θ
)
(关联勒让德多项式):这是决定球谐函数“形状”的核心部分。不同的阶数
l
和频率
m
会对应不同的多项式形状(如常数、线性、二次曲面等)。
e
i
m
ϕ
(复指数项):这一项代表了函数在水平方向(方位角
ϕ
)上的旋转变化。
在渲染时,我们只需根据观察方向,用这些系数和基函数进行加权求和,就能快速还原出该方向上的光照强度。

  1. PBR中的球谐函数
    在计算机图形学的基于物理的渲染(PBR)中,球谐函数扮演着至关重要的角色,主要用于高效地计算间接漫反射光照。

当一个物体(如角色、车辆)在场景中移动时,它不仅要被太阳光(直接光)照亮,还会被周围环境(如墙壁、地面)反射的光线照亮。这部分光线是柔和且无明确方向的漫反射光。为了实现这个效果,图形引擎会在场景中预先放置一些光照探针(Light Probes)。这些探针会“捕捉”周围环境的光照信息,并将其编码为一组球谐系数。当动态物体经过时,GPU 会根据其位置插值出对应的 SH 系数,并实时计算出物体表面接收到的环境光。

这样做的好处是,用极少的计算量(通常只需 2 阶 9 个系数)就能模拟出非常逼真的全局光照效果,让动态物体完美地融入静态场景。

  1. 3DGS中的球谐函数
    在 3DGS 中,球谐函数的用途与 PBR 略有不同。它不是用来计算环境光,而是直接用来表示每个高斯椭球自身的视角相关颜色(View-dependent Color)。

3DGS 的核心思想是,一个高斯点在不同视角下看起来颜色是不同的,这模拟了现实世界中物体表面的高光(Specular Highlight)和菲涅尔效应(Fresnel Effect)。例如,一个光滑的桌面,从某个角度看会有刺眼的反光,换个角度则没有。

因此,在 3DGS 训练阶段,算法会为每个高斯点学习一组球谐系数(存储在 .ply 文件的 f_rest 字段中)。这组系数编码了该点在所有方向上的颜色变化规律。而在渲染阶段,当摄像机观察这个高斯点时,GPU 会根据观察方向(View Direction)和这组 SH 系数,实时计算出该点在当前视角下应该呈现的颜色。

为了平衡渲染质量与性能,3DGS 通常默认使用 3 阶球谐函数。这意味着公式 (24) 中的求和上限
n
取值为 3,阶数
l
的取值范围是
0
,
1
,
2
,
3
。根据球谐函数的性质,对于每一个阶数
l
,频率
m
的取值范围是

l

l
。因此,总的基函数数量(即系数个数)为 1 + 3 + 5 + 7 = 16 个。

将这 16 个基函数项代入公式 (24),我们可以将其展开为四个部分的加权和:

f
(
θ
,
ϕ
)

0

m

0

c
m
0
Y
m
0


0 阶: 基础漫反射

    1

    m


    1

    c
    m
    1
    Y
    m
    1
    

    1 阶: 线性方向

      2

      m


      2

      c
      m
      2
      Y
      m
      2
      

      2 阶: 二次曲面

        3

        m


        3

        c
        m
        3
        Y
        m
        3
        

        3 阶: 三次细节

        (26)

        具体展开后的 16 项如下:

        f
        (
        v
        )

        c
        0
        0
        (
        0.28
        )
        +
        c

        1
        1
        (

        0.48
        y
        )
        +
        c
        0
        1
        (
        0.48
        z
        )
        +
        c
        1
        1
        (

        0.48
        x
        )
        +
        c

        2
        2
        (
        1.09
        x
        y
        )
        +
        c

        1
        2
        (

        1.09
        y
        z
        )
        +
        c
        0
        2
        (
        0.31
        (
        3
        z
        2

        1
        )
        )
        +
        c
        1
        2
        (

        1.09
        x
        z
        )
        +
        c
        2
        2
        (
        0.54
        (
        x
        2

        y
        2
        )
        )
        +
        c

        3
        3
        (

        0.59
        (
        3
        x
        2

        y
        2
        )
        y
        )
        +
        c

        2
        3
        (
        2.89
        x
        y
        z
        )
        +
        c

        1
        3
        (

        0.45
        y
        (
        4
        z
        2

        x
        2

        y
        2
        )
        )
        +
        c
        0
        3
        (
        0.39
        z
        (
        5
        z
        2

        3
        )
        )
        +
        c
        1
        3
        (

        0.45
        x
        (
        4
        z
        2

        x
        2

        y
        2
        )
        )
        +
        c
        2
        3
        (
        1.44
        z
        (
        x
        2

        y
        2
        )
        )
        +
        c
        3
        3
        (

        0.59
        x
        (
        x
        2

        3
        y
        2
        )
        )

        (27)

        在这个公式中:

        常数项:这些数字并非随意指定,而是根据球谐基函数的通用数学定义,代入不同的阶数
        l
        和频率
        m
        后,计算出的归一化系数。它们确保了基函数在球面上的能量分布是标准的。
        x
        ,
        y
        ,
        z
        多项式:球谐基函数原本是基于球体极坐标系(
        θ
        ,
        ϕ
        )定义的,包含复杂的三角函数(如
        sin
        θ
        cos
        ϕ
        )。但在 3D 图形学的实际应用中,为了方便 GPU 进行高效的矩阵运算,我们通常会将这些极坐标下的三角函数,通过数学变换“翻译”成三维笛卡尔坐标系下的多项式形式。
        观察方向:球谐函数本身是定义在“方向”上的,3DGS 中计算的是“当前视角”下的颜色,所以这个“方向”就是摄像机的观察方向,也就是这里的笛卡尔坐标
        (
        x
        ,
        y
        ,
        z
        )

        RGB 三通道:上述公式中的每一个系数
        c
        m
        l
        实际上都是一个包含 R、G、B 三个分量的向量。因此,对于 RGB 三个通道,总共需要存储
        16
        ×
        3

        48
        个浮点数。
        这使得 3DGS 能够用极低的存储开销(使用 3 阶 SH,共 16 个系数,RGB 三通道共 48 个 float 值),就模拟出复杂的光照交互效果,让重建的场景看起来富有光泽和质感,而不是像塑料一样平淡。

        1. 3DGS 着色伪代码
          结合前面的知识,我们可以写出 3DGS 中计算最终颜色的核心伪代码。这个过程发生在片元着色器(Fragment Shader)中:

        // 片元着色器伪代码

        // 输入
        vec3 view_direction; // 观察方向
        float sh_coeffs[48]; // 完整的球谐系数 (f_dc_0~2 + f_rest_0~44)
        float opacity; // 基础不透明度

        // 1. 计算 16 个球谐基函数值 (基于观察方向)
        float sh_basis[16];
        sh_basis[0] = 0.282095; // 0 阶 (对应 f_dc)
        sh_basis[1] = -0.488603 * view_direction.y; // 1 阶 (对应 f_rest 前3个)
        sh_basis[2] = 0.488603 * view_direction.z;
        sh_basis[3] = -0.488603 * view_direction.x;
        // … 计算 2 阶和 3 阶的基函数 (对应 f_rest 后42个)

        // 2. 统一计算最终颜色
        // 将 48 个系数与 16 个基函数进行加权求和
        vec3 final_color = vec3(0.0);
        for (int i = 0; i < 16; i++) {
        // RGB 三个通道分别计算
        final_color.r += sh_coeffs[i] * sh_basis[i];
        final_color.g += sh_coeffs[i + 16] * sh_basis[i];
        final_color.b += sh_coeffs[i + 32] * sh_basis[i];
        }

        // 3. 计算最终 Alpha
        float alpha = opacity * exp(-0.5 * d_squared);

        // 输出
        frag_color = vec4(final_color, alpha);
        通过这套机制,3DGS 巧妙地将复杂的、视角相关的光照效果“烘焙”进了一小组球谐系数中,实现了在极低计算成本下的高质量实时渲染。

        版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
        网站建设 2026/7/19 23:51:44

        金融 AI 分析系统:人工智能驱动的金融行情分析系统落地方案

        在金融市场数字化转型的过程中&#xff0c;传统行情分析长期面临数据来源分散、人工复盘效率低、分析结论依赖个人经验、结果呈现标准化不足等痛点。人工智能技术的落地&#xff0c;并非简单在分析环节叠加模型&#xff0c;而是需要从数据采集、智能解析、结果输出到系统运维的…

        作者头像 李华
        网站建设 2026/7/19 23:48:01

        NitroStack缓存策略指南:提升AI应用响应速度的5种终极方法

        NitroStack缓存策略指南&#xff1a;提升AI应用响应速度的5种终极方法 【免费下载链接】nitrostack The full-stack TypeScript framework to build, test, and deploy production-ready MCP servers and AI-native apps. 项目地址: https://gitcode.com/gh_mirrors/ni/nitro…

        作者头像 李华
        网站建设 2026/7/19 23:44:16

        Python焚诀之函数

        Python焚诀之函数题目1&#xff1a;简单问候函数 编写一个函数 say_hello(name)&#xff0c;接收一个名字参数&#xff0c;返回 "你好&#xff0c;{name}&#xff01;" 格式的字符串。 要求&#xff1a; 定义函数&#xff0c;带一个参数使用 return 返回结果调用函数…

        作者头像 李华