自学内容网 自学内容网

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介

本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念,实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 (Deferred shading)技术
按照本文代码实现后,可以实现以下效果:

渲染结果

二、SSAO介绍以及实现流程

1. SSAO 介绍

(1). 什么是 Ambient Occlusion, AO

简单来说 Ambient Occlusion(AO) 是一种全局照明(Global Illumination,GI)中的根据环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的光照强度系数的算法。
AO 描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比, 因此使得渲染的结果更加富有层次感,对比度更高。
例如:对于下图中的车辆缝隙处(红色),会受到周边模型面片的遮挡,导致接收到的环境光 (Ambient light)较少,因此这些地方会更暗。而在车辆的边缘处(绿色),几乎不会受到周围模型面片的遮挡,接收到的环境光较多,因此这些地方会更亮。
AO示意图

(2). 什么是 Screen-Space Ambient Occlusion, SSAO

为了计算准确的 AO,可以使用光线跟踪算法。但是光线跟踪消耗计算资源太大,而 屏幕空间环境光遮蔽 (Screen-Space Ambient Occlusion, SSAO) 是一种仅仅基于屏幕信息(例如,屏幕上各像素对应 片元 的空间位置信息)快速估计 AO 的算法。

SSAO 算法的基本思想为:
对于目标着色点,在其周围的一个球(或者面向相机方向的半球)内采样多个采样点,如果采样点大多被模型的其他面片遮挡,那么说明该目标着色点的 Ambien Occlusion 比较大,因此该着色点理应较暗些。而反之,目标着色点周围得到的采样点大部分并不会被模型的其他面片遮挡,那么说明该着色点的 Ambient Occlusion 更小,因此会更亮。

以下图为例:
在图中的 红色着色点 附近的一个球内采样,得到 8 个采样点,其中只有两个采样点(白色采样点)相比模型中的其他面片更靠近相机,不会被模型面片遮挡。而其他 6 个采样点(灰色采样点)都会被模型中的其他面片遮挡,则红色目标着色点的 Ambient Occlusion 更大,渲染结果中此着色点会更暗。
而图中的 绿色着色点 附近的大多数采样点(白色采样点)都不会被模型面片遮挡,只有两个采样点(灰色采样点)会被遮挡,则绿色目标着色点的 Ambient Occlusion 更小,渲染结果中此着色点会更亮。

SSAO 示意图
在实现时也可以在朝向相机的半球内采样,理论上这样的结果会更加准确,而不是上图中所示的整个球内采样。在计算得到 各点的 AO 值后,也可以使用 滤波 操作对屏幕上各点的 AO 值进行滤波操作,平滑遮蔽效果,消除噪点。

2. SSAO 实现流程

实现 SSAO 主要分为 4 趟 pass。

(1). Geometry Pass

该 pass 对输入场景模型进行处理,将屏幕各像素对应片元的 texture_color, positon (in world space), normal 和 position (in view space) 输出到 GBuffer 中;

(2). Cal SSAO Pass

该 pass 根据 屏幕各像素对应片元(目标着色点)的 position (in view space) 信息,在各 片元 周围采样,得到采样点,根据采样点 是否会被模型遮挡计算目标着色点的 AO 值;

(3.) Blur SSAO Pass

该 pass 对 上趟流程中计算得到的 AO 进行滤波操作,的到滤波后的 blurredAO;

(4). Lighting (Shading) Pass

该 pass 根据 pass (1) 中得到的 texture_color, positon (in world space), normal 和 pass (3) 中得到的 blurredAO 计算各着色点的颜色值。使用 Phong 着色模型,公式如下:
I = I a ∗ b l u r r e d A O + I d + I s I=Ia * blurredAO + Id + Is I=IablurredAO+Id+Is

3. 主要代码讲解

(1). Geometry Pass Shader

geometryPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 vertexPos;
out vec3 vertexNor;
out vec2 textureCoord;
out vec4 vertexViewPos;

