笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

接着上文 OpenGL核心技术之SSAO技术讲解(二)继续分析SSAO技术,在SSAO阶段和光照阶段之间,我们想要进行模糊SSAO纹理的处理,所以我们又创建了一个帧缓冲对象来储存模糊结果。

GLuint ssaoBlurFBO, ssaoColorBufferBlur;
glGenFramebuffers(1, &ssaoBlurFBO);
glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO);
glGenTextures(1, &ssaoColorBufferBlur);
glBindTexture(GL_TEXTURE_2D, ssaoColorBufferBlur);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBufferBlur, 0);

由于平铺的随机向量纹理保持了一致的随机性,我们可以使用这一性质来创建一个简单的模糊着色器:

#version 330 core
in vec2 TexCoords;
out float fragColor;uniform sampler2D ssaoInput;void main() {vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0));float result = 0.0;for (int x = -2; x < 2; ++x) {for (int y = -2; y < 2; ++y) {vec2 offset = vec2(float(x), float(y)) * texelSize;result += texture(ssaoInput, TexCoords + offset).r;}}fragColor = result / (4.0 * 4.0);
}

这里我们遍历了周围在-2.0和2.0之间的SSAO纹理单元(Texel),采样与噪声纹理维度相同数量的SSAO纹理。我们通过使用返回 vec2 纹理维度的 textureSize ,根据纹理单元的真实大小偏移了每一个纹理坐标。我们平均所得的结果,获得一个简单但是有效的模糊效果:

这就完成了,一个包含逐片段环境遮蔽数据的纹理;在光照处理阶段中可以直接使用。

应用遮蔽因子到光照方程中极其简单:我们要做的只是将逐片段环境遮蔽因子乘到光照环境分量上。如果我们使用上个教程中的Blinn-Phong延迟光照着色器并做出一点修改,我们将会得到下面这个片段着色器:

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D gAlbedo;
uniform sampler2D ssao;struct Light {vec3 Position;vec3 Color;float Linear;float Quadratic;float Radius;
};
uniform Light light;void main()
{             // 从G缓冲中提取数据vec3 FragPos = texture(gPositionDepth, TexCoords).rgb;vec3 Normal = texture(gNormal, TexCoords).rgb;vec3 Diffuse = texture(gAlbedo, TexCoords).rgb;float AmbientOcclusion = texture(ssao, TexCoords).r;// Blinn-Phong (观察空间中)vec3 ambient = vec3(0.3 * AmbientOcclusion); // 这里我们加上遮蔽因子vec3 lighting  = ambient; vec3 viewDir  = normalize(-FragPos); // Viewpos 为 (0.0.0),在观察空间中// 漫反射vec3 lightDir = normalize(light.Position - FragPos);vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color;// 镜面vec3 halfwayDir = normalize(lightDir + viewDir);  float spec = pow(max(dot(Normal, halfwayDir), 0.0), 8.0);vec3 specular = light.Color * spec;// 衰减float dist = length(light.Position - FragPos);float attenuation = 1.0 / (1.0 + light.Linear * dist + light.Quadratic * dist * dist);diffuse  *= attenuation;specular *= attenuation;lighting += diffuse + specular;FragColor = vec4(lighting, 1.0);
}

(除了将其改到观察空间)对比于之前的光照实现,唯一的真正改动就是场景环境分量与 AmbientOcclusion 值的乘法。通过在场景中加入一个淡蓝色的点光源,我们将会得到下面这个结果:

下面把SSAO使用的所有着色器代码给读者展示如下,先展示的是几何着色器的顶点着色器代码如下所示:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{vec4 viewPos = view * model * vec4(position, 1.0f);FragPos = viewPos.xyz; gl_Position = projection * viewPos;TexCoords = texCoords;mat3 normalMatrix = transpose(inverse(mat3(view * model)));Normal = normalMatrix * normal;
}

几何着色器的片段着色器代码如下所示:

#version 330 core
layout (location = 0) out vec4 gPositionDepth;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;const float NEAR = 0.1; // Projection matrix's near plane distance
const float FAR = 50.0f; // Projection matrix's far plane distance
float LinearizeDepth(float depth)
{float z = depth * 2.0 - 1.0; // Back to NDC return (2.0 * NEAR * FAR) / (FAR + NEAR - z * (FAR - NEAR));
}void main()
{    // Store the fragment position vector in the first gbuffer texturegPositionDepth.xyz = FragPos;// And store linear depth into gPositionDepth's alpha componentgPositionDepth.a = LinearizeDepth(gl_FragCoord.z); // Divide by FAR if you need to store depth in range 0.0 - 1.0 (if not using floating point colorbuffer)// Also store the per-fragment normals into the gbuffergNormal = normalize(Normal);// And the diffuse per-fragment colorgAlbedoSpec.rgb = vec3(0.95); // Currently all objects have constant albedo color
}

