Global Illumination_Light Propagation Volumes (LPV)

article/2025/9/11 19:38:59

文章具体参照 https://ericpolman.com/
在这里插入图片描述

本方法的思想就是把场景分成很多的小格子,然后计算每一个小格子里面的光照(LPV)。如果直接计算每个格子里面的光照那代价也是不可接受的,因此本算法用了一种很巧妙的方式来处理:从RSM计算得出的虚拟点光源(VPL)开始,将VPL的光照“注射”给最近的格子,然后再”传播”到整个场景。

直白的来说LPV就是将来自光源的光照信息存储在 3D 网格(通常使用少于场景像素数量级的3D纹理)中。每个主光源(本案例仅适用一个主平行光)都储存着它们照亮的世界中的哪些点(RSM得到),这些点在世界中具有坐标,因此可以在格网中对这些坐标进行分层处理。通过这种方式,您可以将照明点(虚拟点光源)保存在 3D 网格中,并可以使用这些初始点在整个场景中散布光线。

因此该算法通过将直接光照渲染为反射阴影贴图,将其注入一个体积(使用球谐表示)并在该体积中传播光通量(因此得名算法)并尽可能考虑遮挡来近似全局照明。
在这里插入图片描述

从上边的分析可以知道,本算法主要是分五步骤:

  1. 网格建立
  2. 光照存储
  3. 光照注入网格
  4. 光照传播
  5. 光照计算

一、 网格建立