void main() {
  textureCoord = aTexCoord;
  // 裁剪空间坐标系 (clip space) 中 点的位置
  gl_Position = projection * view * model * vec4(aPos, 1.0f);
  // 世界坐标系 (world space) 中 点的位置
  vertexPos = (model * vec4(aPos, 1.0f)).xyz;
  // 世界坐标系 (world space) 中 点的法向
  vertexNor = mat3(transpose(inverse(model))) * aNor;
  // 视图坐标系 (view space) 中 点的位置
  vertexViewPos = view * model * vec4(aPos, 1.0f);
}

geometryPassShader.frag:

#version 330 core
layout(location = 0) out vec4 FragColor;   // diffuse color
layout(location = 1) out vec3 FragPos;     // position in world space
layout(location = 2) out vec3 FragNor;     // normal in world space
layout(location = 3) out vec4 FragViewPos; // position in view space

in vec3 vertexPos;
in vec3 vertexNor;
in vec2 textureCoord;
in vec4 vertexViewPos;

uniform sampler2D texture0;

void main() {
  FragPos = vertexPos;
  FragNor = vertexNor;
  FragColor = texture(texture0, textureCoord);
  FragViewPos = vertexViewPos;
}

(2). Cal SSAO Pass Shader

calSSAOPassShader.vert

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {
  gl_Position = vec4(aPos, 1.0f);
  textureCoord = aTexCoord;
}

calSSAOPassShader.frag:

#version 330 core
out float AO;
in vec2 textureCoord;

uniform mat4 projection;

uniform sampler2D textureViewPos; // position (in view space)

uniform vec3 gKernel[64]; // random position offset

// 计算目标片段的 Ambient Occlusion (AO) 值
// AO in [0,1]
// 为了便于后续计算 代码中的 AO 规定为:
// AO 越接近 0,说明该片段被遮挡的越多(越暗)
// AO 越接近 1,说明该片段被遮挡的越多(越亮)
void main() {

  vec3 shadeViewPos = texture(textureViewPos, textureCoord)
                          .xyz; // 目标片段在 view space 中的坐标

  AO = 0.0;
  float gSampleRad = 1.5f;
  for (int i = 0; i < 64; i++) {
    vec3 sampleViewPos = shadeViewPos + gKernel[i]; // 在目标片段周围随机采样
    vec4 sampleProPos =
        vec4(sampleViewPos, 1.0); // 采样点 在 view space 中的坐标

    sampleProPos = projection * sampleProPos;
    sampleProPos.xy /= sampleProPos.w;
    // 采样点 投影到屏幕,再归一化到[0,1]的 xy 坐标 (即采样点对应的 uv 坐标)
    sampleProPos.xy = sampleProPos.xy * 0.5 + vec2(0.5, 0.5);

    // 相机-采样点 射线与场景相交点(场景表面点)对应的 z 值(在 view space 中)
    float surfaceDepth = texture(textureViewPos, sampleProPos.xy).z;

    if (abs(shadeViewPos.z - surfaceDepth) < gSampleRad) {
      // step(a,b) = if (a<b) return 1.0 else return 0.0;
      // 在 view sapce 中, camera position 为 (0,0,0)
      // 假如 abs(surfaceDepth) < abs(sampleViewPos.z) 说明 场景表面点 比
      // 采样点距离相机更近,那么 AO += 1
      // 假如 abs(surfaceDepth) >= abs(sampleViewPos.z) 说明 采样点 比
      // 场景表面点 距离相机更近,那么 AO += 0
      AO += step(abs(surfaceDepth), abs(sampleViewPos.z));
    }
  }
  // 前面 AO 记录的是 '采样点 被 场景表面 遮挡的次数'
  // 因此需要 令 AO = 1.0 - AO / (采样次数)
  // 最后得到的 AO 才是目标片段的 AO 值
  AO = 1.0 - AO / 64;
}

(3). Blur SSAO Pass Shader

blurSSAOPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {
  gl_Position = vec4(aPos, 1.0f);
  textureCoord = aTexCoord;
}

blurSSAOPassShader.frag:

#version 330 core
// out vec4 FragColor;
out float blurredAO;

in vec2 textureCoord;

uniform sampler2D textureAO;