SSAO技术的顶点着色器代码如下所示:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;out vec2 TexCoords;void main()
{gl_Position = vec4(position, 1.0f);TexCoords = texCoords;
}

SSAO技术的片段着色器代码如下所示:

#version 330 core
out float FragColor;
in vec2 TexCoords;uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D texNoise;uniform vec3 samples[64];// parameters (you'd probably want to use them as uniforms to more easily tweak the effect)
int kernelSize = 64;
float radius = 1.0;// tile noise texture over screen based on screen dimensions divided by noise size
const vec2 noiseScale = vec2(800.0f/4.0f, 600.0f/4.0f); uniform mat4 projection;void main()
{// Get input for SSAO algorithmvec3 fragPos = texture(gPositionDepth, TexCoords).xyz;vec3 normal = texture(gNormal, TexCoords).rgb;vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz;// Create TBN change-of-basis matrix: from tangent-space to view-spacevec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));vec3 bitangent = cross(normal, tangent);mat3 TBN = mat3(tangent, bitangent, normal);// Iterate over the sample kernel and calculate occlusion factorfloat occlusion = 0.0;for(int i = 0; i < kernelSize; ++i){// get sample positionvec3 sample = TBN * samples[i]; // From tangent to view-spacesample = fragPos + sample * radius; // project sample position (to sample texture) (to get position on screen/texture)vec4 offset = vec4(sample, 1.0);offset = projection * offset; // from view to clip-spaceoffset.xyz /= offset.w; // perspective divideoffset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0// get sample depthfloat sampleDepth = -texture(gPositionDepth, offset.xy).w; // Get depth value of kernel sample// range check & accumulatefloat rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth ));occlusion += (sampleDepth >= sample.z ? 1.0 : 0.0) * rangeCheck;           }occlusion = 1.0 - (occlusion / kernelSize);FragColor = occlusion;
}

模糊处理的顶点着色器代码如下所示:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;out vec2 TexCoords;void main()
{gl_Position = vec4(position, 1.0f);TexCoords = texCoords;
}

模糊处理的片段着色器代码如下所示:

#version 330 core
in vec2 TexCoords;out float fragColor;uniform sampler2D ssaoInput;
const int blurSize = 4; // use size of noise texture (4x4)void main()
{vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0));float result = 0.0;for (int x = 0; x < blurSize; ++x) {for (int y = 0; y < blurSize; ++y) {vec2 offset = (vec2(-2.0) + vec2(float(x), float(y))) * texelSize;result += texture(ssaoInput, TexCoords + offset).r;}}fragColor = result / float(blurSize * blurSize);
}

关照使用的Shader顶点着色器代码如下所示:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;out vec2 TexCoords;void main()
{gl_Position = vec4(position, 1.0f);TexCoords = texCoords;
}

关照的片段着色器代码如下所示:

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D gAlbedo;
uniform sampler2D ssao;struct Light {vec3 Position;vec3 Color;float Linear;float Quadratic;
};
uniform Light light;void main()
{             // Retrieve data from g-buffervec3 FragPos = texture(gPositionDepth, TexCoords).rgb;vec3 Normal = texture(gNormal, TexCoords).rgb;vec3 Diffuse = texture(gAlbedo, TexCoords).rgb;float AmbientOcclusion = texture(ssao, TexCoords).r;// Then calculate lighting as usualvec3 ambient = vec3(0.3 * AmbientOcclusion); // <-- this is where we use ambient occlusionvec3 lighting  = ambient; vec3 viewDir  = normalize(-FragPos); // Viewpos is (0.0.0)// Diffusevec3 lightDir = normalize(light.Position - FragPos);vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color;// Specularvec3 halfwayDir = normalize(lightDir + viewDir);  float spec = pow(max(dot(Normal, halfwayDir), 0.0), 8.0);vec3 specular = light.Color * spec;// Attenuationfloat distance = length(light.Position - FragPos);float attenuation = 1.0 / (1.0 + light.Linear * distance + light.Quadratic * distance * distance);diffuse *= attenuation;specular *= attenuation;lighting += diffuse + specular;FragColor = vec4(lighting, 1.0);
}

屏幕空间环境遮蔽是一个可高度自定义的效果,它的效果很大程度上依赖于我们根据场景类型调整它的参数。对所有类型的场景并不存在什么完美的参数组合方式。一些场景只在小半径情况下工作,又有些场景会需要更大的半径和更大的样本数量才能看起来更真实。当前这个演示用了64个样本,属于比较多的了,你可以调调更小的核心大小从而获得更好的结果。