包围体是一个简单的几何空间,里面包含着复杂形状的物体。为物体添加包围体的目的是快速的进行碰撞检测或者进行精确的碰撞检测之前进行过滤(即当包围体碰撞,才进行精确碰撞检测和处理)。包围体类型如下图包括球体、轴对齐包围盒(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 corein  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 corein  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 32layout (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 corelayout (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 corelayout (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 corelayout (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] = {//+Zivec3(0,0,1),//-Zivec3(0,0,-1),//+Xivec3(1,0,0),//-Xivec3(-1,0,0),//+Yivec3(0,1,0),//-Yivec3(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 corelayout (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.1415926fin  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 corelayout (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 corein  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。


http://chatgpt.dhexx.cn/article/1aEO5V56.shtml

相关文章

LPV(Light Propagation Volumes)

lpv 测试了Light Propagation Volumes&#xff0c;全实时没有任何预处理的GI&#xff0c;而且可以适用任意场景。 文档很长&#xff0c;不过基本原理还是比较直白的&#xff1a; 生成reflect shadow map(rsm)。 将rsm信息用SH系数方式注入一个volumetexture中。 …

【GAMES-202实时渲染】4、3D空间全局光照(RSM、LPV、VXGI)

Lec7~8 1、Reflective Shadow Maps&#xff08;RSM&#xff09;2、Light Propagation Volumes&#xff08;LPV&#xff09;3、Voxel Global Illumination&#xff08;VXGI&#xff09; 1、Reflective Shadow Maps&#xff08;RSM&#xff09; RSM是一个特别经典的计算全局光照…

lpv

测试了Light Propagation Volumes&#xff0c;全实时没有任何预处理的GI&#xff0c;而且可以适用任意场景。 文档很长&#xff0c;不过基本原理还是比较直白的&#xff1a; 生成reflect shadow map(rsm)。 将rsm信息用SH系数方式注入一个volumetexture中。 在vol…

操作系统经典 pv过桥问题

Semophere bridge1; Semophere mutexNS1,mutexSN1;//用于保护countNS,countSN int countNS0,countSN0; Semophere s11,s20;//用于交替通过 StoN(){while(1){P(mutexSN);countSN;//来车了v(mutexSN);p(mutexSN);if(countNS0){//对面无车,则直接通过P(bridge);通过countSN--;V…

C语言解决四人/多人过桥问题

参加笔试的时候遇到一道经典的算法题&#xff0c;四人过桥问题。当时没写出来&#x1f605;。 四人过桥问题&#xff1a;在一个黑夜里&#xff0c;有四个人需要过桥&#xff0c;每次只能通过两人&#xff0c;其中一人必须拿着手电筒&#xff1b;但只有一个手电筒&#xff0c;所…

小明过桥问题

小明家必须要过一座桥。小明过桥最快要&#xff11;秒&#xff0c;小明的弟弟最快要&#xff13;秒&#xff0c;小明的爸爸最快要&#xff16;秒&#xff0c;小明的妈妈最快要&#xff18;秒&#xff0c;小明的爷爷最快要&#xff11;&#xff12;秒。每次此桥最多可过两人&…

过桥问题

在一个夜黑风高的晚上&#xff0c;有n&#xff08;n < 50&#xff09;个小朋友在桥的这边&#xff0c;现在他们需要过桥&#xff0c;但是由于桥很窄&#xff0c;每次只允许不大于两人通过&#xff0c;他们只有一个手电筒&#xff0c;所以每次过桥的两个人需要把手电筒带回来…

过桥问题的通解

问题一,一个典型过桥问题: 小明一家5口人在夜晚过一座桥,小明过桥要1分钟,小明的弟弟过桥要3分钟,小明的爸爸过桥要6分钟,小明的妈妈过桥要8分钟,小明的爷爷过桥要12分钟;这座桥每次只能过2个人,因是夜晚,过桥时必须提着灯,小明有一只灯,点燃后30分钟会熄灭,问怎…

如何打开tdms文件?

https://www.zhihu.com/question/305029962/answer/1203851780v 下载地址 http://www.ni.com/example/27944/en/ 还有一个综述&#xff0c;写的挺好&#xff01; https://wenku.baidu.com/view/c62700e4aa00b52acec7ca09.html

转载:TDM协议

转自http://www.wangdali.net/i2s/ 1. PCM简介 PCM (Pulse Code Modulation) 是通过等时间隔(即采样率时钟周期)采样将模拟信号数字化的方法。图11为4 bit 采样深度的PCM数据量化示意图。 图11. 4-bit PCM的采样量化 PCM数字音频接口,即说明接口上传输的音频数据通过PCM…

02 - DDMS

目录 一. DDMS 是什么&#xff1f; 二. 工作原理 三. ddmlib 1.ddmlib简介 总结 一. DDMS 是什么&#xff1f; DDMS 的全称是DalvikDebug Monitor Service&#xff0c;是 Android 开发环境中的Dalvik虚拟机调试监控服务。提供测试设备截屏、查看特定进程正在运行的线程以及堆信…

LabVIEW将现有数据文件映射至TDMS数据文件格式

LabVIEW将现有数据文件映射至TDMS数据文件格式 在某些情况下&#xff0c;可能无法使用TDMS文件格式&#xff0c;例如客户或供应商指定必须使用某种格式存储数据。有些传统仪器可能会自动使用某种自定义格式提供数据输出文件。此外&#xff0c;已经用某种方式收集的传统测量数据…

Matlab打开LabVIEW的tdm/tdms文件

1. 下去NI官网下载 MATLAB TDM Example文件 。 网址&#xff1a;Reading TDM/TDMS Files with The MathWorks, Inc. MATLAB Software - NI Community 这里我两个文件都下载了&#xff0c;但是只打开了2020那个&#xff0c;能用我就没看sp2010那个是干嘛的。 2. 使用Matlab打…

Labview-关于TDMS文件逻辑的学习-从今天开始学习Labview

1. TDMS文件的逻辑格式 TDMS文件的逻辑格式遵循TDM三层结构&#xff0c;仍然是文件、通道组、通道三层。用户在使用时只需要关心这三层就行了。2. TDMS文件API TDMS文件格式基本上可以称为NI用在测试测量领域的通用数据文件格式&#xff0c;LabVIEW, CVI/LabWindows, Signal Ex…

[Matlab科学计算] Matlab打开Labview保存的TDMS文件

1. TDMS文件简单介绍 TDMS文件格式由三个层次结构级别组成&#xff1a;文件、组、通道。文件级别可包含任意数量的组&#xff0c;而每个组又可包含任意数量的通道。通过通道分组&#xff0c;用户可以选择如何组织数据以使其更易于理解。每​个​TDMS​文件​都​包含​两​种​…

JAVA解析TDMS文件

2023年更新&#xff1a; 没想到还有人关注&#xff0c;上传了最新代码 https://github.com/yc97/TDMSDecoder 该代码经过测试&#xff0c;基本没什么bug了 reference: http://www.eefocus.com/Junking/blog/12-07/281264_7bf69.html http://www.ni.com/white-paper/14252/zh…

LabVIEW写入可快速加载的TDMS文件

LabVIEW写入可快速加载的TDMS文件 TDMS文件格式的设计目的是在尽可能快地读写数据的同时仍保持足够的灵活性来适应采集过程中通道数量和采样率的变化。 但是数据读写速度快的文件未必可快速加载。 TDMS文件是一个完全的二进制文件&#xff0c;由多个部分数据段组成&#xff0c;…

LabVIEW TDMS连续写入内存增长

LabVIEW TDMS连续写入内存增长 每次执行TDMS写入VI时&#xff0c;内存&#xff08;RAM&#xff09;使用量都会略有增加。这是内存泄漏吗&#xff0c;如何防止内存使用量增加&#xff1f; 解答 此问题有几种可能的解决方案&#xff1a; TDMS文件的引用可能没有适当地关闭。对…

Matlab查看tdms文件

由于最近项目需要使用Labview开发解调设备&#xff0c;对于高速采集卡就需要使用tdms存储数据&#xff08;存储的数据量较大&#xff09;&#xff0c;而用matlab无法对tdms格式文件进行直接读取&#xff0c;所以查找一些相关博客&#xff0c;解决了读取的问题。&#xff08;以下…

Labview数据存储与读取——TDMS文件的创建与写入

Labview数据存储与读取——TDMS文件的创建与写入 你好&#xff0c;这是我在自学Labview编写软件过程中使用的一些功能。我在存储采集卡数据时&#xff0c;通过阅读大量他人的程序&#xff0c;发现TDM及TDMS文件十分适合波形数据的记录&#xff0c;TDMS文件比TDM文件在存储动态…