void main() {
  blurredAO = 0.0;
  float Offsets[4] = float[](-1.5, -0.5, 0.5, 1.5);
  float originAO = texture(textureAO, textureCoord).x;

  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      float AO = texture(textureAO, textureCoord).r;
      vec2 tc = textureCoord;
      tc.x = textureCoord.x + Offsets[j] / textureSize(textureAO, 0).x;
      tc.y = textureCoord.y + Offsets[i] / textureSize(textureAO, 0).y;
      blurredAO += texture(textureAO, tc).x;
    }
  }

  blurredAO /= 16.0;
}

(4). Lighting (Shading) Pass Shader

lightingSSAOPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {
  gl_Position = vec4(aPos, 1.0f);
  textureCoord = aTexCoord;
}

lightingSSAOPassShader.frag:

#version 330 core
out vec4 FragColor;

in vec2 textureCoord;

uniform int state;

uniform vec3 lightPos;

uniform vec3 cameraPos;
uniform vec3 k;

uniform sampler2D textureColor;     // color
uniform sampler2D texturePos;       // position (in world space)
uniform sampler2D textureNor;       // normal (in world space)
uniform sampler2D textureBlurredAO; // blurredAO

void main() {
  vec3 vertexPos = texture(texturePos, textureCoord).xyz;
  vec3 vertexNor = texture(textureNor, textureCoord).xyz;
  vec3 lightColor = vec3(1.0f, 1.0f, 1.0f);

  // Ambient
  // Ia = ka * La
  float ambientStrenth = k[0];
  vec3 ambient = ambientStrenth * lightColor;
  float blurredAO = texture(textureBlurredAO, textureCoord).x;

  if (state == 0) {
    // Rendering scene with SSAO on.
    ambient = ambient * vec3(blurredAO);
  } else if (state == 1) {
    // Rendering scene with SSAO off.
  } else {
    // Rendering AO.
    FragColor = vec4(blurredAO);
    return;
  }
  vec3 diffuse = vec3(0, 0, 0);
  vec3 specular = vec3(0, 0, 0);
  // Diffuse
  // Id = kd * max(0, normal dot light) * Ld
  float diffuseStrenth = k[1];
  vec3 normalDir = normalize(vertexNor);

  vec3 lightDir = normalize(lightPos - vertexPos);
  diffuse = diffuseStrenth * max(dot(normalDir, lightDir), 0.0) * lightColor;

  // Specular (Phong)
  // Is = ks * (view dot reflect)^s * Ls

  float specularStrenth = k[2];
  vec3 viewDir = normalize(cameraPos - vertexPos);
  vec3 reflectDir = reflect(-lightDir, normalDir);
  specular = specularStrenth * pow(max(dot(viewDir, reflectDir), 0.0f), 2) *
             lightColor;

  // Specular (Blinn-Phong)
  // Is = ks * (normal dot halfway)^s Ls
  // float specularStrenth = k[2];
  // vec3 viewDir = normalize(cameraPos - vertexPos);
  // vec3 halfwayDir = normalize(lightDir + viewDir);
  // vec3 temp_specular = specularStrenth *
  //                      pow(max(dot(normalDir, halfwayDir), 0.0f), 2) *
  //                      lightColor;

  diffuse = clamp(diffuse, 0.0, 1.0);
  specular = clamp(specular, 0.0, 1.0);

  // Obejct color
  vec3 objectColor = texture(textureColor, textureCoord).xyz;

  // Color = Ambient + Diffuse + Specular
  // I = Ia + Id + Is
  FragColor = vec4((ambient + diffuse + specular) * objectColor, 1.0f);
}

4. 全部代码及模型文件

使用OpenGL实现 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的全部代码以及模型文件可以在 OpenGL实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 中下载。程序运行后按下 空格 键在使用SSAO渲染场景直接渲染场景渲染AO值三种模式中切换。
渲染结果如下:
渲染结果

三、参考

[1].ogl-tutorial45 Screen Space Ambient Occlusion
[2].游戏后期特效第四发 – 屏幕空间环境光遮蔽(SSAO)


原文地址:https://blog.csdn.net/Strengthennn/article/details/144849932

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!