一些你可以调整(比如说通过uniform)的参数:核心大小,半径和/或噪声核心的大小。你也可以提升最终的遮蔽值到一个用户定义的幂从而增加它的强度:

occlusion = 1.0 - (occlusion / kernelSize);
FragColor = pow(occlusion, power);

多试试不同的场景和不同的参数,来欣赏SSAO的可定制性。

尽管SSAO是一个很微小的效果,可能甚至不是很容易注意到,它在很大程度上增加了合适光照场景的真实性,它也绝对是一个在你工具箱中必备的技术。

OpenGL核心技术之SSAO技术讲解(三)相关推荐

  1. OpenGL核心技术之切线空间

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  2. 游戏运动模糊技术讲解

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  3. OpenGL核心技术之GPU编程

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  4. 玩转iOS开发:NSURLSession讲解(三)

    文章分享至我的个人技术博客: https://cainluo.github.io/14986211698053.html 前言 虽然前面两讲都是说了NSURLSession的一些理论上的知识, 但我们 ...

  5. 后端薪资要比前端高吗?什么技术是公司的核心技术?前端技术算不算核心技术?

    前段时间在跟学生聊天,有同学提到了:以后的工作方向,他说:听学长学姐说后端薪资要比前端高,并且掌握公司核心技术的都是后端,但是自己想要从事前端该怎么规划发展自己的事业? 大致是这个意思记得不是很仔细了 ...

  6. MySQL字段约束及多表查询---讲解三

    MySQL环境配置(mysql有下载包) MySQL数据库表的基础操作(增删改查)-讲解一 MySQL数据库表的模糊/多行/分组/排序/分页查询以及字mysql数据类型的讲解-讲解二 MySQL字段约 ...

  7. 基于OpenGL的地形建模技术的研究与实现

    毕业论文 基于OpenGL的地形建模技术的研究与实现 诚信声明 本人郑重声明:本设计(论文)及其研究工作是本人在指导教师的指导下独立完成的,在完成设计(论文)时所利用的一切资料均已在参考文献中列出. ...

  8. 【技术美术】千人千面如何炼成 技术讲解捏脸系统设计原理

    学习自 http://games.sina.com.cn/o/z/wuxia/2015-10-15/fxivsch3599438-p5.shtml 1. 技术讲解捏脸系统设计原理 天刀脸模型的风格定位 ...

  9. OpenGL播放视频的技术

    本文记录OpenGL播放视频的技术.上一篇文章中,介绍了一种简单的使用OpenGL显示视频的方式.但是那还不是OpenGL显示视频技术的精髓.和Direct3D一样,OpenGL更好的显示视频的方式也 ...

最新文章

  1. python中平均值函数_python自定义函数ma(x,y)求简单平均值输出结果到列表
  2. 学习 Message(14): 区分左右 Shift、Ctrl、Alt
  3. APIO2010 特别行动队 斜率优化DP算法笔记
  4. 2.利用计算机进行信息加工的一般过程是:,[信息技术教案]《计算机信息加工的一般过程》教案...
  5. 十三、深入Python字典和集合
  6. spring配置详解-属性注入(p名称空间SPEL表达式)
  7. 克隆需要验证_GeneCopoeia基因克隆
  8. 认识本质:黑天鹅、关键时刻与张小龙的产品观
  9. 机器学习之 weka学习(二)算法说明
  10. python:软件目录结构规范
  11. PACKING【二维01背包】
  12. ZBlog插件简洁轻巧的编辑器 iceEditor修复版
  13. HDU 2844 Coins (多重背包)
  14. Logback配置一(按时间归档)
  15. jQuery:获取浏览器中的分辨率
  16. MacOS自动操作Automator的技巧
  17. 微信小程序——flex弹性布局水平垂直居中
  18. e.detail.value 小程序如何传值
  19. 谜一样的科学家——阿兰图灵
  20. Ubuntu19下隐藏桌面图标

热门文章

  1. C#操作Excel模板
  2. 联想拯救者Y7000亮度调低后屏幕黑屏
  3. 华为matebookxpro调不了亮度解决办法
  4. ue4 改变枢轴位置_[UE4蓝图][Materials]虚幻4中可互动的雪地材质完整实现(二)
  5. Java 的内存结构
  6. Arbitrum 的 Nitro 项目启动和交易执行源码解析
  7. jQuery Validate验证使用记录
  8. Android 基于Kotlin Flow实现一个倒计时功能
  9. 魔灵的羁绊为啥显示连接服务器失败,魔灵的羁绊连接 | 手游网游页游攻略大全...
  10. 廊坊知恩:什么是互动率