EvalDiffuse
- 对于一个diffuse的着色点,它的BRDF为:
/*
* Evaluate diffuse bsdf value.
*
* wi, wo are all in world space.
* uv is in screen space, [0, 1] x [0, 1].
*
*/
vec3 EvalDiffuse(vec3 wi, vec3 wo, vec2 uv) {
vec3 albedo = GetGBufferDiffuse(uv);
vec3 normal = GetGBufferNormalWorld(uv);
float cosine = max(0.0, dot(wi, normal));
return albedo * cosine * INV_PI;
}
EvalDirectionalLight
- 就是针对每个顶点考虑可见度的光照计算
vec3 EvalDirectionalLight(vec2 uv) {
float visibility = GetGBufferuShadow(uv);
vec3 Le = uLightRadiance * visibility;
return Le;
}
直接光照的检验
可以检验直接光照是否正确
void main() {
float s = InitRand(gl_FragCoord.xy);
vec3 w0 = normalize(uCameraPos - vPosWorld.xyz);
vec3 wi = normalize(uLightDir);
vec2 screenUV = GetScreenCoordinate(vPosWorld.xyz);
//vec3 L = vec3(0.0);
vec3 L = EvalDiffuse(wi, w0, screenUV) * EvalDirectionalLight(screenUV);
//L = GetGBufferDiffuse(GetScreenCoordinate(vPosWorld.xyz));
vec3 color = pow(clamp(L, vec3(0.0), vec3(1.0)), vec3(1.0 / 2.2));
gl_FragColor = vec4(vec3(color.rgb), 1.0);
}
Screen Space Ray Tracing
实现SSR的找交点的算法,通过倍增的方式快速找到交点,但是这里我感觉如果和课上的内容一样,应该还有一步存储一个step中最浅高度的做法,这里我没写这一步,偷懒了。。。
bool RayMarch(vec3 ori, vec3 dir, out vec3 hitPos) {
float step = 0.08;
float mulStep = 1.0;
const int num_step = 50; //max steps
bool intersect = false;
vec3 curPos = ori;
for(int i = 0; i < num_step; i++){
vec3 wpos = curPos + step * mulStep * dir;
intersect = false;
if(outScreen(wpos)){
return false;
}
if(Front(wpos)){
curPos = wpos;
mulStep *= 2.0;
}
else{
intersect = true;
if(mulStep <= 1.0){//close
float d1 = GetGBufferDepth(GetScreenCoordinate(curPos)) - GetDepth(curPos);
float d2 = GetDepth(wpos) - GetGBufferDepth(GetScreenCoordinate(wpos));
if(d1 > 0.0 && d2 > 0.0){
curPos = wpos;
hitPos = curPos;
return true;
}
}
}
if(intersect){
mulStep /= 2.0;
}
}
return false;
}
写一个验证的逻辑
vec3 EvalReflect(vec3 wo, vec3 Pos, vec2 uv){
vec3 normal = normalize(GetGBufferNormalWorld(uv));
vec3 hitPos;
if(RayMarch(Pos, reflect(-wo, normal), hitPos)){
return GetGBufferDiffuse(GetScreenCoordinate(hitPos));
}
else{
return vec3(0.);
}
}
间接光照
- 首先前面两个就是采样和判断射线是否和其它物体有交点,这就是之前的光线追踪的做法
- 如果没有交点,计算直接光照
- 如果有交点以后,因为我们认为间接光照源都是漫反射材质,所以只需要使用第一大步计算的两个函数来计算交点间接光照物体对着色点的光照贡献。
- 根据蒙特卡洛方法
- 对于间接光照而言在计算hitpoint的时候就考虑了visibility。所以乘一个着色点的bsdf,除以采样概率
- 最后除以采样的次数。
我们需要使用作业框架提供的函数LocalBasis(vec3 n, out vec3 b1, out vec3 b2)
,取得两个切线向量,然后组成TBN矩阵把这个局部向量转换成世界坐标下的方向向量,也就是我们世界空间下的步进方向。
void main() {
float s = InitRand(gl_FragCoord.xy);
vec3 w0 = normalize(uCameraPos - vPosWorld.xyz);
vec3 wi = normalize(uLightDir);
vec2 screenUV = GetScreenCoordinate(vPosWorld.xyz);
//vec3 L = vec3(0.0);
//vec3 L = EvalDiffuse(wi, w0, screenUV) * EvalDirectionalLight(screenUV);
//vec3 L = EvalReflect(w0, vPosWorld.xyz, screenUV);
//L = GetGBufferDiffuse(GetScreenCoordinate(vPosWorld.xyz));
vec3 normal = normalize(GetGBufferNormalWorld(screenUV));
vec3 L = EvalDiffuse(wi, w0, screenUV) * EvalDirectionalLight(screenUV);
vec3 inL = vec3(0.);
for(int i = 0; i < SAMPLE_NUM ; i++){
float pdf;
vec3 localPos = SampleHemisphereCos(s, pdf);
vec3 b1;
vec3 b2;
LocalBasis(normal, b1, b2);
vec3 dirSam = normalize(mat3(normal, b1, b2) * localPos);
vec3 hitPos;
if(RayMarch(vPosWorld.xyz, dirSam, hitPos)){
vec2 hitScreenUV = GetScreenCoordinate(hitPos);
inL += ((EvalDiffuse(dirSam, w0, screenUV) * EvalDiffuse(wi, -dirSam, hitScreenUV) * EvalDirectionalLight(hitScreenUV)) /pdf);
}
}
L += inL;
L /= float(SAMPLE_NUM);
vec3 color = pow(clamp(L, vec3(0.0), vec3(1.0)), vec3(1.0 / 2.2));
gl_FragColor = vec4(vec3(color.rgb), 1.0);
}
//engine.js
// Add shapes
//loadGLTF(renderer, 'assets/cube/', 'cube1', 'SSRMaterial');
loadGLTF(renderer, 'assets/cube/', 'cube2', 'SSRMaterial');
// loadGLTF(renderer, 'assets/cave/', 'cave', 'SSRMaterial');
采样20次后,可以看到一个不错的color bleeding现象
//engine.js
// Add shapes
//loadGLTF(renderer, 'assets/cube/', 'cube1', 'SSRMaterial');
//loadGLTF(renderer, 'assets/cube/', 'cube2', 'SSRMaterial');
loadGLTF(renderer, 'assets/cave/', 'cave', 'SSRMaterial');
提高
参考
Bonus 1:实现Mipmap优化的 Screen Space Ray Tracing(Hiz优化)
- 还记得上面我们说的就是理论上应该是和一个步长的最大的最浅的深度进行比较
- 一个简单的优化思路是,我们使用深度图Mipmap,与常见Mipmap不同,这里使用的Mipmap不是记录更大一层的Mipmap对应的四个像素的平均值,而是记录四个像素的最小值。
- 有了最小深度的Mipmap后,我们相当于有了一个场景深度的加速结构,处于上层的Mipmap中的一个像素对应的深度反映了下层的Mipmap的一片区域的最小深度,如果当前光线与较上层的Mipmap无相交,则与下层的Mipmap也无相交。
- 有了这个结构后,我们就可以动态调整步进距离了,我们可以尝试先步进一个小的距离,若与场景物体无相交,则可以逐步提高当前采样的Mipmap等级,因为高层Mipmap的一个像素对应了低层Mipmap的一个区域,提高了Mipmap等级也意味着步进距离也可以跟着增大了,若在高层Mipmap判断与场景物体有相交,意味着光线在这片区域内存在与场景物体的交点,则需要降低Mipmap等级直到找到具体的相交位置点。
占个坑以后看参考