任务
为场景实现屏幕空间的全局光照效果
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 3
void 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的优化。但是看其他大佬的作业,大家需要改动的地方特别多,最近没什么时间就没去做了= =



















