【Unity/大气渲染】单次散射的原理和简单实现

发布时间 2023-04-14 12:12:58作者: Relolihentai

这篇随笔将会简单实现一个基于物理的相对真实的大气渲染效果

如下图,太空中的星球、相对真实的天空盒

如果没有大气,太阳光没有直接照射到的地方将会是一片黑暗

而我们能从太空中看到星球表面泛起的蓝光,日出时的美丽景色,都得于太阳光在大气中的散射

地球的大气中充斥着诸如空气分子,小水滴,尘粒等微小分子

当太阳光遇到这些分子时,会改变辐射方向,我们将这种现象叫做散射

(通常辐射能不会转化为热能,而只是改变方向)

图1

在现实中,散射可能会发生无数次,介于性能,我们只实现一次散射就可以得到一个相对不错的效果

下面是单次散射的简单原理

我们首先思考一下太阳光如何通过大气进入我们的眼睛

假设我们所在的位置为 A点

现在给定一个观察方向,我们看向天空,视线与大气顶部相较于B点,而迎着我们视线方向的太阳光都会进入我们的眼睛

(实际上我们知道,我们的视野不是一条线,而是可以假定为一个视锥体,所以一副画面将会有许多条视线方向充斥着视锥体)

由于我们只计算单次散射,所以只需要计算刚好散射到我们视线方向的太阳光就可以了

图2

但其实!有一件事情

这看似只有一次散射,但散射实际上时时刻刻都在发生,太阳光每向前传播一点就会遇到大气粒子然后发生散射

也就是说光线在这条路径上不断发生着散射,而散射就会造成太阳光向其他方向分散,就可能不会进入我们的眼睛

而单次散射的实际意义是 只计算光的一次转向,两次之后转向的光我们统一按照衰减计算

所以固定方向光线的衰减就是类似的过程,光线在传播过程中不断地被散射,越来越弱

 

因此太阳光进入我们眼睛的整个流程是这样的

 对于给定的视线方向,太阳光沿着固定方向 LightDir 入射,在 CP段 不断被散射衰减,然后与 AB段 形成无数个交点,我们将其成为Pi(i = 1,2....),在 P处 散射一部分光刚好迎着我们的视线方向进入我们的眼睛A点,PA段也不断被散射衰减

·  

为什么要首先给定视线方向,其实这是适应shader的编写,因为我们写shader都是对顶点和片元操作

而模型通常是空心的,我们不会直接取到球中的一个点,而是球最外层的一个点,也就是我们的B点

而我们也知道A点(摄像机坐标),所以直接计算视线方向上散射进摄像机的光

 

在这里我会简单说一下shader计算的具体思路,方便理解公式的推导

我们知道太阳光和 AB段 形成了无数个交点 Pi,在每个交点 Pi 上都有一部分光被散射进摄像机

介于性能,我们不会全部计算,而是将 AB段 均分成 N 份,也就是采样 N 次,计算每个采样点散射进摄像机的光

具体思路为,对于 球模型 的一个顶点,我们将其定为 B点,计算 AB段 的长度,除以采样数量 N,得到 步长step

我们还可以得到 向量AB,进而得到 方向向量AB,乘以 步长step 就可以得到一步前进的向量

从 摄像机A点 开始,循环 N 次,每次计算一个 采样点P,再加上步长step得到下一个采样点,累加得到一条视线方向最终的散射结果

然后 顶点 / 片元 着色器会遍历顶点输出最终的画面效果

下面是公式的简单推导(不涉及具体证明)

我们首先计算到达 P点 的光强,由于只计算在 P点 的一次转向所以 CP 段只计算光的衰减

设定 C点 的入射光强为 Ic,到达 P点 的光强为 Ip

图3

\(Ip=Ic\cdot T\left(CP\right)\)

T(CP) 为光线从 C点 到 P点 的衰减方程,我们又将其叫做 透射函数

到达 P点 后,设定到达 A点 的光强为 Ia

\(Ia=Ip\cdot S\left(CP,PA\right)\cdot T\left(PA\right)\)

T(PA) 和上面一样,是 PA段 的透射函数

S(CP, PA) 是沿 CP方向 传播的光向 PA 方向散射的散射函数

就是从 C点 到达 P点的光 又有多少刚好散射到 PA 方向

简单地代换一下我们就可以知道从 C点 到 A点 的光强

\(Ia=Ic\cdot S\left(CP,PA\right)\cdot T\left(CP\right)\cdot T\left(PA\right)\)

而我们知道 AB段 上有 N 个 P点

所以对于一个顶点,最终到达 摄像机的光强

\(Ia=Ic\cdot\sum_{n=1}^{N}S\left(CP,PA\right)\cdot T\left(CP\right)\cdot T\left(PA\right)\)

也就是说实际上我们只需要知道透射和散射函数就可以了

于是就轮到解释一些非常简单的物理公式

因为我们知道透射衰减实际上也是散射,我们只需要将所有角度的散射加起来(求球面积分)就可以得到衰减

所以来了解这次涉及到的两种主要散射,瑞利散射(Rayleight)和米氏散射(Mie)

 

瑞利散射,指光线在遇到比自身波长小的粒子时发生的散射,而且会更多地散射波长短的光,比如蓝光,所以我们能够看到地球表面有一层薄薄的蓝光

米氏散射,指光线在遇到和自身波长差不多或大一些的粒子时发生的散射,对所有所有波长的光都会同等散射,并且大部分光都会沿着原来原来的方向传播

所以光路的形成得于米氏散射,比如我们熟悉的丁达尔效应

 

瑞利散射函数公式

\(S\left(λ,θ,h\right)=\frac{\pi^{2}\left(n^{2}-1\right)^{2}}{2}\cdot\frac{ρ\left(h\right)}{H_{0}}\cdot\frac{1}{λ^{4}}\left(1+\cos^{2}θ\right)\)

其中 n 为空气折射系数 1.00029

ρ(h) 为密度比率,在海平面为 1,随高度增加指数下降

H0 为标准大气的分子数密度 2.504*10^25

而我们对散射函数进行球面积分,就得到了透射函数

\(β\left(λ,h\right)=\int_{0}^{2\pi}\int_{0}^{\pi}S\left(λ,θ,h\right)\cdot\sinθ\cdot dθ\cdot dφ=\frac{8\pi^{3}\left(n^{2}-1\right)^{2}}{3}\cdot\frac{ρ\left(h\right)}{H_{0}}\cdot\frac{1}{λ^{4}}\)

 

下面我们对透射函数和散射函数进行拆解,这是计划的一部分(为了方便计算和编写)