任务
为场景实现屏幕空间的全局光照效果
1.直接光照: 实现ssrFragmentShader中的EvalDiffuse(wi, wo, uv) 和EvalDirectionalLight(uv) 函数,并在 main 函数中实现直接光照的效果。
2.屏幕空间光线求交:实现RayMarch(ori, dir, out hitPos) 函数。RayMarch 函数的返回值为是否相交,当相交的时候需要将参数 hitPos设置为交点。参数 ori 和 dir 为世界坐标系中的值,分别代表光线的起点和方向,其中方向向量为单位向量。
3.间接光照:在 main 函数中实现间接光照,使用蒙特卡洛方法求解渲染方程。
实现
EvalDiffuse
该函数是计算diffuse材质的BSDF的,功能简单,其实就是之前101的内容。这里将EvalDiffuse看作渲染方程的f,但是由于计算间接光照的伪代码没有给出cos项,因此这里多乘以一个cos项。另外提一下,这里的INV_PI是PI的倒数,因为在计算机里乘法计算比除法计算更快,因此预定义一个INV_PI能加速渲染。
vec3 EvalDiffuse(vec3 wi, vec3 wo, vec2 uv) {vec3 diffuse = GetGBufferDiffuse(uv);vec3 normal = GetGBufferNormalWorld(uv);float cos = max(0., dot(normal, wi));return diffuse * cos * INV_PI;
}
EvalDirectionLight
直接使用题目提供的API,简短两行
vec3 EvalDirectionalLight(vec2 uv) {float visibility = GetGBufferuShadow(uv);return visibility * uLightRadiance;
}
RayMarch
按照202课上的原理,在着色点的位置开始,以某个方向射出光线,一步步向前试探找到交点。有交点的条件是:试探的点的深度比屏幕空间的该位置的深度更深(也就是在屏幕空间一直走直到被遮挡住,这时候说明有交点)。
bool RayMarch(vec3 ori, vec3 dir, out vec3 hitPos) {//定义一个最大步数防止没有找到交点而无限前进const int maxStep = 100;float stepSize = 0.05;vec3 stepDir = normalize(dir) * stepSize;for(int step=0; step< maxStep ; step++){float depth = GetDepth(ori);vec2 screenUV = GetScreenCoordinate(ori);float screenDepth = GetGBufferDepth(screenUV);if(depth > screenDepth + 0.0001){hitPos = ori;return true;}ori += dir;}return false;
}
main里用蒙特卡洛方法解渲染方程
这里渲染方程没有cos项,cos项在上面的EvalDiffuse已经实现。
将其转化为代码,这里有需要注意的地方就是direction是局部坐标系的。在101中的作业3里做bumper时,我们使用TBN矩阵来将切线坐标系转化为世界坐标系。将切线坐标系转化为世界坐标系其实还需要uv坐标来确定,因为需要知道某个三角形在纹理上的位置。而这里只涉及方向的转化不涉及位置,所以直接使用TBN矩阵就能获得direction转化到世界坐标的朝向。作业框架里给我们提供了LocalBasis函数来获取两个切线。因此获取坐标系的三个方向后,使用它们创建一个TBN矩阵。
vec3 inDirectLight = vec3(0.0);for(int i =0;i<SAMPLE_NUM;i++){float pdf;vec3 direction = SampleHemisphereUniform(s,pdf);vec3 b1,b2;LocalBasis(normal,b1,b2);mat3 rotateMatrix = mat3(normal,b1,b2);direction = normalize(rotateMatrix * direction);vec3 hitPos;if(RayMarch(pos,direction,hitPos)){vec2 hitUV = GetScreenCoordinate(hitPos);inDirectLight += EvalDiffuse(direction,wo,uv)/pdf * EvalDiffuse(wi,direction,hitUV) * EvalDirectionalLight(hitUV);}}inDirectLight /= float(SAMPLE_NUM);L += inDirectLight;
整个的main代码
vec3 EvalReflect(vec3 wi,vec3 wo,vec2 uv){vec3 pos = GetGBufferPosWorld(uv);vec3 normal = GetGBufferNormalWorld(uv);vec3 dir = normalize(reflect(-wo,normal) );vec3 hitPos;if(RayMarch(pos,dir,hitPos)){vec2 hitUV = GetScreenCoordinate(hitPos);return GetGBufferDiffuse(hitUV);}return vec3(0.0,0.0,0.0);
}#define SAMPLE_NUM 3void main() {float s = InitRand(gl_FragCoord.xy);vec3 pos = vPosWorld.xyz;vec2 uv = GetScreenCoordinate(pos);vec3 wi = normalize(uLightDir);vec3 wo = normalize(uCameraPos - pos );vec3 normal = GetGBufferNormalWorld(uv);//vec3 L = vec3(0.0);//L = GetGBufferDiffuse(GetScreenCoordinate(vPosWorld.xyz)); //初始的无阴影着色vec3 L = EvalDiffuse(wi,wo,uv) * EvalDirectionalLight(uv); //用于调试带阴影的着色//L = ( L + EvalReflect(wi,wo,uv) ) / 2.0; //用于调试反射vec3 inDirectLight = vec3(0.0);for(int i =0;i<SAMPLE_NUM;i++){float pdf;vec3 direction = SampleHemisphereUniform(s,pdf);vec3 b1,b2;LocalBasis(normal,b1,b2);mat3 rotateMatrix = mat3(normal,b1,b2);direction = normalize(rotateMatrix * direction);vec3 hitPos;if(RayMarch(pos,direction,hitPos)){vec2 hitUV = GetScreenCoordinate(hitPos);inDirectLight += EvalDiffuse(direction,wo,uv)/pdf * EvalDiffuse(wi,direction,hitUV) * EvalDirectionalLight(hitUV);}}inDirectLight /= float(SAMPLE_NUM);L += inDirectLight; 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里将cube1换成了cube2。调试cave时,cave是有自己的摄像机属性和光源属性的,需要额外调一下。
原来硬阴影的cube2
间接光照的cube2
硬阴影的cave
实现了间接光照的cave
bonus部分没做了,原理不会很难,就是对RayMarch的优化。但是看其他大佬的作业,大家需要改动的地方特别多,最近没什么时间就没去做了= =