我们本次来看一下VXGI技术,首先了解一下其基本情况:Parse Voxel Octree GI(又叫Voxel Cone Tracing)和VXGI都是NVIDIA的技术,VXGI就是SVOGI最终产品化后的名字。
内容大部分参照外网搜集的 VXGI(基于体素的锥形光线追踪)文档、论文资料
本算法的大体步骤如下:
- 把整个场景体素化,然后把体素化的场景保存在一个三维的数据结构里。
- 接着将直接光照通过用Reflective Shadow Map或者直接注入的方式把光照信息写到这个三维的数据结构里去。
- 在渲染时,得到像素的位置和法线信息之后就类似光线追踪一样追踪一些Cone,追踪的时候要从体素化后并且有直接光照信息的数据结构里读取光照信息,然后得到光照作用在当前像素上,完成渲染。
可以如下图直观理解其算法流程:
此外VXGI使用了Clip Map技术。Clip Map就是类似mip map的一种存储方式,只不过在最低LOD的几个level只保存了中心的信息,在算法里Map的中心当然是你的相机视点,也就是说离视点越远的地方场景的体素信息越粗糙。VXGI因为可以调整追踪的Cone的角度的大小,可以通过追踪非常细的Cone来近似Glossy的反射。
但是VXGI也存在一定的问题:每一帧都要做场景体素化(当然可以只体素化动态的部分)比较费时间,而且3d的文理会费比较多的显存,而且和LPV一样基于场景体素化的精度决定了光照的精度,所以也会有漏光等artifact存在。
上述简单的说了下VXGI的基本问题,下边我们就来逐步讲解实现流程及代码示例。
一、模型体素化
体素化整个GI算法的基础。因此我们做的第一件事就是创建场景几何体的体素表示。我们通过使用着色器来执行从由三角形网格组成的场景到我们的表示的转换。
不过体素化可以采用的方法也比较多,主要有以下几种:
- 直接将体素与场景进行碰撞检测。该方法比较原始,效率也较低,虽然也可以借助于GPU进行加速,但是在该特定场合中使用起来还是诸多不便;
- 基于CUDA进行体素操作,这也是使用GPU进行加速的一种方法。比如这里的一个使用CUDA进行光栅化方法,稍作修改就可以同样用来实现体素化;
- 基于GPU
Rasterization的方法,使用这种方法的好处就是不需要特殊的Pipeline,很容易将它往现有的游戏或渲染引擎中进行整合;
由于整个算法要搞定动态场景以及动态光源,因而这里的体素化操作也是需要实时更新的,因而就需要效率尽可能高的体素化操作算法;因此采用第三种方法基于实现体素化算法。
体素化的过程主要如下图所示:
其主要流程是:
- 使用与体素化细分分辨率相同的正交投影窗口来渲染三维网格中的每个三角形;
- 对于每个三角形计算出一个投影面积最大的投影矩阵,然在在此位置上做光栅化,这样使得光栅化效率最大化;光栅化出的每个像素对应一个该方向上的体素;
- 在光栅化出的每个像素中执行Shader,利用imageStore方法将像素对应的体素信息写入到3D Texture中;
- 对六个投影轴方向分别进行上述操作之后得到6张3D Texture;之后对其进行合并得到最终的3D
Texture,其中就包含了整个场景的完整体素化结果。
使用上述方法进行体素化之后的结果如下:
其中需要注意的是:使用正交投影矩阵,以及判断投影方向
因为体素本质上是一个个轴对齐的立方体,为了将模型的每个三角面用一堆体素表示出来,我们需要使用正交投影,如下图:
但我们都清楚的是,一个立方体有六个面,难道我们需要进行六次投影吗?答案是否定的,首先由于是正交投影,因此投影到上面和下面的结果是一样的,左右面、前后面同理,因此可以首先排除三个面。
整体生成过程可以参见如下流程:
其次需要注意的是:裂缝孔洞问题漏光问题。
这主要是因为上述体素化方法使用了保守的判断操作,因而可能会漏掉若干体素。对于这种情况,一个可行的解决方法是对三角形向周围进行扩展,如下:
在扩展后的三角形基础上再进行同样保守的判断操作就会得到原始被裁掉的那些体素,这样一来体素化的效果就会提高不少。
二、构建体素MipMap
接下来,我们对 3D 体素纹理进行 mipmap,以便我们稍后在渲染过程中对其进行光线行进时对其进行采样。我们需要使用一种数据结构对其进行管理,八叉树的主要思想是将一个3维空间(立方体)等分成八个次级立方体,并以此不停的迭代下去,直到每个立方体唯一代表一个物体(或其一部分)——叶子结点,所以其应该是一个从根节点进行扩展的树形结构,但是现在的情况是我们已经完成了体素化——即所有的叶子节点已经被计算出来,那么通过将相邻的八个体素合并成一个,并不断的迭代直到合并成一个和模型的AABB包围盒同样大小的体素来构建八叉树——是不是很像mipmap呢。
其具体表现如下:
直接/自发射光注入:
在此之前,我们还需要了解光照数据如何进行存储,如下图所示:
在其中我们烘焙与视图无关的直接光照数据,这些数据将用于后续间接光照的确定。其实本质上是通过将从光源出发的直接光照存储在与我们所讨论的像素相关的表面上来确定来自额外光反射的辐射——就像光对我们像素的贡献而不是直接来自光。 如果您理解这一点,那么您就会明白,我们还可以存储来自发光材料的自发光数据,这些数据稍后将转化为来自所述材料的直接照明,并且还可以烘焙其他有用的数据。
总的来说注入其实也就是类RSM的处理过程,如上图。
做过场景管理的你们应该会对八叉树很了解,这是一种很常用且简单的空间分割方法,可以用来进行碰撞检测等的加速操作,实用性很强。这里的Cone Tracing算法也是基于一个类似的八叉树结构上进行的,这里的主要问题是如何快速地创建出基于体素信息的空间八叉树结构。如下图所示即为不同MipMap的3D纹理的可视化展示。
因此,在第一步完成了体素操作之后,即可以得到存储于3D Texture中的场景离散信息,此时其相当于保存了八叉树的最底层信息,也即每个叶子上的信息。接下来需要通过这些叶子来得到整个树的结构,这里使用的方法即是自底向上的OCTree创建。通常,自顶向下的方法是对当前的每个结点进行八个子结点的分割;而自底向上则是对每八个子结点进行合并来得到它们对应的父结点,这样一直到最顶层的结点结束即可。这里利用了Mipmap的原理来对3D Texture进行不同级别的Mipamp的产生,也就相当于得到不同深度下的八叉树结构,如下:
当前Mipmap图层中每个Texel就代表了一个场景体素结点,对其进行合并后就得到了上一级(也即上一层OCTree)的更大的结点,如此进行直到顶层。这样一来,对于一个比如含有10级Mipmap的3D Texture中得到了对应深度为10的场景分割OCTree,这个作为下一步进行Cone Tracing的基本空间数据结构。在GI场合中使用时, 每个体素中需要存储的信息只需要RGBA就够了。
三、Cone Tracing
最后,我们在屏幕空间中执行锥形跟踪,以确定对我们像素的间接漫反射和间接镜面反射照明的贡献。在实现中,还可以选择从锥体追踪计算直接漫射照明。
如下图所述,可以采用不同数量及数量级的Cone来模拟物体表面的材质属性。
此处我们也可以来直观的看一下BRDF的可视化表示:
从以上两张图片,你一定可以发现其三维可视化表示极其类同,所以,我们可以发现所有的渲染本质都是类似的。
首先,在每个表面的每个点上,将传统做GI计算时的半球积分空间给分割成多个独立的Cones,用这些Cones组合得到的空间(中间会有重叠或裂隙)来近似原始的半球空间,并在其上做Irridiance的采积。
之后,对于每个独立的Cone,又使用下述方法再进行近似:
也即是在每个Cone的内部又将其用多个密布排列的Cube来进行近似,使用Cube的方法是其会使得OCTree的Tracing变得很方便。 每个Cube大小的计算就可以根据具体Cone的属性(比如夹角,最大长度等)来进行计算,一般来说从每个Cone内部分割出来的Cube个数不会太多.
对于每个Cube在OCTree中的Tracing,使用的方法也比较简单:直接计算出的Cube的Size,然后根据此Size找出与其最适配的那层Mipmap,这里的原则就是Cube的Size要尽可能地与Mipmap层中的结点Size接近。最后,直接使用此Cube的位置信息来采样Mipmap中的相应位置上的结点值,即可完成对此Cube的Tracing。
对Cone中的每个Cube完成Tracing之后,当前Cone方向上的Irridiance累积结果就可以认为是Cone中所有Cube采样结果的叠加。这个看起来虽然有些不太合理,但是视觉效果上的近似已经很不错了。将每个Cube认为是Transparent属性,然后Irridiance会在其中进行不断的传递。
具体的计算查找层级关系,可参照论文中的计算方法:
四、代码实现
首先我们来看一下渲染的整体架构
接下来shader的实现具体可参照上图右侧流程:
4.1 Voxelization(体素化)
- VS:
#version 450 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
out vec3 worldPositionGeom;
out vec3 normalGeom;
void main(){
worldPositionGeom = vec3(M * vec4(position, 1));
normalGeom = normalize(mat3(transpose(inverse(M))) * normal);
gl_Position = P * V * vec4(worldPositionGeom, 1);
}
- GS:
#version 450 core
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
in vec3 worldPositionGeom[];
in vec3 normalGeom[];
out vec3 worldPositionFrag;
out vec3 normalFrag;
void main(){
const vec3 p1 = worldPositionGeom[1] - worldPositionGeom[0];
const vec3 p2 = worldPositionGeom[2] - worldPositionGeom[0];
const vec3 p = abs(cross(p1, p2));
for(uint i = 0; i < 3; ++i){
worldPositionFrag = worldPositionGeom[i];
normalFrag = normalGeom[i];
if(p.z > p.x && p.z > p.y){
gl_Position = vec4(worldPositionFrag.x, worldPositionFrag.y, 0, 1);
} else if (p.x > p.y && p.x > p.z){
gl_Position = vec4(worldPositionFrag.y, worldPositionFrag.z, 0, 1);
} else {
gl_Position = vec4(worldPositionFrag.x, worldPositionFrag.z, 0, 1);
}
EmitVertex();
}
EndPrimitive();
}
- PS:
#version 450 core
// 灯光设置.
#define POINT_LIGHT_INTENSITY 1
#define MAX_LIGHTS 1
// 灯光设置衰减因子.
#define DIST_FACTOR 1.1f /* Distance is multiplied by this when calculating attenuation. */
#define CONSTANT 1
#define LINEAR 0
#define QUADRATIC 1
// 返回给定距离的衰减因子 .
float attenuate(float dist){ dist *= DIST_FACTOR; return 1.0f / (CONSTANT + LINEAR * dist + QUADRATIC * dist * dist); }
struct PointLight {
vec3 position;
vec3 color;
};
struct Material {
vec3 diffuseColor;
vec3 specularColor;
float diffuseReflectivity;
float specularReflectivity;
float emissivity;
float transparency;
};
uniform Material material;
uniform PointLight pointLights[MAX_LIGHTS];
uniform int numberOfLights;
uniform vec3 cameraPosition;
layout(RGBA8) uniform image3D texture3D;
in vec3 worldPositionFrag;
in vec3 normalFrag;
vec3 calculatePointLight(const PointLight light){
const vec3 direction = normalize(light.position - worldPositionFrag);
const float distanceToLight = distance(light.position, worldPositionFrag);
const float attenuation = attenuate(distanceToLight);
const float d = max(dot(normalize(normalFrag), direction), 0.0f);
return d * POINT_LIGHT_INTENSITY * attenuation * light.color;
};
vec3 scaleAndBias(vec3 p) { return 0.5f * p + vec3(0.5f); }
bool isInsideCube(const vec3 p, float e) { return abs(p.x) < 1 + e && abs(p.y) < 1 + e && abs(p.z) < 1 + e; }
void main(){
vec3 color = vec3(0.0f);
if(!isInsideCube(worldPositionFrag, 0)) return;
// 计算漫射光照的贡献 .
const uint maxLights = min(numberOfLights, MAX_LIGHTS);
for(uint i = 0; i < maxLights; ++i) color += calculatePointLight(pointLights[i]);
vec3 spec = material.specularReflectivity * material.specularColor;
vec3 diff = material.diffuseReflectivity * material.diffuseColor;
color = (diff + spec) * color + clamp(material.emissivity, 0, 1) * material.diffuseColor;
// 输出光照信息到3D纹理.
vec3 voxel = scaleAndBias(worldPositionFrag);
ivec3 dim = imageSize(texture3D);
float alpha = pow(1 - material.transparency, 4); // For soft shadows to work better with transparent materials.
vec4 res = alpha * vec4(vec3(color), 1);
imageStore(texture3D, ivec3(dim * voxel), res);
}
4.2 Voxel Cone Tracing(基于体素的锥形光线追踪)
- VS:
#version 450 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
out vec3 worldPositionFrag;
out vec3 normalFrag;
void main(){
worldPositionFrag = vec3(M * vec4(position, 1));
normalFrag = normalize(mat3(transpose(inverse(M))) * normal);
gl_Position = P * V * vec4(worldPositionFrag, 1);
}
- PS:
#version 450 core
#define TSQRT2 2.828427
#define SQRT2 1.414213
#define ISQRT2 0.707106
// --------------------------------------
// 光(体素)圆锥跟踪设置.
// --------------------------------------
#define MIPMAP_HARDCAP 5.4f /* mipmap层级,影响渲染效果. */
#define VOXEL_SIZE (1/64.0) /* 体素的大小. 128x128x128 => 1/128 = 0.0078125. */
#define SHADOWS 1 /* 阴影开关. */
#define DIFFUSE_INDIRECT_FACTOR 0.52f /* 漫射间接光照的强度. */
// --------------------------------------
// 其余光照设置.
// --------------------------------------
#define SPECULAR_MODE 1 /* 0 == Blinn-Phong , 1 == reflection model. */
#define SPECULAR_FACTOR 4.0f /* 镜面强度调整因子. */
#define SPECULAR_POWER 65.0f /* 镜面强度粗糙度. */
#define DIRECT_LIGHT_INTENSITY 0.96f /* (直接)点光强度因子. */
#define MAX_LIGHTS 1 /* 灯光数. */
// 光照衰减的因子。 可参见函数“attenuate()” .
#define DIST_FACTOR 1.1f /* 在计算衰减时,距离乘以这个系数. */
#define CONSTANT 1
#define LINEAR 0 /* . */
#define QUADRATIC 1
// 其他数据.
#define GAMMA_CORRECTION 1 /* 是否使用伽马校正. */
// 基础点光源.
struct PointLight {
vec3 position;
vec3 color;
};
// 基础材质.
struct Material {
vec3 diffuseColor;
float diffuseReflectivity;
vec3 specularColor;
float specularDiffusion; // “反射和折射”镜面扩散.
float specularReflectivity;
float emissivity; // 发射材料使用漫射色作为发射色 .
float refractiveIndex;
float transparency;
};
struct Settings {
bool indirectSpecularLight; // 是否渲染间接反射光.
bool indirectDiffuseLight; // 是否渲染间接漫射光.
bool directLight; // 是否要渲染直射光.
bool shadows; // 是否渲染阴影.
};
uniform Material material;
uniform Settings settings;
uniform PointLight pointLights[MAX_LIGHTS];
uniform int numberOfLights; // 灯光数.
uniform vec3 cameraPosition; // 世界空间下相机位置.
uniform int state; // .
uniform sampler3D texture3D; // 体素纹理.
in vec3 worldPositionFrag;
in vec3 normalFrag;
out vec4 color;
vec3 normal = normalize(normalFrag);
float MAX_DISTANCE = distance(vec3(abs(worldPositionFrag)), vec3(-1));
// 返回给定距离的衰减因子 .
float attenuate(float dist){ dist *= DIST_FACTOR; return 1.0f / (CONSTANT + LINEAR * dist + QUADRATIC * dist * dist); }
// 返回一个正交于u的向量.
vec3 orthogonal(vec3 u){
u = normalize(u);
vec3 v = vec3(0.99146, 0.11664, 0.05832); // Pick any normalized vector.
return abs(dot(u, v)) > 0.99999f ? cross(u, vec3(0, 1, 0)) : cross(u, v);
}
// (from [-1, 1] to [0, 1]).
vec3 scaleAndBias(const vec3 p) { return 0.5f * p + vec3(0.5f); }
// 如果点p在单位立方体内,返回true .
bool isInsideCube(const vec3 p, float e) { return abs(p.x) < 1 + e && abs(p.y) < 1 + e && abs(p.z) < 1 + e; }
//使用阴影圆锥跟踪返回一个柔和的阴影混合。
//每个步骤使用2个样本,性能开销较大。
float traceShadowCone(vec3 from, vec3 direction, float targetDistance){
from += normal * 0.05f;
float acc = 0;
float dist = 3 * VOXEL_SIZE;
// 影响范围大小.
const float STOP = targetDistance - 16 * VOXEL_SIZE;
while(dist < STOP && acc < 1){
vec3 c = from + dist * direction;
if(!isInsideCube(c, 0)) break;
c = scaleAndBias(c);
float l = pow(dist, 2); // 对阴影进行平方衰减.
float s1 = 0.062 * textureLod(texture3D, c, 1 + 0.75 * l).a;
float s2 = 0.135 * textureLod(texture3D, c, 4.5 * l).a;
float s = s1 + s2;
acc += (1 - acc) * s;
dist += 0.9 * VOXEL_SIZE * (1 + 0.05 * l);
}
return 1 - pow(smoothstep(0, 1, acc * 1.4), 1.0 / 1.4);
}
// 追踪漫反射体素锥.
vec3 traceDiffuseVoxelCone(const vec3 from, vec3 direction){
direction = normalize(direction);
const float CONE_SPREAD = 0.325;
vec4 acc = vec4(0.0f);
//控制闭合表面漏光情况。
//如果使用阴影圆锥跟踪,较低的值会导致效果很差。
float dist = 0.1953125;
// Trace.
while(dist < SQRT2 && acc.a < 1){
vec3 c = from + dist * direction;
c = scaleAndBias(from + dist * direction);
float l = (1 + CONE_SPREAD * dist / VOXEL_SIZE);
float level = log2(l);
float ll = (level + 1) * (level + 1);
vec4 voxel = textureLod(texture3D, c, min(MIPMAP_HARDCAP, level));
acc += 0.075 * ll * voxel * pow(1 - voxel.a, 2);
dist += ll * VOXEL_SIZE * 2;
}
return pow(acc.rgb * 2.0, vec3(1.5));
}
// 追踪高光体素锥.
vec3 traceSpecularVoxelCone(vec3 from, vec3 direction){
direction = normalize(direction);
const float OFFSET = 8 * VOXEL_SIZE;
const float STEP = VOXEL_SIZE;
from += OFFSET * normal;
vec4 acc = vec4(0.0f);
float dist = OFFSET;
// Trace.
while(dist < MAX_DISTANCE && acc.a < 1){
vec3 c = from + dist * direction;
if(!isInsideCube(c, 0)) break;
c = scaleAndBias(c);
float level = 0.1 * material.specularDiffusion * log2(1 + dist / VOXEL_SIZE);
vec4 voxel = textureLod(texture3D, c, min(level, MIPMAP_HARDCAP));
float f = 1 - acc.a;
acc.rgb += 0.25 * (1 + material.specularDiffusion) * voxel.rgb * voxel.a * f;
acc.a += 0.25 * voxel.a * f;
dist += STEP * (1.0f + 0.125f * level);
}
return 1.0 * pow(material.specularDiffusion + 1, 0.8) * acc.rgb;
}
//使用体素锥追踪计算间接漫射光。当前的实现与展示的一样使用9个圆锥。
vec3 indirectDiffuseLight(){
const float ANGLE_MIX = 0.5f; // 角度混合(1.0f =>正交方向,0.0f =>法线方向) .
const float w[3] = {1.0, 1.0, 1.0}; // 锥的权重.
// 求边锥的底,法向量为其底向量之一.
const vec3 ortho = normalize(orthogonal(normal));
const vec3 ortho2 = normalize(cross(ortho, normal));
// 求角锥的基向量.
const vec3 corner = 0.5f * (ortho + ortho2);
const vec3 corner2 = 0.5f * (ortho - ortho2);
//找到跟踪的起始位置(从偏移量开始) .
const vec3 N_OFFSET = normal * (1 + 4 * ISQRT2) * VOXEL_SIZE;
const vec3 C_ORIGIN = worldPositionFrag + N_OFFSET;
// 累积间接漫射光用.
vec3 acc = vec3(0);
//我们在法线方向上向前偏移,在圆锥方向上向后偏移。
//向后锥体方向可以改善GI,而向前方向可以移除artifacts。
const float CONE_OFFSET = -0.01;
// 追踪前锥
acc += w[0] * traceDiffuseVoxelCone(C_ORIGIN + CONE_OFFSET * normal, normal);
// 追踪4个边锥.
const vec3 s1 = mix(normal, ortho, ANGLE_MIX);
const vec3 s2 = mix(normal, -ortho, ANGLE_MIX);
const vec3 s3 = mix(normal, ortho2, ANGLE_MIX);
const vec3 s4 = mix(normal, -ortho2, ANGLE_MIX);
acc += w[1] * traceDiffuseVoxelCone(C_ORIGIN + CONE_OFFSET * ortho, s1);
acc += w[1] * traceDiffuseVoxelCone(C_ORIGIN - CONE_OFFSET * ortho, s2);
acc += w[1] * traceDiffuseVoxelCone(C_ORIGIN + CONE_OFFSET * ortho2, s3);
acc += w[1] * traceDiffuseVoxelCone(C_ORIGIN - CONE_OFFSET * ortho2, s4);
// 追踪4个中心锥.
const vec3 c1 = mix(normal, corner, ANGLE_MIX);
const vec3 c2 = mix(normal, -corner, ANGLE_MIX);
const vec3 c3 = mix(normal, corner2, ANGLE_MIX);
const vec3 c4 = mix(normal, -corner2, ANGLE_MIX);
acc += w[2] * traceDiffuseVoxelCone(C_ORIGIN + CONE_OFFSET * corner, c1);
acc += w[2] * traceDiffuseVoxelCone(C_ORIGIN - CONE_OFFSET * corner, c2);
acc += w[2] * traceDiffuseVoxelCone(C_ORIGIN + CONE_OFFSET * corner2, c3);
acc += w[2] * traceDiffuseVoxelCone(C_ORIGIN - CONE_OFFSET * corner2, c4);
// 返回结果
return DIFFUSE_INDIRECT_FACTOR * material.diffuseReflectivity * acc * (material.diffuseColor + vec3(0.001f));
}
// 使用体素圆锥追踪计算间接镜面光。
vec3 indirectSpecularLight(vec3 viewDirection){
const vec3 reflection = normalize(reflect(viewDirection, normal));
return material.specularReflectivity * material.specularColor * traceSpecularVoxelCone(worldPositionFrag, reflection);
}
// 使用体素锥追踪计算折射光 .
vec3 indirectRefractiveLight(vec3 viewDirection){
const vec3 refraction = refract(viewDirection, normal, 1.0 / material.refractiveIndex);
const vec3 cmix = mix(material.specularColor, 0.5 * (material.specularColor + vec3(1)), material.transparency);
return cmix * traceSpecularVoxelCone(worldPositionFrag, refraction);
}
//计算一个给定点光的漫反射和镜面直射光。
//使用阴影圆锥跟踪软阴影。
vec3 calculateDirectLight(const PointLight light, const vec3 viewDirection){
vec3 lightDirection = light.position - worldPositionFrag;
const float distanceToLight = length(lightDirection);
lightDirection = lightDirection / distanceToLight;
const float lightAngle = dot(normal, lightDirection);
// --------------------
// Diffuse lighting.
// --------------------
float diffuseAngle = max(lightAngle, 0.0f); // Lambertian.
// --------------------
// Specular lighting.
// --------------------
#if (SPECULAR_MODE == 0) /* Blinn-Phong. */
const vec3 halfwayVector = normalize(lightDirection + viewDirection);
float specularAngle = max(dot(normal, halfwayVector), 0.0f);
#endif
#if (SPECULAR_MODE == 1) /* Perfect reflection. */
const vec3 reflection = normalize(reflect(viewDirection, normal));
float specularAngle = max(0, dot(reflection, lightDirection));
#endif
float refractiveAngle = 0;
if(material.transparency > 0.01){
vec3 refraction = refract(viewDirection, normal, 1.0 / material.refractiveIndex);
refractiveAngle = max(0, material.transparency * dot(refraction, lightDirection));
}
// --------------------
// Shadows.
// --------------------
float shadowBlend = 1;
#if (SHADOWS == 1)
if(diffuseAngle * (1.0f - material.transparency) > 0 && settings.shadows)
shadowBlend = traceShadowCone(worldPositionFrag, lightDirection, distanceToLight);
#endif
// --------------------
// 光照叠加.
// --------------------
diffuseAngle = min(shadowBlend, diffuseAngle);
specularAngle = min(shadowBlend, max(specularAngle, refractiveAngle));
const float df = 1.0f / (1.0f + 0.25f * material.specularDiffusion); // Diffusion factor.
const float specular = SPECULAR_FACTOR * pow(specularAngle, df * SPECULAR_POWER);
const float diffuse = diffuseAngle * (1.0f - material.transparency);
const vec3 diff = material.diffuseReflectivity * material.diffuseColor * diffuse;
const vec3 spec = material.specularReflectivity * material.specularColor * specular;
const vec3 total = light.color * (diff + spec);
return attenuate(distanceToLight) * total;
};
// 累加所有来自点光源的直接光线(包括漫射和镜面) .
vec3 directLight(vec3 viewDirection){
vec3 direct = vec3(0.0f);
const uint maxLights = min(numberOfLights, MAX_LIGHTS);
for(uint i = 0; i < maxLights; ++i) direct += calculateDirectLight(pointLights[i], viewDirection);
direct *= DIRECT_LIGHT_INTENSITY;
return direct;
}
void main(){
color = vec4(0, 0, 0, 1);
const vec3 viewDirection = normalize(worldPositionFrag - cameraPosition);
// 间接的漫射光.
if(settings.indirectDiffuseLight && material.diffuseReflectivity * (1.0f - material.transparency) > 0.01f)
color.rgb += indirectDiffuseLight();
// 间接镜面光(镜面反射).
if(settings.indirectSpecularLight && material.specularReflectivity * (1.0f - material.transparency) > 0.01f)
color.rgb += indirectSpecularLight(viewDirection);
// 自发光.
color.rgb += material.emissivity * material.diffuseColor;
// 透明
if(material.transparency > 0.01f)
color.rgb = mix(color.rgb, indirectRefractiveLight(viewDirection), material.transparency);
// 直接光照.
if(settings.directLight)
color.rgb += directLight(viewDirection);
#if (GAMMA_CORRECTION == 1)
color.rgb = pow(color.rgb, vec3(1.0 / 2.2));
#endif
}
以上便是VXGI的所有Shader代码,运行结果可见:
直接光照如下:
间接光照_漫反射,如下:
间接光照_高光反射,如下:
结合软阴影,叠加在一起如下:
我们也可以将物体材质添加上自发光,则可以渲染如下:
4.3 voxel_visualization(体素可视化)
VS:
#version 450 core
layout(location = 0) in vec3 position;
uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
out vec3 worldPosition;
void main(){
worldPosition = vec3(M * vec4(position, 1));
gl_Position = P * V * vec4(worldPosition, 1);
}
PS:
#version 450 core
in vec3 worldPosition;
out vec4 color;
void main(){ color.rgb = worldPosition; }
VS:
#version 450 core
uniform mat4 V;
layout(location = 0) in vec3 position;
out vec2 textureCoordinateFrag;
// ( from [-1, 1] to [0, 1]).
vec2 scaleAndBias(vec2 p) { return 0.5f * p + vec2(0.5f); }
void main(){
textureCoordinateFrag = scaleAndBias(position.xy);
gl_Position = vec4(position, 1);
}
PS:
// 用于可视化3D纹理着色器:简单的路径跟踪片段着色器
#version 450 core
#define INV_STEP_LENGTH (1.0f/STEP_LENGTH)
#define STEP_LENGTH 0.005f
uniform sampler2D textureBack; // 单位立方体背面的FBO.
uniform sampler2D textureFront; // 单位立方体正面的FBO.
uniform sampler3D texture3D; // 存储体素化的纹理.
uniform vec3 cameraPosition; // 世界照相机的位置.
uniform int state = 0; // mipmap采样等级.
in vec2 textureCoordinateFrag;
out vec4 color;
// (from [-1, 1] to [0, 1]).
vec3 scaleAndBias(vec3 p) { return 0.5f * p + vec3(0.5f); }
// 如果p在以(0,0,0)为中心的统一立方体(+ e)内,则返回true。.
bool isInsideCube(vec3 p, float e) { return abs(p.x) < 1 + e && abs(p.y) < 1 + e && abs(p.z) < 1 + e; }
void main() {
const float mipmapLevel = state;
// 初始化光线.
const vec3 origin = isInsideCube(cameraPosition, 0.2f) ?
cameraPosition : texture(textureFront, textureCoordinateFrag).xyz;
vec3 direction = texture(textureBack, textureCoordinateFrag).xyz - origin;
const uint numberOfSteps = uint(INV_STEP_LENGTH * length(direction));
direction = normalize(direction);
// Trace.
color = vec4(0.0f);
for(uint step = 0; step < numberOfSteps && color.a < 0.99f; ++step) {
const vec3 currentPoint = origin + STEP_LENGTH * step * direction;
vec3 coordinate = scaleAndBias(currentPoint);
vec4 currentSample = textureLod(texture3D, scaleAndBias(currentPoint), mipmapLevel);
color += (1.0f - color.a) * currentSample;
}
color.rgb = pow(color.rgb, vec3(1.0 / 2.2));
}
执行此shader可见体素如下:
我们可以来看一下自放光的体素: