辐照度贴图的生成算法分析

在读这篇博客的时候,需要读者具备一些基本的IBL(image-based lighting)的相关知识。
比如什么是辐射率,辐射度,立体角等相关的知识。还需要知道球坐标和笛卡尔坐标之间的相互转的关系等。
同时官方参考网址:https://learnopengl.com/PBR/IBL/Diffuse-irradiance
源代码也有:https://github.com/JoeyDeVries/LearnOpenGL.git
对应的源码是:
在这里插入图片描述
ok,完整的代码,读者都可以自行下载进行分析。

下面是我再次读后的读后感。

1、如果场景中只有有限个光源(无论是点光源还是平行光),那么其中计算某个光源对物体表面一点的影响是直观的而且是简单的,只要用兰伯特n和l直接点乘,再做对应的衰减等,就可以完成光照的计算。
这些都是直接的光照。
优点是计算简单,直观;缺点是没有考虑周围环节的影响(再具体地说就是环境贴图对物体的影响)。
2、如果把周围的环境(环境或者天空盒)都考虑进去的话,那么意味着什么,意味着,环境贴图上的每个像素都是一个点光源,都会对物体上的一个点,进行光照影响,这就是IBL的核心。

那么怎么将这个算法落地实现呢?
答案就是对环境贴图进行预处理。这里我们要明确算法的流程:

  1. 以物体上的一个点p为例:
    在这里插入图片描述
    它需要考虑所有方向进入的光线对其的影响,所有就要进行积分运算。
    那么积分又是不切实际的,对于实时光照来说,所以考虑是否能将环境贴图进行预处理,最后只要给出一个方向,直接采样就行。

答案是肯定的,这也是本节主要的讲解的内容。

  1. 预处理环境贴图的shader编写
Shader irradianceShader("2.1.2.cubemap.vs", "2.1.2.irradiance_convolution.fs");

顶点着色器是:

#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 WorldPos;
uniform mat4 projection;
uniform mat4 view;
void main()
{
    WorldPos = aPos;  
    gl_Position =  projection * view * vec4(WorldPos, 1.0);
}

顶点着色器的输入只有一个:aPos
输出有两个:WorldPos和gl_Position

下面看下片元着色器:

#version 330 core
out vec4 FragColor;
in vec3 WorldPos;
uniform samplerCube environmentMap;
const float PI = 3.14159265359;
void main()
{		
	// The world vector acts as the normal of a tangent surface
    // from the origin, aligned to WorldPos. Given this normal, calculate all
    // incoming radiance of the environment. The result of this radiance
    // is the radiance of light coming from -Normal direction, which is what
    // we use in the PBR shader to sample irradiance.
    vec3 N = normalize(WorldPos);
    vec3 irradiance = vec3(0.0);   
    // tangent space calculation from origin point
    vec3 up    = vec3(0.0, 1.0, 0.0);
    vec3 right = cross(up, N);
    up            = cross(N, right);

直接将WorldPos作为切空间的法线方向。
切空间的up向量这里定义为:(0.0,1.0,0.0)
opengl是右手坐标系,计算right是up叉乘N,得到了right向量。
而up向量,又用N叉乘right得到。
这样up、right、N分别正交,构成了切空间。

	float sampleDelta = 0.025;
    float nrSamples = 0.0;
    for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
    {
        for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
        {
            // spherical to cartesian (in tangent space)
            vec3 tangentSample = vec3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
            // tangent space to world
            vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; 

            irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
            nrSamples++;
        }
    }
    irradiance = PI * irradiance * (1.0 / float(nrSamples));
    FragColor = vec4(irradiance, 1.0);
}

第一个for:增量是sampleDelta=0.025,范围是0到2PI(方位角)
第二个for:增量是sampleDelta=0.025,范围是0到0.5PI(天顶角)
然后是球坐标转换为笛卡尔坐标:
在这里插入图片描述

x=r*sinθ*cosφ
y=r*sinθ*sinφ
z=r*cosθ

笛卡尔坐标系(x,y,z)与球坐标系(r,θ,φ)的转换关系
在这里插入图片描述
我们可以在unity画下试试:
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public Vector3 normal;
    public float length = 4;
    public float sampleDelta = 1.0f;
    private const float PI = 3.14159265359f;

    public void OnDrawGizmos()
    {
        Vector3 N = Vector3.Normalize(normal);
        Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
        Vector3 right = Vector3.Cross(up, N);
        up = Vector3.Cross(N, right);
        Gizmos.color = Color.blue;
        Gizmos.DrawLine(Vector3.zero, N.normalized * 2 * length);
        Gizmos.color = Color.red;
        Gizmos.DrawLine(Vector3.zero, right.normalized * 2 * length);
        Gizmos.color = Color.green;
        Gizmos.DrawLine(Vector3.zero, up.normalized * 2 * length);

        Gizmos.color = Color.white;
        for (float phi = 0.0f; phi < 2.0 * PI; phi += sampleDelta)
        {
            for (float theta = 0.0f; theta < 0.5 * PI; theta += sampleDelta)
            {
                Vector3 tangentSample = new Vector3(
                    Mathf.Sin(theta) * Mathf.Cos(phi), 
                    Mathf.Sin(theta) * Mathf.Sin(phi), 
                    Mathf.Cos(theta));

                Vector3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
                Gizmos.DrawLine(Vector3.zero, sampleVec.normalized * length);
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述
这其实不是什么坐标转换,如果换个思路理解就更简单了。

看下图:
在这里插入图片描述
如图所示向量(1,1)。它实在x轴(1,0)和y轴(0,1)这个坐标系的坐标为(1,1)。
试想着,如果我们把坐标系旋转个45度,那么此时x’轴就为(1,-1)而y’轴为(1,1)。
那么此时向量(1,1)在这个坐标系下的坐标是多少呢?
很自然的点乘得到:
x轴坐标为:(1,1)dot(1,-1)=0
y轴坐标为:(1,1)dot(1,1)=2
单位化之后得到:(0,1)
在这里插入图片描述

那么我们知道了,其实上面的这个采样的过程就是,根据法线,求得一个半球,然后对半球上的所有点(自定义离散的度)进行采样。
法线变了,半球也会随之改变,但是始终是在正半球的上方,预计分就是对所有的这些采样点进行累加。

关于radiance和irradiance的区别:
参考:https://www.cnblogs.com/lsy-lsy/p/16556651.html
Irradiance是单位面积上来自所有方向的能量;
Radiance是单位面积上从某个方向上入射进来的能量;
两者之间的关系Radiance在单位球的上半部分对立体角积分,可得到Irradiance。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值