球谐函数——irradiance map

Tags:

球谐函数在图形学里最大的应用就是编码压缩irradiance map。

从radiance map输出irradiance map

radiance map是什么

wiki:https://en.wikipedia.org/wiki/Reflection_mapping

其实也就是hdr map、enviroment map。一般是一张全景照片。

可想而知,这张图图片一般可以用cube map的方式存储,即6张方形图,这其实就是skybox的贴图了(天光)。

为什么又叫hdr map,是因为为了得到pbr效果的话,这张radiance map的亮度范围要允许足够大,如果只是存0-255的亮度的话就不是pbr了,hdr map至少是32位float。

球形环境贴图

6张方形图虽然好理解,但是不利于计算。于是就有了球形环境贴图的存储方式,球形环境贴图一般宽度是高度的两倍,往中间切开的话,能得到2张1:1的方形图。

球形环境贴图的坐标理解

因为像素有面积,那么可设:

  • 每个pixel左下角的坐标为(x,y),则(x=0,y=0)代表图像左下的第一个像素,(x=w-1,y=h-1)代表右上的最后一个像素
  • 用(u,v)表示像素的单位化坐标,那么( u=(x+0.5)/w,v=(y+0.5)/h )就表示了像素(x,y)的中心点,( u=0,v=0 )代表图像左下的第一个像素的左下角,而( u=(w-1+1)/w,v=(h-1 + 1)/h ),即(u=1,v=1) 表示右上的最后一个像素的右上角

可见uv的取值范围是[0, 1]。我们此时已得到了像素坐标xy转成uv坐标的方法。

再设球形环境贴图的横坐标轴为θ,纵坐标是φ(0到π),

我们想要的θ、φ取值需要在指定范围,θ是-π到π,φ是0到π,θ相当于环绕y轴一整个圆圈,φ则相当于绕x轴半个圆圈。这样就覆盖了整个单位球的球面(记住这是3D空间的球)。

θ是-π到π的好处是,能使得球形环境贴图是看起来是对称的。左半边的横坐标轴取值范围是-π到0,右半边的范围是0到π,纵轴一样是0到π。

可以用以下公式把uv坐标转成θ、φ球坐标:

float theta = pi*(uv.x*2.0f - 1.0f);
float phi = pi*uv.y;

有了θ、φ球坐标后,还可以转成3D笛卡尔坐标。

\[ x = r * cos\theta sin \phi \]

\[ y = r * sin\theta sin \phi \]

\[ z = r * cos \phi \]

float x = sin(phi)*sin(theta);
float y = cos(phi);
float z = -sin(phi)*cos(theta); // 这里故意对z取反,不影响变换正确性

x、y、z做了一些调换。

此时我们就得到了从像素坐标到3D笛卡尔坐标的转换公式。在CPU计算irradiance map时,可以预先把这个转换关系烘焙到一个w*h大小的、元素为vector3的二维数组里,称之为directionImage。下文会用到这个directionImage。

什么是irradiance map

前面介绍了radiance map是一张场景全景图,那么当场景里有一个对象,要计算场景对该对象的光照影响时,并不是直接在这个对象的fragment shader里去采样radiance map,这是错误的。

首先假设只考虑diffuse光照的情况。那么对象上的每一个着色点,都受到场景四面八方的环境光影响。所以每一个着色点应该是去采样整张环境贴图(假设没有遮挡),而不是环境贴图的一个点。

用代码解释会更好理解,但需要回顾下什么是蒙特卡洛算法。因为采样整张环境贴图就是一次积分过程,于是可以用蒙特卡洛,把积分变成累加再求平均,以下是没有重要性采样的蒙特卡罗算irradiance map的算法:

// m_directionImage包含了所有方向,direction是笛卡尔坐标系朝向,pixelPos是2D像素坐标
data.m_directionImage.parallelForPixels2D([&](const vec3& direction, ivec2 pixelPos)
{
    // basis是以该方向为法线的世界空间正交坐标系
    mat3 basis = makeOrthogonalBasis(direction); 
    vec3 accum = vec3(0.0f);

    // 迭代随机采样1000次,随机一个方向
    for (u32 sampleIt = 0; sampleIt < m_hemisphereSampleCount; ++sampleIt)
    {

        // u等于sampleIt/m_hemisphereSampleCount,v随机
        vec2 sampleUv = sampleHammersley(sampleIt, m_hemisphereSampleCount);

        // uv转成球坐标再转成笛卡尔坐标,注意这是local坐标系的
        vec3 hemisphereDirection = sampleCosineHemisphere(sampleUv);

        // 投影到该世界坐标系
        vec3 sampleDirection = basis * hemisphereDirection;

        // 采样hdr map得到该方向对应的radiance只,加到accum
        accum += (vec3)m_radianceImage.sampleNearest(cartesianToLatLongTexcoord(sampleDirection));
    }

    // 求平均
    accum /= m_hemisphereSampleCount;

    // 输出到irradiance map
    m_irradianceImage.at(pixelPos) = vec4(accum, 1.0f);
});

其中m_irradianceImage是一个和m_radianceImage一样大的数组。

有重要性采样的蒙特卡洛,同样采样数的前提下,精度会更高。因为涉及到复杂的pdf问题,这里不介绍了。

用球谐函数编码irradiance map

上一节的蒙特卡洛方法求出来的irradianceImage虽然非常准确,但是很占内存。这对于实时渲染是很糟糕的,意味着要让gpu缓存一张巨大的irradiance map贴图。

记采样方向为θ、φ,以及f(θ, φ)表示该方向的环境光,因为f本质是一个积分,那么

球谐的基函数

生成步骤

(未经授权禁止转载)
Written on March 27, 2022

写作不易,您的支持是我写作的动力!