文章具体参照 https://ericpolman.com/
本方法的思想就是把场景分成很多的小格子,然后计算每一个小格子里面的光照(LPV)。如果直接计算每个格子里面的光照那代价也是不可接受的,因此本算法用了一种很巧妙的方式来处理:从RSM计算得出的虚拟点光源(VPL)开始,将VPL的光照“注射”给最近的格子,然后再”传播”到整个场景。
直白的来说LPV就是将来自光源的光照信息存储在 3D 网格(通常使用少于场景像素数量级的3D纹理)中。每个主光源(本案例仅适用一个主平行光)都储存着它们照亮的世界中的哪些点(RSM得到),这些点在世界中具有坐标,因此可以在格网中对这些坐标进行分层处理。通过这种方式,您可以将照明点(虚拟点光源)保存在 3D 网格中,并可以使用这些初始点在整个场景中散布光线。
因此该算法通过将直接光照渲染为反射阴影贴图,将其注入一个体积(使用球谐表示)并在该体积中传播光通量(因此得名算法)并尽可能考虑遮挡来近似全局照明。
从上边的分析可以知道,本算法主要是分五步骤:
- 网格建立
- 光照存储
- 光照注入网格
- 光照传播
- 光照计算
一、 网格建立
包围体是一个简单的几何空间,里面包含着复杂形状的物体。为物体添加包围体的目的是快速的进行碰撞检测或者进行精确的碰撞检测之前进行过滤(即当包围体碰撞,才进行精确碰撞检测和处理)。包围体类型如下图包括球体、轴对齐包围盒(AABB)、有向包围盒(OBB)。
其中AABB包围盒是一种平行于坐标轴的长方体,计算的方式也很简单,只需要计算模型的最大的顶点和最小的顶点就可以构成一个AABB包围盒。
而由于需要将光照从一个格子传播到周围六个格子里面,需要能快速读取周围的格子,因此我采用的是将模型的AABB包围盒分割为等大(大小为CellSize)的小立方体。如此一来已知一个位置P,求属于某个格子如下:
box = ivec3((P - MinAABB) / CellSize)
求某个格子的中心坐标为:
center = (box - 0.5) * CellSize + MinAABB
二、 光照存储
网格建立好了,那每个网格里面存储的是什么呢?用什么存储呢?先来看看与光照有光的物理量,如果想详细了解可以看一下以前文章【PBR系列二】辐射度量学理论。
简单回顾一下基本的物理量:
2.1 光照的物理量
- Radiant flux Φ :单位时间内光源向各个方向发出的总能量。
- Radiant Intensity I(w) :对于一个光源向单位立体角 w 发出的能量。
- Irradiance E(x) :对于一个点 x 单位面积上接收到(垂直于点 x )的能量。
- Radince L(p,w) :对于一个光源从单位面积上向单位立体角[公式]发出的能量。
一定要注意上述物理量是光源发出还是着色点接受。
那大家觉得一个网格里面应该存储那个物理量呢?由于建立网格是为了将光照进行向周围传播那么每个网格中心都应该看成一个光源,那么我们记录的应该就是Radiant flux Φ 。但问题又来了,这里的光源往各个方向辐射的能量不是均等的,因此无法用一个常量 Φ 而需要记录每一个方向的 Φ(w) 。当然我们不可能用一个Cubemap来记录 Φ(w) ,因此还需要另想办法。
大家把网格中心的光源想像成一个绝对光滑的小球,这样的好处是对于小球表面任意一个点接受到的Irradiance E(x,-w) 都会完全被垂直的反射出去也就是 Φ(w) 。
如此一来我们就可以记录小球每个点 p (法线为 Np ,每个点法线都不相同) 的接受到的Irradiance E(x,Np) 。有人可能会想这不还是需要记录每个方向吗?但是根据我们在球谐光照里面讲到的:
我们用球谐函数将 E(x,Np) 拆开,这样对于小球表面任意点 p 其中 L 都是一样的,因此只需要记录球谐系数 L ,在确定点 p 之后代入上式即可。同时根据Irradiance 和BRDF(本文只考虑漫反射因此BRDF = (1/π) )的关系也可以求出 L(p,w)
这里还要分析一下 L 的计算,在球谐光照里面是对于整个天空盒(立方体贴图)进行积分,伪代码即:
for(pixel &p : Cubemap)
Li += p.color * Yi(normalise(p.position)) * dw;
但这里没有立方体贴图只有点光源怎么办?其实可以想象成整个立方体贴图上只有几个像素点不为0,那么就只需要计算这些不为0 的点即可。
综上,为了最大限度地提高效率和帧速率,该算法将照明信息存储在球谐函数 (SH) 中。对于每一个小格子只需要存储球谐系数 L 即可,本文用的是两阶球谐函数,也就是对于每一个小格子存储4个球谐系数,因此本文用三张3D纹理来存储每个格子的球谐系数(RGB),这些3D纹理的大小也就是XYZ方向上的格子数目。
三、 光照注入网格
接下来就是实际的光照传播过程了,在此之前还需要利用RSM算法产生n个虚拟点光源(VPL),然后就需要把这些VPL注入到他们对应的格子里面去。我使用了渲染反射阴影贴图所产生的通量、世界空间位置和世界空间法线贴图,其中:
- 通量是注入的间接光的颜色
- 世界空间位置用于确定网格单元
- 法线确定初始传播方向。
只需要把一个格子里面的VPL当成类似与天空盒上的像素,然后计算Irradiance的球谐系数 L 即可。
而且并非 RSM 中的每个像素都将用于网格中的注入,有时候一些智能下采样仍然能提供稳定的结果。下图可以展示注射阶段后生成的 3D 网格。在白光下,可以看出 VPL 的颜色与它们被反射的表面非常相似。
1. 计算每个VPL所处的格子
ivec2 RSMCoords = ivec2(gl_VertexID % u_RSMResolution, gl_VertexID / u_RSMResolution);
v2f_posFromRSM = texelFetch(u_RSMPositionTexture, RSMCoords,0).rgb;
v2f_volumeCellIndex = ivec3((v2f_posFromRSM - u_MinAABB) / u_CellSize);
2. 按照公式计算 L
vec3 CellCenter = (v2f_volumeCellIndex - 0.5) * u_CellSize + u_MinAABB;
v2f_vplToCell = normalize(v2f_posFromRSM - CellCenter);
Li = v2f_fluxFromRSM * Y(v2f_vplToCell);
四、 光照传播
在将VPL的光照注入到最近的格子之后就可以开始进行光照传播了。要注意本文的光照传播也不具有数学依据只是作者定义出来的一种方式。假设以上图灰色的格子作为起点向周围的格子进行光照传播:
source cell为辐射能量的格子,destination cell为需要计算接受能量的格子
整个传播过程可以描述为:从source cell 辐射能量到destination cell的五个面(注意这里不包括直接相接的那个面),然后以这五个面作为光源计算destination cell 中心点(我们假设这个中心点为 d)接受到的 Irradiance E(d,nd) ,再将其球谐系数 L 即可。
以上图为例,其中 Lf(-nd) 为面Face f向点 d 辐射出来的Radiance,但计算整个面太复杂,因此作者就假设所有的能量都从平面中心点辐射出来(因为从中心辐射能量,那入射方向也是 nd ,并且 Lf(-nd) 作者也给出了一种近似的计算方法:
其中 L(Wc) 如上图所示是光源V向 Wc 辐射出来的Radiance, Ac 如图所示为一个立体角。
因为每个格子里面存储的都是球谐系数因此 L(Wc) 为(根据上两个式求的Radiance):
同时在对于点 d 计算得到 Lf(-nd) 之后还需要将其用球谐展开存成球谐系数:
在前面已经分析过了,由于球谐系数是可以累加的,因此直接进行累加即可。这里注意上两个式子都用到了球谐函数,但是球谐函数前面的系数不一样。
这样就求得了一个方向的球谐系数,那接着还有四个方向的球谐系数需要计算,步骤和上面一样,不过需要注意立体角 Ac 的不同,如下图所示,立体角 Ac 立体角 Ab 就不一样。
这种光照传播是一个极其不准确的过程,同时由于一次传播可能效果不好,因此作者建议多传播几次,最终达到一个比较好的效果。
您也可以使用朝向该单元格的方向计算该位置有多少能量可以传播到相邻单元格,然后将其乘以余弦,这是一种以漫射方式传播光的常用方法。下图显示了这样一个对象,指向“向上”即向前的方向具有 100% 的强度,而指向侧面或向后的方向具有 0% 的强度,因为光不会在该方向上物理反弹。
五、 光照计算渲染
渲染实际上是最简单的部分。我们在网格的每个单元格中都有一组每个颜色分量的 SH 系数。我们有一个带有世界空间位置和世界空间法线的 G-Buffer。我们得到世界空间位置的网格单元,并根据世界空间法线的 SH 存储的在网格单元中的系数进行计算。
具体计算过程如下:
5.1 GBufferPass
本阶段主要是取场景Gbuffer数据
VS:
#version 430 core
layout(location = 0) in vec3 _Position;
layout(location = 1) in vec3 _Normal;
layout(location = 2) in vec2 _TexCoord;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
uniform mat4 u_ModelMatrix;
out vec3 v2f_FragPosInViewSpace;
out vec2 v2f_TexCoords;
out vec3 v2f_Normal;
void main()
{
vec4 FragPosInViewSpace = u_ViewMatrix * u_ModelMatrix * vec4(_Position, 1.0f);
gl_Position = u_ProjectionMatrix * FragPosInViewSpace;
v2f_TexCoords = _TexCoord;
v2f_Normal = normalize(mat3(transpose(inverse(u_ViewMatrix * u_ModelMatrix))) * _Normal);
v2f_FragPosInViewSpace = vec3(FragPosInViewSpace);
}
PS:
#version 430 core
in vec3 v2f_FragPosInViewSpace;
in vec2 v2f_TexCoords;
in vec3 v2f_Normal;
layout (location = 0) out vec4 AlbedoAndMetallic_;
layout (location = 1) out vec4 NormalAndDoubleRoughness_;
layout (location = 2) out vec4 Position_;
uniform mat4 u_TransposeInverseViewModelMatrix;
uniform sampler2D u_DiffuseTexture;
uniform sampler2D u_NormalTexture;
uniform float u_Near = 0.1;
uniform float u_Far = 100.0f;
float LinearizeDepth(float vDepth)
{
float z = vDepth * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
vec3 FetchNormal(vec2 vTexcoord, mat3 vTBNMatrix)
{
vec3 n = texture2D(u_NormalTexture, vTexcoord).wyz * 2.0 - 1.0;
n.z = sqrt(max(1.0 - n.x*n.x - n.y*n.y, 0.0));
return (vTBNMatrix * n);
}
void main()
{
float gamma = 2.2;
vec3 diffuseColor = pow(texture(u_DiffuseTexture, v2f_TexCoords).rgb, vec3(gamma));
AlbedoAndMetallic_ = vec4(diffuseColor,1.0);
float alpha = textureLod(u_DiffuseTexture, v2f_TexCoords,0).a;
if(alpha != 1.0f)
discard;
NormalAndDoubleRoughness_ = vec4(v2f_Normal,0);
Position_ = vec4(v2f_FragPosInViewSpace, LinearizeDepth(gl_FragCoord.z));
}
5.2 RSMBufferPass
本阶段主要是获取VPL虚拟点光源:
VS:
#version 430 core
layout(location = 0) in vec3 _Position;
layout(location = 1) in vec3 _Normal;
layout(location = 2) in vec2 _TexCoord;
uniform mat4 u_ModelMatrix;
uniform mat4 u_LightVPMatrix;
out vec2 v2f_TexCoords;
out vec3 v2f_WorldNormal;
out vec3 v2f_WorldPos;
void main()
{
vec4 FragPosInWorldSpace = u_ModelMatrix * vec4(_Position, 1.0f);
gl_Position = u_LightVPMatrix * FragPosInWorldSpace;
v2f_TexCoords = _TexCoord;
v2f_WorldNormal = _Normal;
v2f_WorldPos = vec3(FragPosInWorldSpace);
}
PS:
#version 430 core
in vec2 v2f_TexCoords;
in vec3 v2f_WorldNormal;
in vec3 v2f_WorldPos;
layout (location = 0) out vec4 RadiantFlux_;
layout (location = 1) out vec4 NormalAndRoughness_;
layout (location = 2) out vec4 Position_;
layout (location = 3) out vec4 AlbedoAndMetallic_;
uniform vec3 u_LightColor = vec3(1);
uniform int u_RSMSize;
uniform int u_VPLsCount;
uniform float u_RSMCameraAreaInWorldSpace;
uniform sampler2D u_DiffuseTexture;
uniform float u_Intensity;
void main()
{
NormalAndRoughness_ = vec4(v2f_WorldNormal, 0);
float gamma = 2.2;
vec3 diffuseColor = pow(texture(u_DiffuseTexture, v2f_TexCoords,0).rgb, vec3(gamma));
AlbedoAndMetallic_ = vec4(diffuseColor,1.0);
float alpha = textureLod(u_DiffuseTexture, v2f_TexCoords,0).a;
if(alpha != 1.0f)
discard;
vec3 RadiantFlux = u_LightColor / u_VPLsCount * u_RSMCameraAreaInWorldSpace * u_Intensity * diffuseColor;
RadiantFlux_ = vec4(RadiantFlux, 1.0f);
Position_ = vec4(v2f_WorldPos, 1.0f);
}
5.3 DirectLightPass
CS:
#version 430 core
#pragma optionNV (unroll all) //暂时不知道有没有起作用
#define LOCAL_GROUP_SIZE 32
layout (local_size_x = LOCAL_GROUP_SIZE, local_size_y = LOCAL_GROUP_SIZE) in;
uniform sampler2D u_InputAlbedoTexture;
uniform sampler2D u_InputNormalTexture;
uniform sampler2D u_InputPositionTexture;
uniform sampler2D u_LightDepthTexture;
layout (rgba32f, binding = 0) uniform writeonly image2D u_OutputDirectIlluminationImage;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
uniform mat4 u_LightVPMatrixMulInverseCameraViewMatrix;
uniform vec3 u_LightDir;
uniform float u_Intensity;
void main()
{
ivec2 FragPos = ivec2(gl_GlobalInvocationID.xy);
vec4 Normal = texelFetch(u_InputNormalTexture, FragPos, 0);
vec4 Position = texelFetch(u_InputPositionTexture, FragPos, 0);
vec3 Albedo = texelFetch(u_InputAlbedoTexture, FragPos, 0).xyz;
if((abs(Normal.x) < 0.0001f) && (abs(Normal.y) < 0.0001f) && (abs(Normal.z) < 0.0001f))
{
imageStore(u_OutputDirectIlluminationImage, FragPos, vec4(0, 0, 0, 1));
return;
}
vec3 FragViewNormal = normalize(Normal.xyz);
vec3 FragViewPos = Position.xyz;
vec4 FragPosInLightSpace = u_LightVPMatrixMulInverseCameraViewMatrix * vec4(FragViewPos, 1);
vec3 LightDirInViewSpace = -normalize(vec3(u_ViewMatrix * vec4(u_LightDir, 0.0f))); //这个负号不要忘了
float DirectIllumination;
FragPosInLightSpace /= FragPosInLightSpace.w;
FragPosInLightSpace.xyz = (FragPosInLightSpace.xyz + 1) / 2;
float Visibility4DirectLight = 0.0f;
if(FragPosInLightSpace.z < 0.0f || FragPosInLightSpace.x > 1.0f || FragPosInLightSpace.y > 1.0f || FragPosInLightSpace.x < 0.0f || FragPosInLightSpace.y < 0.0f )
DirectIllumination = 0;
else
{
vec2 FragNDCPos4Light = FragPosInLightSpace.xy;
float ClosetDepth4Light = texture(u_LightDepthTexture, FragNDCPos4Light).r;
float Bias = max(0.00001 * (1.0 - dot(FragViewNormal, LightDirInViewSpace)), 0.00001);
Visibility4DirectLight = (FragPosInLightSpace.z - Bias < ClosetDepth4Light) ? 1.0f : 0.0f;
DirectIllumination = u_Intensity * max(dot(LightDirInViewSpace, FragViewNormal), 0) * Visibility4DirectLight;
}
imageStore(u_OutputDirectIlluminationImage, FragPos, vec4(DirectIllumination, DirectIllumination, DirectIllumination, 1));
}
5.4 LightInjectPass
本阶段处理光照注入:
VS:
#version 430 core
layout (location = 0) in vec2 _Position;
flat out ivec3 v2f_volumeCellIndex;
out vec3 v2f_posFromRSM;
out vec3 v2f_normalFromRSM;
out vec4 v2f_fluxFromRSM;
out vec3 v2f_vplToCell;
uniform sampler2D u_RSMRadiantFluxTexture;
uniform sampler2D u_RSMPositionTexture;
uniform sampler2D u_RSMNormalTexture;
uniform float u_CellSize;
uniform vec3 u_MinAABB;
uniform int u_RSMResolution;
ivec3 convertPointToGridIndex(vec3 vPos) {
return ivec3((vPos - u_MinAABB) / u_CellSize);
}
void main()
{
ivec2 RSMCoords = ivec2(gl_VertexID % u_RSMResolution, gl_VertexID / u_RSMResolution);
v2f_posFromRSM = texelFetch(u_RSMPositionTexture, RSMCoords,0).rgb;
v2f_normalFromRSM = texelFetch(u_RSMNormalTexture, RSMCoords,0).rgb;
v2f_fluxFromRSM = texelFetch(u_RSMRadiantFluxTexture, RSMCoords,0);
v2f_volumeCellIndex = convertPointToGridIndex(v2f_posFromRSM);
vec3 CellCenter = (v2f_volumeCellIndex - 0.5) * u_CellSize + u_MinAABB;
v2f_vplToCell = normalize(v2f_posFromRSM - CellCenter);
gl_Position = vec4(_Position, 0.0, 1.0);
}
PS:
#version 430 core
#extension GL_NV_shader_atomic_float : require
#extension GL_NV_shader_atomic_fp16_vector : require
#extension GL_NV_gpu_shader5 : require
#define PI 3.1415926f
#define SH_C0 0.282094792f // 1 / 2sqrt(pi)
#define SH_C1 0.488602512f // sqrt(3/pi) / 2
#define SH_cosLobe_C0 0.886226925f // sqrt(pi)/2
#define SH_cosLobe_C1 1.02332671f // sqrt(pi/3)
layout(rgba16f ,binding = 0) uniform image3D LPVGridR_;
layout(rgba16f ,binding = 1) uniform image3D LPVGridG_;
layout(rgba16f ,binding = 2) uniform image3D LPVGridB_;
flat in ivec3 v2f_volumeCellIndex;
in vec3 v2f_posFromRSM;
in vec3 v2f_normalFromRSM;
in vec4 v2f_fluxFromRSM;
in vec3 v2f_vplToCell;
vec4 evalSH_direct(vec3 dir) {
return vec4(SH_C0, -SH_C1 * dir.y, SH_C1 * dir.z, -SH_C1 * dir.x);
}
void main()
{
vec4 SHCoeffsR = evalSH_direct(v2f_vplToCell) * v2f_fluxFromRSM.r;
vec4 SHCoeffsG = evalSH_direct(v2f_vplToCell) * v2f_fluxFromRSM.g;
vec4 SHCoeffsB = evalSH_direct(v2f_vplToCell) * v2f_fluxFromRSM.b;
imageAtomicAdd(LPVGridR_,v2f_volumeCellIndex,f16vec4(SHCoeffsR));
imageAtomicAdd(LPVGridG_,v2f_volumeCellIndex,f16vec4(SHCoeffsG));
imageAtomicAdd(LPVGridB_,v2f_volumeCellIndex,f16vec4(SHCoeffsB));
}
5.5 GeometryInjectPass
本部分处理场景物体与格子之间的关系
VS:
#version 430 core
layout (location = 0) in vec2 _Position;
flat out ivec3 v2f_volumeCellIndex;
out vec3 v2f_posFromRSM;
out vec3 v2f_normalFromRSM;
out float surfelArea;
uniform sampler2D u_RSMPositionTexture;
uniform sampler2D u_RSMNormalTexture;
uniform float u_CellSize;
uniform vec3 u_MinAABB;
uniform int u_RSMResolution;
uniform float u_RSMArea = 4.0;
uniform mat4 u_LightViewMat;
ivec3 convertPointToGridIndex(vec3 vPos) {
return ivec3((vPos - u_MinAABB) / u_CellSize);
}
float calculateSurfelAreaLightOrtho(vec3 lightPos) {
return (u_RSMArea)/(u_RSMResolution * u_RSMResolution);
}
void main()
{
ivec2 RSMCoords = ivec2(gl_VertexID % u_RSMResolution, gl_VertexID / u_RSMResolution);
v2f_posFromRSM = texelFetch(u_RSMPositionTexture, RSMCoords,0).rgb;
v2f_normalFromRSM = texelFetch(u_RSMNormalTexture, RSMCoords,0).rgb;
vec4 viewPos = u_LightViewMat * vec4(v2f_posFromRSM,1.0);
surfelArea = calculateSurfelAreaLightOrtho(viewPos.xyz);
v2f_volumeCellIndex = convertPointToGridIndex(v2f_posFromRSM);
gl_Position = vec4(_Position, 0.0, 1.0);
}
PS:
#version 430 core
#extension GL_NV_shader_atomic_float : require
#extension GL_NV_shader_atomic_fp16_vector : require
#extension GL_NV_gpu_shader5 : require
#define PI 3.1415926f
#define SH_C0 0.282094792f // 1 / 2sqrt(pi)
#define SH_C1 0.488602512f // sqrt(3/pi) / 2
#define SH_cosLobe_C0 0.886226925f // sqrt(pi)/2
#define SH_cosLobe_C1 1.02332671f // sqrt(pi/3)
layout(rgba16f ,binding = 0) uniform image3D GeometryVolume_;
flat in ivec3 v2f_volumeCellIndex;
in vec3 v2f_posFromRSM;
in vec3 v2f_normalFromRSM;
in float surfelArea;
uniform float u_CellSize;
uniform vec3 u_LightDir;
vec4 evalCosineLobeToDir(vec3 dir) {
return vec4(SH_cosLobe_C0, -SH_cosLobe_C1 * dir.y, SH_cosLobe_C1 * dir.z, -SH_cosLobe_C1 * dir.x);
}
float calculateBlockingPotencial(vec3 dir, vec3 normal) {
return clamp((surfelArea * clamp(dot(normal,dir),0.0,1.0))/(u_CellSize * u_CellSize),0.0,1.0);
}
void main()
{
float BlockingPotencial = calculateBlockingPotencial(u_LightDir, v2f_normalFromRSM);
vec4 SHCoeffGV = evalCosineLobeToDir(v2f_normalFromRSM) * BlockingPotencial;
imageAtomicAdd(GeometryVolume_,v2f_volumeCellIndex,f16vec4(SHCoeffGV));
}
5.6 PropagationPass
本部分主要处理光照传播,并维持稳定处理。
VS:
#version 430 core
layout (location = 0) in vec3 _Position;
flat out ivec3 v2f_volumeCellIndex;
void main()
{
v2f_volumeCellIndex = ivec3(_Position);
gl_Position = vec4(0,0,0,1.0);
}
PS:
#version 430 core
#extension GL_NV_shader_atomic_float : require
#extension GL_NV_shader_atomic_fp16_vector : require
#extension GL_NV_gpu_shader5 : require
#define PI 3.1415926f
#define SH_C0 0.282094792f // 1 / 2sqrt(pi)
#define SH_C1 0.488602512f // sqrt(3/pi) / 2
#define SH_cosLobe_C0 0.886226925f // sqrt(pi)/2
#define SH_cosLobe_C1 1.02332671f // sqrt(pi/3)
layout(rgba16f ,binding = 0) uniform image3D RAccumulatorLPV_;
layout(rgba16f ,binding = 1) uniform image3D GAccumulatorLPV_;
layout(rgba16f ,binding = 2) uniform image3D BAccumulatorLPV_;
layout(rgba16f ,binding = 3) uniform image3D LPVGridR_;
layout(rgba16f ,binding = 4) uniform image3D LPVGridG_;
layout(rgba16f ,binding = 5) uniform image3D LPVGridB_;
uniform sampler3D LPVGridR;
uniform sampler3D LPVGridG;
uniform sampler3D LPVGridB;
uniform sampler3D GeometryVolume;
flat in ivec3 v2f_volumeCellIndex;
uniform vec3 u_GridDim;
uniform bool u_FirstPropStep;
uniform float occlusionAmplifier = 1.0f;
const float directFaceSubtendedSolidAngle = 0.4006696846f;
const float sideFaceSubtendedSolidAngle = 0.4234413544f;
vec4 evalSH_direct(vec3 dir) {
return vec4(SH_C0, -SH_C1 * dir.y, SH_C1 * dir.z, -SH_C1 * dir.x);
}
vec4 evalCosineLobeToDir(vec3 dir) {
return vec4(SH_cosLobe_C0, -SH_cosLobe_C1 * dir.y, SH_cosLobe_C1 * dir.z, -SH_cosLobe_C1 * dir.x);
}
const ivec3 propDirections[6] = {
//+Z
ivec3(0,0,1),
//-Z
ivec3(0,0,-1),
//+X
ivec3(1,0,0),
//-X
ivec3(-1,0,0),
//+Y
ivec3(0,1,0),
//-Y
ivec3(0,-1,0)
};
const ivec2 cellSides[4] = {ivec2(1.0, 0.0), ivec2(0.0, 1.0), ivec2(-1.0, 0.0), ivec2(0.0, -1.0)};
vec3 getEvalSideDirection(int index, ivec3 orientation) {
const float smallComponent = 0.4472135; // 1 / sqrt(5)
const float bigComponent = 0.894427; // 2 / sqrt(5)
const ivec2 side = cellSides[ index ];
vec3 tmp = vec3(side.x * smallComponent, side.y * smallComponent, bigComponent);
return vec3(orientation.x * tmp.x, orientation.y * tmp.y, orientation.z * tmp.z);
}
vec3 getReprojSideDirection(int index, ivec3 orientation) {
const ivec2 side = cellSides[ index ];
return vec3(orientation.x*side.x, orientation.y*side.y, 0);
}
void main()
{
vec4 cR = vec4(0.0);
vec4 cG = vec4(0.0);
vec4 cB = vec4(0.0);
for(int neighbour = 0; neighbour < 6; neighbour++) {
vec4 RSHcoeffsNeighbour = vec4(0.0);
vec4 GSHcoeffsNeighbour = vec4(0.0);
vec4 BSHcoeffsNeighbour = vec4(0.0);
ivec3 mainDirection = propDirections[neighbour];
ivec3 neighbourCellIndex = v2f_volumeCellIndex - mainDirection;
RSHcoeffsNeighbour = texelFetch(LPVGridR, neighbourCellIndex, 0);
GSHcoeffsNeighbour = texelFetch(LPVGridG, neighbourCellIndex, 0);
BSHcoeffsNeighbour = texelFetch(LPVGridB, neighbourCellIndex, 0);
vec3 occCoord = (vec3(neighbourCellIndex.xyz) + 0.5 * mainDirection) / u_GridDim;
vec4 occCoeffs = texture(GeometryVolume, occCoord);
float occlusionValue = 1.0 - clamp(occlusionAmplifier * dot(occCoeffs, evalSH_direct(-mainDirection)),0.0,1.0 );
float occludedDirectFaceContribution = occlusionValue * directFaceSubtendedSolidAngle;
vec4 mainDirectionCosineLobeSH = evalCosineLobeToDir(mainDirection);
vec4 mainDirectionSH = evalSH_direct(mainDirection);
cR += occludedDirectFaceContribution * max(0.0, dot(RSHcoeffsNeighbour, mainDirectionCosineLobeSH)) * mainDirectionSH;
cG += occludedDirectFaceContribution * max(0.0, dot(GSHcoeffsNeighbour, mainDirectionCosineLobeSH)) * mainDirectionSH;
cB += occludedDirectFaceContribution * max(0.0, dot(BSHcoeffsNeighbour, mainDirectionCosineLobeSH)) * mainDirectionSH;
for(int face = 0; face < 4; face++) {
vec3 evalDirection = getEvalSideDirection(face, mainDirection);
vec3 reprojDirection = getReprojSideDirection(face, mainDirection);
vec3 occCoord = (vec3(neighbourCellIndex.xyz) + 0.5 * evalDirection) / u_GridDim;
vec4 occCoeffs = texture(GeometryVolume, occCoord);
occlusionValue = 1.0 - clamp(occlusionAmplifier * dot(occCoeffs, evalSH_direct(-evalDirection)),0.0,1.0);
float occludedSideFaceContribution = occlusionValue * sideFaceSubtendedSolidAngle;
vec4 reprojDirectionCosineLobeSH = evalSH_direct(reprojDirection);
vec4 evalDirectionSH = evalCosineLobeToDir(evalDirection);
cR += occludedSideFaceContribution * max(0.0, dot(RSHcoeffsNeighbour, evalDirectionSH)) * reprojDirectionCosineLobeSH;
cG += occludedSideFaceContribution * max(0.0, dot(GSHcoeffsNeighbour, evalDirectionSH)) * reprojDirectionCosineLobeSH;
cB += occludedSideFaceContribution * max(0.0, dot(BSHcoeffsNeighbour, evalDirectionSH)) * reprojDirectionCosineLobeSH;
}
}
imageAtomicAdd(LPVGridR_,v2f_volumeCellIndex,f16vec4(cR / PI));
imageAtomicAdd(LPVGridG_,v2f_volumeCellIndex,f16vec4(cG / PI));
imageAtomicAdd(LPVGridB_,v2f_volumeCellIndex,f16vec4(cB / PI));
vec4 R = imageLoad(LPVGridR_, v2f_volumeCellIndex);
vec4 G = imageLoad(LPVGridG_, v2f_volumeCellIndex);
vec4 B = imageLoad(LPVGridB_, v2f_volumeCellIndex);
imageAtomicAdd(RAccumulatorLPV_, v2f_volumeCellIndex, f16vec4(R));
imageAtomicAdd(GAccumulatorLPV_, v2f_volumeCellIndex, f16vec4(G));
imageAtomicAdd(BAccumulatorLPV_, v2f_volumeCellIndex, f16vec4(B));
}
5.7 IndirectLightPass
VS:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
PS:
#version 430 core
#define SH_C0 0.282094792f // 1 / 2sqrt(pi)
#define SH_C1 0.488602512f // sqrt(3/pi) / 2
#define PI 3.1415926f
in vec2 v2f_TexCoords;
layout (location = 0) out vec4 Color_;
uniform sampler3D u_RAccumulatorLPV;
uniform sampler3D u_GAccumulatorLPV;
uniform sampler3D u_BAccumulatorLPV;
uniform sampler2D u_NormalTexture;
uniform sampler2D u_PositionTexture;
uniform float u_CellSize;
uniform vec3 u_MinAABB;
uniform mat4 u_InverseCameraViewMatrix;
vec4 evalSH_direct( vec3 direction ) {
return vec4( SH_C0, -SH_C1 * direction.y, SH_C1 * direction.z, -SH_C1 * direction.x );
}
ivec3 convertPointToGridIndex(vec3 vPos) {
return ivec3((vPos - u_MinAABB) / u_CellSize);
}
const ivec3 propDirections[8] = {
ivec3(u_CellSize * 0.5,u_CellSize * 0.5,u_CellSize * 0.5),
ivec3(u_CellSize * 0.5,u_CellSize * 0.5,-u_CellSize * 0.5),
ivec3(u_CellSize * 0.5,-u_CellSize * 0.5,u_CellSize * 0.5),
ivec3(u_CellSize * 0.5,-u_CellSize * 0.5,-u_CellSize * 0.5),
ivec3(-u_CellSize * 0.5,u_CellSize * 0.5,u_CellSize * 0.5),
ivec3(-u_CellSize * 0.5,u_CellSize * 0.5,-u_CellSize * 0.5),
ivec3(-u_CellSize * 0.5,-u_CellSize * 0.5,u_CellSize * 0.5),
ivec3(-u_CellSize * 0.5,-u_CellSize * 0.5,-u_CellSize * 0.5)
};
void main()
{
vec3 Normal = texture(u_NormalTexture,v2f_TexCoords).xyz;
vec3 Position = texture(u_PositionTexture, v2f_TexCoords).xyz;
Position = vec3(u_InverseCameraViewMatrix * vec4(Position,1));
vec4 SHintensity = evalSH_direct(Normal);
ivec3 LpvCellCoords = convertPointToGridIndex(Position);
vec3 LpvIntensity = vec3(0);
vec3 LpvCellBasePos = vec3(convertPointToGridIndex(Position)) * u_CellSize + u_MinAABB;
vec3 alpha = clamp((Position - LpvCellBasePos) / u_CellSize, vec3(0), vec3(1));
for (int i = 0; i < 8; ++i) {
ivec3 offset = ivec3(i, i >> 1, i >> 2) & ivec3(1);
ivec3 LpvCellCoords = convertPointToGridIndex(Position) + offset;
vec3 trilinear = mix (1 - alpha, alpha, offset);
float weight = trilinear.x * trilinear.y * trilinear.z;
weight = max(0.0002, weight);
LpvIntensity += weight
* vec3(
dot(SHintensity, texelFetch(u_RAccumulatorLPV, LpvCellCoords,0)),
dot(SHintensity, texelFetch(u_GAccumulatorLPV, LpvCellCoords,0)),
dot(SHintensity, texelFetch(u_BAccumulatorLPV, LpvCellCoords,0))
);
}
Color_ = vec4(max(LpvIntensity, 0 ),1) ;
}
5.8 ScreenQuadPass
最终显示
VS:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
PS:
#version 430 core
in vec2 v2f_TexCoords;
out vec4 Color_;
uniform sampler2D u_IndirectTexture;
uniform sampler2D u_DirectTexture;
uniform sampler2D u_AlbedoTexture;
uniform float u_Exposure = 2.0f;
void main()
{
vec3 Albedo = texture(u_AlbedoTexture, v2f_TexCoords).rgb;
if(equal(Albedo,vec3(0,0,0)) == bvec3(1,1,1))
Albedo = vec3(0.52, 0.77, 1);
vec3 TexelColor = (texture(u_DirectTexture, v2f_TexCoords).rgb * 0.8f + 5.0 * texture(u_IndirectTexture, v2f_TexCoords).rgb + 0.01) * Albedo;
vec3 mapped = vec3(1.0) - exp(-TexelColor * u_Exposure);
mapped = pow(mapped, vec3(1.0f / 2.2f));
Color_ = vec4(mapped, 1.0f);
}
六、 LPV算法总结
本文的全局光照算法与Light Probe算法类似,不过可以不用预计算,但是不适用于大场景多光源,且效率与效果制约于网格大小,属于四趟算法。以后有时间,我们来看一下NVIDIA的VXGI算法,这个才是大多数引擎的主流技术,效果优于LPV。