镜面 IBL 预过滤贴图的计算

镜面 IBL 预过滤贴图,本质是在提前计算不同粗糙度下,镜面反射方向附近能看到的环境光平均值。预过滤贴图计算的核心流程:对 cubemap 的每一个方向 R,对每一个 roughness,做一次 GGX 重要性采样。

计算中用到的函数:

cpp 复制代码
// 把一个 32 位整数的二进制位完全反转
uint ReverseBits32(uint bits){
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u)  | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u)  | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u)  | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u)  | ((bits & 0xFF00FF00u) >> 8u);
    return bits;
}
// 将反转后的32位整数映射到[0, 1]
// 2.3283064365386963e-10 ≈ 2^(-32)
float radicalInverse(uint bits){
    return float(ReverseBits32(bits)) * 2.3283064365386963e-10;
}
// 输入遍历序号 i 和 遍历总数 sampleCount
// 输出 i/sampleCount 和 反转后被映射到[0, 1]的 i 
float2 hammersley(int i, int N){
    return float2(float(i) / float(N), radicalInverse(uint(i)));
}

void pixarONB(float3 n, out float3 b1, out float3 b2)
{
    float sign_ = n.z >= 0.0 ? 1.0 : -1.0;
    float a = -1.0 / (sign_ + n.z);
    float b = n.x * n.y * a;
    b1 = float3(1.0 + sign_ * n.x * n.x * a, sign_ * b, -sign_ * n.x);
    b2 = float3(b, sign_ + n.y * n.y * a, -n.y);
}

float3 rotateToNormal(float3 L, float3 N)
{
    float3 tangent, bitangent;
    pixarONB(N, tangent, bitangent);
    tangent   = normalize(tangent);
    bitangent = normalize(bitangent);
    return normalize(tangent * L.x + bitangent * L.y + N * L.z);
}

float3 importanceSampleGGX(float2 Xi, float3 N, float roughness)
{
    float a = roughness * roughness;
    float cosTheta = sqrt((1.0 - Xi.x) / (1.0 + (a * a - 1.0) * Xi.x));
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
    float phi = Xi.y * TWO_PI;
    float3 L = normalize(float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta));
    return rotateToNormal(L, N);
}

float distributionIBL(float NdotH, float roughness)
{
    float a2 = roughness * roughness;
    float denom = NdotH * NdotH * (a2 - 1.0) + 1.0;
    return a2 / (PI * denom * denom);
}

float3 getEnvironmentB1(float3 rayDir, float level)
{
	float2 tc = float2((atan2(rayDir.z, rayDir.x) / TWO_PI) + 0.5,
						acos(rayDir.y) / PI);
    return SAMPLE_TEXTURE2D_LOD(_Channel1, sampler_Channel1, tc, level).rgb;
}

镜面 IBL 预过滤贴图计算:

cpp 复制代码
// 通过法线方向 N 和粗糙度 roughness, 通过重要性采样 GGX 分布得到"预过滤环境光颜色"
// 通常用于 PBR IBL 的镜面反射环境光
float3 getPreFilteredColour(float3 N, float roughness, int sampleCount)
{
	// R:反射向量、N:法线向量、V:视角向量(被初始化为法线向量)
	// 假设观察方向正好等于反射方向,也等价于假设视角方向和表面法线对齐。
	float3 R = N;
    float3 V = R;

	// 权重累计,最后做归一化,防止颜色过亮或过暗
    float  totalWeight      = 0.0;
    // 用于累加采样到的环境颜色
    float3 prefilteredColor = float3(0,0,0);

	// #define ZERO 0
    for (int i = ZERO; i < sampleCount; i++)
    {
    	// 生成一个伪随机、平均分布的二维采样点 Xi.x、Xi.y ∈ [0, 1]
    	// Hammersley 序列是一种低差异,比纯随机采样更均匀
    	float2 Xi = hammersley(i, sampleCount);
    	// 根据 Xi 和 roughness 生成一个在法线 N 的附近的半程向量 H
    	// 其中 roughness 越大,生成的半程向量离法线 N 越远
        float3 H  = importanceSampleGGX(Xi, N, roughness);
        // H = normalize(L + V), 所以根据 V 和 H 可以计算出 L
        float3 L  = normalize(reflect(-V, H));

		// dot_c = max(dot(a, b), 0)
        float NdotL = dot_c(N, L);
        // 只保留表面上半球方向的采样,下半的反射看不到
        if (NdotL > 0.0)
        {
        	// 初始化环境贴图的 mip level, level 越大,采样越模糊的 mip
        	float level = 0.0;
        	// 条件编译,如果定义了就启用这部分代码,计算合适的 mip level
			#if ENV_FILTERING == 1
				// 计算两个点积
				// 法线和半程向量的夹角余弦
            	float NdotH = dot_c(N, H);
            	// 观察方向和半程向量的夹角余弦
            	float VdotH = dot_c(V, H);
            	// 计算采样概率密度 pdf
            	float pdf    = distributionIBL(NdotH, roughness * roughness) * NdotH / (4.0 * VdotH);
            	// 当前采样大约覆盖的立体角
                float omegaS = 1.0 / (float(sampleCount) * pdf);
                // 表示环境 cubemap 中一个像素大约覆盖的立体角
                float omegaP = 4.0 * PI / (6.0 * 512.0 * 512.0);
                level = max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0);
			#endif
			// 沿着方向 L,在指定 mip level 上采样环境颜色
			// NdotL 这是 Lambert 余弦权重,方向越接近法线,贡献越大;越贴近边缘,贡献越小
            prefilteredColor += getEnvironmentB1(L, level) * NdotL;
            // 累加权重
            totalWeight      += NdotL;
        }
    }
    // 返回归一化后的颜色
    return prefilteredColor / totalWeight;
}
相关推荐
Lsk_Smion1 小时前
力扣实训 _ [200].岛屿数量
算法·leetcode·深度优先
Boom_Shu1 小时前
长方形的关系
数据结构·c++·算法
CG_MAGIC1 小时前
V-Ray灯光系统详解:穹顶灯、面光与IES光域网
3d·贴图·效果图·建模教程·渲云渲染
ZhengEnCi2 小时前
O07-银行家算法
算法
装不满的克莱因瓶2 小时前
图像尺寸调整:缩放矩阵如何改变像素坐标?
人工智能·线性代数·数学·算法·机器学习·矩阵
Lumbrologist2 小时前
【C++】零基础入门 · 第 13 节:类与对象基础
java·c++·算法
LONGZETECH2 小时前
软硬协同+故障注入:无人机仿真维修与操控仿真底层算法逻辑拆解
大数据·c语言·算法·3d·unity·无人机
Lsk_Smion2 小时前
力扣实训 _ [543].二叉树的直径 _ [23].合并K个升序列表
数据结构·算法·leetcode