您当前所在位置: > 爆料站 > 君子堂

光影的魔法!Cocos Creator 实现屏幕空间的环境光遮蔽(SSAO)

时间:2021-10-13 13:35:13  来源:  作者:网络转载
光影镜像相机

光影镜像相机

大小:24.9M更新:2021-01-09

分类:拍摄美化

引言:

本文作者 alpha 从事游戏前端开发已经5年,毕业后他先是入职了腾讯无线大连研发中心,而后开启了北漂生涯,在北京的这3年一直都在使用 Cocos Creator,对前端业务,包体、内存优化有很多的实践经验。最近 alpha 在学习计算机图形学相关技术,今天他将同大家分享 Cocos Creator 3.3 实现屏幕空间的环境光遮蔽(SSAO)的技术经验。

什么是 AO ?

环境光(Ambient Lighting)是场景总体光照中的一个固定光照常量,用来模拟光的散射(Scattering)。在现实中,光线会以任意方向散射,它的强度是会改变的。

其中一种间接光照的模拟叫做环境光遮蔽(Ambient Occlusion),它的原理是通过将褶皱、孔洞和非常靠近的墙面变暗的方法近似模拟出间接光照。这些区域很大程度上是被周围的几何体遮挡的,所以这些地方看起来会更暗一些。

在2007年,Crytek 公司发布了一款叫做屏幕空间环境光遮蔽(Screen Space Ambient Occlusion,SSAO)的技术,并用在了他们的看家作孤岛危机上。这一技术使用了屏幕空间场景的深度而不是真实的几何体数据来确定遮蔽量。这一做法相对于真正的环境光遮蔽(基于光线追踪)不但速度快,而且还能获得较好的效果,使得它成为近似实时环境光遮蔽的标准。

下面这幅图展示了在使用和不使用 SSAO 时场景的不同。特别注意对比电话亭后面和墙角部分,你会发现(环境)光被遮蔽了许多:


虽然这个效果不是非常明显,但是启用 AO 确实给我们更真实的感觉,这些小的遮蔽细节能让整个场景看起来更有立体感。

SSAO 原理

SSAO 背后的原理很简单:对于屏幕上的每一个片段,会根据周边深度值计算一个遮蔽因子(Occlusion Factor)。这个遮蔽因子之后会被用来决定片段的环境光分量。遮蔽因子是通过采集片段周围球型核心(Kernel)的多个深度样本,并和当前片段深度值对比而得到的。高于片段深度值样本的个数就是我们想要的遮蔽因子。


上图中在几何体内灰色的深度样本都是高于片段深度值的,他们会增加遮蔽因子;几何体内样本个数越多,片段获得的环境光照也就越少。

很明显,渲染效果的质量和精度与采样的样本数量有直接关系。如果样本数量太低,渲染的精度会急剧减少,会得到一种叫做波纹(Banding)的效果;如果它太高了,会影响性能。通过引入随机性到采样核心(Sample Kernel)从而减少样本的数目。通过随机旋转采样核心,能在有限样本数量中得到高质量的结果。然而随机性引入了一个很明显的噪声图案,需要通过模糊降噪来修复这一问题。下面这幅图片展示了波纹效果还有随机性造成的效果:


可以看到,尽管在低样本数的情况下得到了很明显的波纹效果,引入随机性之后这些波纹效果就完全消失了。最初 Crytek 的实现是用一个深度缓冲做为输入,但是这种方式存在一些问题(如自遮闭, 光环),由于这个原因,现在通常不会使用球体的采样核心,而是使用一个沿着表面法向量的半球体采样核心。


通过在法向半球体(Normal Oriented Hemisphere)周围采样,将不会考虑到片段背面的几何体,它消除了环境光遮蔽灰蒙蒙的感觉,从而产生更真实的结果。

SSAO 特点:

独立于场景复杂性,仅和投影后最终的像素有关,和场景中的顶点数三角数无关。

跟传统的 AO 处理方法相比,不需要预处理,无需加载时间,也无需系统内存中的内存分配,所以更加适用于动态场景。

对屏幕上的每个像素以相同的一致方式工作。

没有 CPU 使用 - 它可以在 GPU 上完全执行。

可以轻松集成到任何现代图形管线中。

在了解了 AO & SSAO 之后,我们来看看要怎么基于 Cocos Creator 3.3.1 实现 SSAO。

Demo 地址:

https://gitee.com/yanjifa/cc-ssao-demo

样本缓冲

SSAO 需要几何体的信息来确定一个片段的遮蔽因子,对于每个片段(像素),需要如下数据:

逐片段位置向量

逐片段法线向量

逐片段反射颜色

采样核心

用来旋转采样核心的随机旋转向量

通过使用一个逐片段观察空间位置,可以将一个采样半球核心对准片段的观察空间表面法线。对于每一个核心样本会采样线性深度纹理来比较结果。采样核心会根据旋转矢量稍微偏转一点;所获得的遮蔽因子将会之后用来限制最终的环境光照分量。


通过以上发现 SSAO 所需的数据不正是延迟管线的 G-buffer,关于 G-buffer 是什么可通过文章「延迟着色法」[1]做一个简单的了解。阅读引擎代码 editor/assets/chunks/standard-surface-entry-entry.chunk 和 cocos/core/pipeline/define.ts :

// editor/assets/chunks/standard-surface-entry-entry.chunk 33 行附近

#elif CC_PIPELINE_TYPE == CC_PIPELINE_TYPE_DEFERRED

layout(location = 0) out vec4 fragColor0;

layout(location = 1) out vec4 fragColor1;

layout(location = 2) out vec4 fragColor2;

layout(location = 3) out vec4 fragColor3;

void main () {

StandardSurface s; surf(s);

fragColor0 = s.albedo;                         // 漫反射颜色 -> 反照率纹理

fragColor1 = vec4(s.position, s.roughness);    // 位置 -> 世界空间位置

fragColor2 = vec4(s.normal, s.metallic);       // 法线 -> 世界空间法线

fragColor3 = vec4(s.emissive, s.occlusion);    // 和本文无关, 不做介绍

}

#endif


// cocos/core/pipeline/define.ts  117 行 附近

export enum PipelineGlobalBindings {

UBO_GLOBAL,

UBO_CAMERA,

UBO_SHADOW,

SAMPLER_SHADOWMAP,

SAMPLER_ENVIRONMENT,

SAMPLER_SPOT_LIGHTING_MAP,

SAMPLER_GBUFFER_ALBEDOMAP,   // 6

SAMPLER_GBUFFER_POSITIONMAP, // 7

SAMPLER_GBUFFER_NORMALMAP,   // 8

SAMPLER_GBUFFER_EMISSIVEMAP,

SAMPLER_LIGHTING_RESULTMAP,

COUNT,

}

// cocos/core/pipeline/define.ts  283 行 附近

const UNIFORM_GBUFFER_ALBEDOMAP_NAME = 'cc_gbuffer_albedoMap';

export const UNIFORM_GBUFFER_ALBEDOMAP_BINDING = PipelineGlobalBindings.SAMPLER_GBUFFER_ALBEDOMAP; // 6

// ...

const UNIFORM_GBUFFER_POSITIONMAP_NAME = 'cc_gbuffer_positionMap';

export const UNIFORM_GBUFFER_POSITIONMAP_BINDING = PipelineGlobalBindings.SAMPLER_GBUFFER_POSITIONMAP; // 7

// ...

const UNIFORM_GBUFFER_NORMALMAP_NAME = 'cc_gbuffer_normalMap';

export const UNIFORM_GBUFFER_NORMALMAP_BINDING = PipelineGlobalBindings.SAMPLER_GBUFFER_NORMALMAP; // 8

// ...

通过以上代码可以分析出引擎 G-buffer 的数据布局,和具体 G-buffer 数据内容,深度值后面将会使用 G-buffer 计算得出。


自定义渲染管线

通过扩展延迟渲染管线的方式,在内置渲染管线的 LightFlow 上增加 一个 SsaoStage 用来生成 AO 纹理。首先创建一个渲染管线资源,资源管理器右键->创建->Render Pipeine->Render Pipeline Asset,命名为 ssao-deferrd-pipeline,创建 ssao-material | ssao-effect 着色器用来计算 AO 纹理,完整文件如下:

.

├── ssao-constant.chunk            // UBO 描述

├── ssao-deferred-pipeline.rpp     // 管线资源文件

├── ssao-effect.effect             // ssao shader

├── ssao-lighting.effect           // 光照 shader, 直接拷贝内置 internal/effects/pipeline/defferrd-lighting

├── ssao-lighting.mtl

├── ssao-material.mtl

├── ssao-render-pipeline.ts        // 定制管线脚本

├── ssao-stage.ts                  // stage 脚本

└── uboDefine.ts                   // Uniform Buffer Object 定义脚本

对应管线配置如下,在 LightingFlow 下 Stages 最前面加入 SsaoStage,并指定对应的材质,可以看到,引擎现在其实已经支持后处理(PostProcess)了,只要指定材质就可以了,可能当前版本还不完善,所以引擎组还没公开,其实 SSAO 也可以算是一种后处理效果,管线资源的属性设置如下:


自定义管线脚本如下:

// uboDefine.ts

import { gfx, pipeline } from "cc";

const { DescriptorSetLayoutBinding, UniformSamplerTexture, DescriptorType, ShaderStageFlagBit, Type } = gfx;

const { SetIndex, PipelineGlobalBindings, globalDescriptorSetLayout } = pipeline;

let GlobalBindingStart = PipelineGlobalBindings.COUNT; // 11

let GlobalBindingIndex = 0;

/**

* 定义 SSAO Frame Buffer, 布局描述

*/

const UNIFORM_SSAOMAP_NAME = 'cc_ssaoMap';

export const UNIFORM_SSAOMAP_BINDING = GlobalBindingStart + GlobalBindingIndex++; // 11

const UNIFORM_SSAOMAP_DESCRIPTOR = new DescriptorSetLayoutBinding(UNIFORM_SSAOMAP_BINDING, DescriptorType.SAMPLER_TEXTURE, 1, ShaderStageFlagBit.FRAGMENT);

const UNIFORM_SSAOMAP_LAYOUT = new UniformSamplerTexture(SetIndex.GLOBAL, UNIFORM_SSAOMAP_BINDING, UNIFORM_SSAOMAP_NAME, Type.SAMPLER2D, 1);

globalDescriptorSetLayout.layouts[UNIFORM_SSAOMAP_NAME] = UNIFORM_SSAOMAP_LAYOUT;

globalDescriptorSetLayout.bindings[UNIFORM_SSAOMAP_BINDING] = UNIFORM_SSAOMAP_DESCRIPTOR;

/**

* 采样核心、相机远近裁剪面 near & far 等 UniformBlock 布局描述

*/

export class UBOSsao {

public static readonly SAMPLES_SIZE = 64; // 最大采样核心

public static readonly CAMERA_NEAR_FAR_LINEAR_INFO_OFFSET = 0;

public static readonly SSAO_SAMPLES_OFFSET = UBOSsao.CAMERA_NEAR_FAR_LINEAR_INFO_OFFSET + 4;

public static readonly COUNT = (UBOSsao.SAMPLES_SIZE + 1) * 4;

public static readonly SIZE = UBOSsao.COUNT * 4;

public static readonly NAME = 'CCSsao';

public static readonly BINDING = GlobalBindingStart + GlobalBindingIndex++; // 12

public static readonly DESCRIPTOR = new gfx.DescriptorSetLayoutBinding(UBOSsao.BINDING, gfx.DescriptorType.UNIFORM_BUFFER, 1, gfx.ShaderStageFlagBit.ALL);

public static readonly LAYOUT = new gfx.UniformBlock(SetIndex.GLOBAL, UBOSsao.BINDING, UBOSsao.NAME, [

new gfx.Uniform('cc_cameraNFLSInfo', gfx.Type.FLOAT4, 1), // vec4

new gfx.Uniform('ssao_samples', gfx.Type.FLOAT4, UBOSsao.SAMPLES_SIZE), // vec4[64]

], 1);

}

globalDescriptorSetLayout.layouts[UBOSsao.NAME] = UBOSsao.LAYOUT;

globalDescriptorSetLayout.bindings[UBOSsao.BINDING] = UBOSsao.DESCRIPTOR;

/**

*  ssao-render-pipeline.ts

*  扩展延迟渲染管线

*/

import { _decorator, DeferredPipeline, gfx, renderer } from "cc";

import { UNIFORM_SSAOMAP_BINDING } from "./uboDefine";

const { ccclass } = _decorator;

const _samplerInfo = [

gfx.Filter.POINT,

gfx.Filter.POINT,

gfx.Filter.NONE,

gfx.Address.CLAMP,

gfx.Address.CLAMP,

gfx.Address.CLAMP,

];

const samplerHash = renderer.genSamplerHash(_samplerInfo);

export class SsaoRenderData {

frameBuffer?: gfx.Framebuffer | null;

renderTargets?: gfx.Texture[] | null;

depthTex?: gfx.Texture | null;

}

@ccclass("SsaoRenderPipeline")

export class SsaoRenderPipeline extends DeferredPipeline {

private _width = 0;

private _height = 0;

private _ssaoRenderData: SsaoRenderData | null = null!;

private _ssaoRenderPass: gfx.RenderPass | null = null;

public activate(): boolean {

const result = super.activate();

this._width = this.device.width;

this._height = this.device.height;

this._generateSsaoRenderData();

return result;

}

public resize(width: number, height: number) {

if (this._width === width && this._height === height) {

return;

}

super.resize(width, height);

this._width = width;

this._height = height;

this._destroyRenderData();

this._generateSsaoRenderData();

}

public getSsaoRenderData(camera: renderer.scene.Camera): SsaoRenderData {

if (!this._ssaoRenderData) {

this._generateSsaoRenderData();

}

return this._ssaoRenderData!;

}

/**

* 核心代码, 创建一个 FrameBuffer 存储 SSAO 纹理

*/

private _generateSsaoRenderData() {

if (!this._ssaoRenderPass) {

const colorAttachment = new gfx.ColorAttachment();

colorAttachment.format = gfx.Format.RGBA8;

colorAttachment.loadOp = gfx.LoadOp.CLEAR;

colorAttachment.storeOp = gfx.StoreOp.STORE;

colorAttachment.endAccesses = [gfx.AccessType.COLOR_ATTACHMENT_WRITE];

const depthStencilAttachment = new gfx.DepthStencilAttachment();

depthStencilAttachment.format = this.device.depthStencilFormat;

depthStencilAttachment.depthLoadOp = gfx.LoadOp.CLEAR;

depthStencilAttachment.depthStoreOp = gfx.StoreOp.STORE;

depthStencilAttachment.stencilLoadOp = gfx.LoadOp.CLEAR;

depthStencilAttachment.stencilStoreOp = gfx.StoreOp.STORE;

const renderPassInfo = new gfx.RenderPassInfo([colorAttachment], depthStencilAttachment);

this._ssaoRenderPass = this.device.createRenderPass(renderPassInfo);

}

this._ssaoRenderData = new SsaoRenderData();

this._ssaoRenderData.renderTargets = [];

// 因为 SSAO 纹理最终是一张灰度图, 所以使用 Format.R8 单通道纹理, 减少内存占用, 使用时只需要读取 R 通道即可

this._ssaoRenderData.renderTargets.push(this.device.createTexture(new gfx.TextureInfo(

gfx.TextureType.TEX2D,

gfx.TextureUsageBit.COLOR_ATTACHMENT | gfx.TextureUsageBit.SAMPLED,

gfx.Format.R8,

this._width,

this._height,

)));

this._ssaoRenderData.depthTex = this.device.createTexture(new gfx.TextureInfo(

gfx.TextureType.TEX2D,

gfx.TextureUsageBit.DEPTH_STENCIL_ATTACHMENT,

this.device.depthStencilFormat,

this._width,

this._height,

));

this._ssaoRenderData.frameBuffer = this.device.createFramebuffer(new gfx.FramebufferInfo(

this._ssaoRenderPass!,

this._ssaoRenderData.renderTargets,

this._ssaoRenderData.depthTex,

));

this.descriptorSet.bindTexture(UNIFORM_SSAOMAP_BINDING, this._ssaoRenderData.frameBuffer.colorTextures[0]!);

const sampler = renderer.samplerLib.getSampler(this.device, samplerHash);

this.descriptorSet.bindSampler(UNIFORM_SSAOMAP_BINDING, sampler);

}

public destroy(): boolean {

this._destroyRenderData();

return super.destroy();

}

private _destroyRenderData() {

if (!this._ssaoRenderData) {

return;

}

if (this._ssaoRenderData.depthTex) {

this._ssaoRenderData.depthTex.destroy();

}

if (this._ssaoRenderData.renderTargets) {

this._ssaoRenderData.renderTargets.forEach((o) => {

o.destroy();

})

}

if (this._ssaoRenderData.frameBuffer) {

this._ssaoRenderData.frameBuffer.destroy();

}

this._ssaoRenderData = null;

}

}

通过项目设置修改渲染管线为自定义的 SSAO 管线:


采样核心

我们需要沿着表面法线方向生成大量的样本。就像前面介绍的那样,想要生成形成半球形的样本。由于对每个表面法线方向生成采样核心非常困难,也不合实际,所以将在切线空间(Tangent Space)内生成采样核心,法向量将指向正 z 方向。

假设有一个单位半球,生成一个拥有最大64样本值的采样核心:

// ssao-stage.ts

activate(pipeline: DeferredPipeline, flow: RenderFlow) {

super.activate(pipeline, flow);

const device = pipeline.device;

this._sampleBuffer = device.createBuffer(new gfx.BufferInfo(

gfx.BufferUsageBit.UNIFORM | gfx.BufferUsageBit.TRANSFER_DST,

gfx.MemoryUsageBit.HOST | gfx.MemoryUsageBit.DEVICE,

UBOSsao.SIZE,

UBOSsao.SIZE,

));

this._sampleBufferData = new Float32Array(UBOSsao.COUNT);

const sampleOffset = UBOSsao.SSAO_SAMPLES_OFFSET / 4;

// 64 样本值采样核心, 这里写的不太详细, 可结合 LearnOpenGL CN 的教程, 加深理解

for (let i = 0; i < UBOSsao.SAMPLES_SIZE; i++) {

let sample = new Vec3(

Math.random() * 2.0 - 1.0,

Math.random() * 2.0 - 1.0,

Math.random() + 0.01, // 这里和原教程有点区别, Z 稍微增加一个很小的值, 可改善平面波纹(Banding)的效果, 可能会对精度造成影响

);

sample = sample.normalize();

let scale = i / UBOSsao.SAMPLES_SIZE;

// 通过插值, 将核心样本靠近原点分布

scale = lerp(0.1, 1.0, scale * scale);

sample.multiplyScalar(scale);

const index = 4 * (i + sampleOffset);

this._sampleBufferData[index + 0] = sample.x;

this._sampleBufferData[index + 1] = sample.y;

this._sampleBufferData[index + 2] = sample.z;

}

this._pipeline.descriptorSet.bindBuffer(UBOSsao.BINDING, this._sampleBuffer);

}

我们在切线空间中以-1.0到1.0为范围变换 x 和 y 方向,并以 0.0 和 1.0 为范围变换样本的 z 方向 (如果以-1.0到1.0为范围,取样核心就变成球型了)。由于采样核心将会沿着表面法线对齐,所得的样本矢量将会在半球里。通过权重插值,得到一个大部分样本靠近原点的核心分布。


获取深度数据

通过 G-buffer 中的 PostionMap 获取线性深度值:

float getDepth(vec3 worldPos) {

// 转到观察空间

vec3 viewPos = (cc_matView * vec4(worldPos.xyz, 1.0)).xyz;

// cc_cameraNFLSInfo.y -> 相机 Far, 通过 ssao-stage.ts 脚本更新

float depth = -viewPos.z / cc_cameraNFLSInfo.y;

return depth;

}

深度图如下:


SSAO 着色器

/**

* ssao-effect.effect

*/

CCProgram ssao-fs %{

precision highp float;

#include <cc-global>

#include <cc-shadow-map-base>

#include <ssao-constant>

// 最大 64

#define SSAO_SAMPLES_SIZE 64

in vec2 v_uv;

#pragma builtin(global)

layout (set = 0, binding = 7) uniform sampler2D cc_gbuffer_positionMap;

#pragma builtin(global)

layout (set = 0, binding = 8) uniform sampler2D cc_gbuffer_normalMap;

layout(location = 0) out vec4 fragColor;

// 随机数 0.0 - 1.0

float rand(vec2 uv, float dx, float dy)

{

uv += vec2(dx, dy);

return fract(sin(dot(uv,  vec2(12.9898, 78.233))) * 43758.5453);

}

// 随机旋转采样核心向量

vec3 getRandomVec(vec2 uv){

return vec3(

rand(uv, 0.0, 1.0) * 2.0 - 1.0,

rand(uv, 1.0, 0.0) * 2.0 - 1.0,

0.0

);

}

// 获取线性深度

float getDepth(vec3 worldPos) {

vec3 viewPos = (cc_matView * vec4(worldPos.xyz, 1.0)).xyz;

float depth = -viewPos.z / cc_cameraNFLSInfo.y;

return depth;

}

// 深度图

// void main () {

//   vec3 worldPos = texture(cc_gbuffer_positionMap, v_uv).xyz;

//   fragColor = vec4(getDepth(worldPos));

// }

void main () {

vec3 worldPos = texture(cc_gbuffer_positionMap, v_uv).xyz;

vec3 normal = texture(cc_gbuffer_normalMap, v_uv).xyz;

vec3 randomVec = getRandomVec(v_uv);

float fragDepth = -getDepth(worldPos);

// 创建一个TBN矩阵,将向量从切线空间变换到观察空间

vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));

vec3 bitangent = cross(normal, tangent);

mat3 TBN = mat3(tangent, bitangent, normal);

// 取样半径

float radius = 1.0;

float occlusion = 0.0;

for(int i = 0; i < SSAO_SAMPLES_SIZE; ++i)

{

vec3 ssaoSample = TBN * ssao_samples.xyz;

ssaoSample = worldPos + ssaoSample * radius;

float aoDepth = -getDepth(ssaoSample);

vec4 offset = vec4(ssaoSample, 1.0);

offset      = (cc_matProj * cc_matView) * offset;   // 转换到裁剪空间

offset.xyz /= offset.w;                             // 透视除法

offset.xyz  = offset.xyz * 0.5 + 0.5;               // 从 NDC (标准化设备坐标, -1.0 - 1.0) 变换到 0.0 - 1.0

vec3 samplePos = texture(cc_gbuffer_positionMap, offset.xy).xyz;

float sampleDepth = -getDepth(samplePos);

// 范围检查

float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragDepth - sampleDepth));

// 检查样本的当前深度值是否大于存储的深度值,如果是,添加到最终的贡献因子上

occlusion += (sampleDepth >= aoDepth ? 1.0 : 0.0) * rangeCheck;

}

// 将遮蔽贡献根据核心的大小标准化,并输出结果

occlusion = 1.0 - (occlusion / float(SSAO_SAMPLES_SIZE));

fragColor = vec4(occlusion, 1.0, 1.0, 1.0);

}

}%

下图展示了环境遮蔽着色器产生的纹理:


可见,环境遮蔽产生了非常强烈的深度感。仅仅通过环境遮蔽纹理就已经能清晰地看见模型一定躺在地板上而不是浮在空中。

现在的效果仍然看起来不是很完美,不连续的噪点清晰可见,为了创建一个光滑的环境遮蔽结果,需要模糊环境遮蔽纹理进行降噪。


应用 SSAO 纹理

最后将 SSAO 纹理进行模糊降噪,并逐片段将环境遮蔽因子乘到环境光照分量上,拷贝内置光照着色器(internal/effects/pipeline/deferred-lighting.effect)命名为 ssao-lighting.effect。

/**

* 本文改动部分添加了中文注释

*/

CCProgram lighting-fs %{

precision highp float;

#include <cc-global>

#include <shading-standard-base>

#include <shading-standard-additive>

#include <output-standard>

#include <cc-fog-base>

in vec2 v_uv;

#pragma builtin(global)

layout (set = 0, binding = 6) uniform sampler2D cc_gbuffer_albedoMap;

#pragma builtin(global)

layout (set = 0, binding = 7) uniform sampler2D cc_gbuffer_positionMap;

#pragma builtin(global)

layout (set = 0, binding = 8) uniform sampler2D cc_gbuffer_normalMap;

#pragma builtin(global)

layout (set = 0, binding = 9) uniform sampler2D cc_gbuffer_emissiveMap;

#pragma builtin(global)

layout (set = 0, binding = 11) uniform sampler2D cc_ssaoMap;

layout(location = 0) out vec4 fragColor;

vec4 gaussianBlur(sampler2D Tex, vec2 UV, float Intensity)

{

// 省略, 详见 demo 工程

return texture(Tex, UV);

}

// 屏幕展示 SSAO 纹理

// void main() {

//   // 降噪

//   vec4 color = gaussianBlur(cc_ssaoMap, v_uv, 3.0);

//   // 不降噪

//   vec4 color = texture(cc_ssaoMap, v_uv);

//   fragColor = vec4(vec3(color.r), 1.0);

// }

void main () {

StandardSurface s;

vec4 albedoMap = texture(cc_gbuffer_albedoMap,v_uv);

vec4 positionMap = texture(cc_gbuffer_positionMap,v_uv);

vec4 normalMap = texture(cc_gbuffer_normalMap,v_uv);

vec4 emissiveMap = texture(cc_gbuffer_emissiveMap,v_uv);

// ssao 环境遮蔽因子, 单通道纹理, 所以只取 R 通道

vec4 ssaoMap = vec4(vec3(gaussianBlur(cc_ssaoMap, v_uv, 3.0).r), 1.0);

s.albedo = albedoMap * ssaoMap; // 乘到辐照率贴图上, 应用遮蔽纹理

s.position = positionMap.xyz;

s.roughness = positionMap.w;

s.normal = normalMap.xyz;

s.metallic = normalMap.w;

s.emissive = emissiveMap.xyz;

s.occlusion = emissiveMap.w;

// fixme: default value is 0, and give black result

float fogFactor;

CC_TRANSFER_FOG_BASE(vec4(s.position, 1), fogFactor);

vec4 shadowPos;

CC_TRANSFER_SHADOW_BASE(vec4(s.position, 1), shadowPos);

vec4 color = CCStandardShadingBase(s, shadowPos) +

CCStandardShadingAdditive(s, shadowPos);

CC_APPLY_FOG_BASE(color, fogFactor);

fragColor = CCFragOutput(color);

}

}%

最后来看下最终的渲染结果对比,首先是 SSAO 开启的效果:


SSAO 关闭的效果:


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

已知问题

编辑器摄像机预览会渲染不正确。

资源管理器里面点击自定义管线资源文件,编辑器控制台会报错,可能会导致编辑器无响应 (目前建议没事别碰,碰过重启编辑器可恢复正常)。

手机浏览器 (小米10 Pro) 下使用最大采样核心 (64) 时,帧数只有个位数,可以确定当前版本基本不能应用到实际项目中,还需优化。

Native 下自定义渲染管线同时还需要自定义 Engine-Native[2] 引擎,所以 Native 暂时还未支持,可参考 PR 3934[3] 添加对 Native 的支持,这里要感谢 大表姐Kristine 提供的信息。

相关教程

LearnOpenGL-CN->目录->高级光照->SSAO:

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/09%20SSAO/

环境遮罩之 SSAO 原理:

https://zhuanlan.zhihu.com/p/46633896

GAMES202-高质量实时渲染(视频00:46:25开始):

https://www.bilibili.com/video/BV1YK4y1T7yY?p=8

参考链接

延迟着色法[1]:

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/08%20Deferred%20Shading/

Engine-Native[2]:

https://github.com/cocos-creator/engine-native/tree/develop/cocos/renderer/pipeline

PR 3934[3]:

https://github.com/cocos-creator/engine-native/pull/3934


来源:COCOS
原文:https://mp.weixin.qq.com/s/0aNtXI7s41meJhI5M4JItQ


资源转载网络,如有侵权联系删除。
相关下载

玩家评论

光影下的极品尤物性感撩人私拍

详情>>

阅读: 1
日期: 2021-09-18
《和平精英》新版本“光影冒险”开启 童年经典动画梦幻联动

近日《和平精英》开启全新的版本“光影冒险”,在新版本中那些经典的动画角色孙悟空、黑猫警长、九色鹿、沉香等会出现在游戏帮助玩家,全新的冒险等待玩家体验。详情>>

阅读: 2
日期: 2021-09-08
和平精英光影冒险模式打卡点在哪里-和平精英光影冒险模式打卡点位置介绍

和平精英光影冒险模式打卡点在哪里?很多玩家都还不知道,下面234游戏网为大家整理了和平精英光影冒险模式打卡点位置介绍,一起来看看吧。 和平精英光影冒险模式打卡点在详情>>

阅读: 2
日期: 2021-09-08
《和平精英》“光影冒险”新版本上线

  今日,《和平精英》联袂上海美术电影制片厂(以下简称:上美影),诚意打造的“光影冒险”全新版本正式上线!即日起,海岛地图增添的“光影工厂&详情>>

阅读: 1
日期: 2021-09-08
《和平精英》光影工厂活动介绍

  《和平精英》手游中的光影工厂活动是新上线的活动,相信还有很多小伙伴不是很清楚光影工厂活动内容是什么,下面是game234小编给大家带来的和平精英光影工厂活详情>>

阅读: 4
日期: 2021-09-07
高能手办团光影搭配攻略:光影搭配简单、困难、噩梦阵容推荐[多图]

高能手办团光影搭配有三个不同的难度模式,玩家们,具体的光影搭配打法game234小编这里会分享一些攻略,帮助玩家了解不同难度应该搭配什么样的阵容,详细的光影搭配攻略已经分享详情>>

阅读: 5
日期: 2021-08-28
和平精英光影冒险攻略大全 吃鸡光影冒险模式入口以及玩法分享

和平精英光影冒险是一个全新的玩法体验,目前是在体验中上线的,全新的玩法,有着多种的体验,能够在里面介绍出各种的规则,下面就来介绍下和平精英光影冒险怎么玩。 和平精英详情>>

阅读: 7
日期: 2021-08-24
和平精英光影冒险模式攻略大全 光影冒险模式玩法介绍

和平精英光影冒险模式怎么玩?游戏在近期开放了体验服,在这个体验服中,将上线各式各样的玩法,同时还有全新的地图以及新模式将同步上线,光影冒险作为本次新增的模式,那么这个模式详情>>

阅读: 7
日期: 2021-08-22
和平精英光影冒险模式攻略 光影冒险模式玩法详解

和平精英光影冒险模式不少玩家都在询问,那么具体的规则是什么,同时大家如何进入呢,下面为大家介绍和平精英光影冒险模式玩法详解。 和平精英光影冒险模式玩法详解 在海岛详情>>

阅读: 6
日期: 2021-08-22
和平精英光影冒险模式怎么玩?光影冒险模式玩法攻略[多图]

和平精英光影冒险是海岛地图带来的全新玩法,体验服已经更新了光影冒险模式,想要了解光影冒险的玩法可以参考下面game234小编带来的攻略,攻略中会具体的介绍光影冒险的玩法规详情>>

阅读: 6
日期: 2021-08-21
徐百慧朦胧光影红裙生日写真释出状态极佳_造型

原标题:徐百慧朦胧光影红裙生日写真释出 状态极佳 8月3日是演员徐百慧的生日,当天上午她在个人微博晒出一组美图,一身红色丝绸吊带裙尽显婀娜身姿,皮详情>>

阅读: 3
日期: 2021-08-03
《毁灭战士:永恒》光追vs原版对比 光影细节更真实

id Software在本周推送了《毁灭战士:永恒》次世代更新,追加了光线追踪、DLSS支持,油管UP主ElAnalistaDeBits随后分享了光追版《DOOM:永恒》开启/关闭光追详情>>

阅读: 4
日期: 2021-07-04
林允晒最新光影写真侧颜优秀氛围感十足_布前秀

原标题:林允晒最新光影写真 侧颜优秀氛围感十足 6月25日晚,林允在社交平台晒出六张最新写真,并配发一颗蓝色的心形表情包。写真中的林允,浓眉大眼配上详情>>

阅读: 4
日期: 2021-06-26
XSX版本画面升级《流放者柯南》光影效果超级明显

《流放者柯南》这款游戏在之前就已经正式加入Xbox Game Pass,订阅了关于XGP的玩家免费玩这款游戏作品,虽然这波热度让玩家门非常感兴趣之外还同时推出详情>>

阅读: 4
日期: 2021-06-10
设计上海2021落幕,留下光影故事_Occhio

原标题:设计上海2021落幕,留下光影故事 上个周末设计上海2021和“安藤忠雄:挑战”展览都在上海落下了帷幕。 普利兹克奖得主安藤忠雄说,如果他的作品详情>>

阅读: 2
日期: 2021-06-08
《质量效应:传奇版》VS原版画面对比 光影显著升级

作为登陆新主机的复刻作品,《质量效应:传奇版(Mass Effect Legendary Edition)》画质相比原版是有所区别的。随着游戏在本周正式发售,外媒近日拿《质量效应详情>>

阅读: 2
日期: 2021-05-16
中海·九樾造极开启建筑光影美学2.0时代_质感

原标题:中海·九樾造极 开启建筑光影美学2.0时代 央广网4月25日消息(记者 张道慧)最好的建筑是这样的,我们深处在其中,却不知道自然在哪里终了,艺术在哪详情>>

阅读: 4
日期: 2021-04-25
光影链接游戏介绍 光影链接好不好玩

光影链接玩法介绍,光影链接好不好玩,相信玩过光影链接日服,台服的玩家对这款游戏是不陌生的,因为这款游戏早已在台服日服公测了,至于国服的话,还早还早,毕竟因为改详情>>

阅读: 9
日期: 2021-04-20
光影链接飞鸟好看吗 光影链接飞鸟角色介绍

光影链接飞鸟好看吗,光影链接飞鸟角色介绍,相信很多爱玩光影链接的小伙伴跟小编一样,都是十分期待着正式服的上线,虽然现在还没什么消息,但是第二次内测后小编就详情>>

阅读: 9
日期: 2021-04-20
光影链接葛城怎么样 光影链接葛城角色解析

光影链接葛城怎么样,光影链接葛城角色解析,葛城是光影链接里一个很豪爽的角色,一头金发武力值爆表,那么葛城的具体资料是什么,她的cv是谁呢,感兴趣的小伙伴就跟随详情>>

阅读: 4
日期: 2021-04-19
光影链接未来怎么样 光影链接未来角色解析

光影链接未来怎么样,光影链接未来角色解析,未来是光影链接中焰红莲队的成员,比较的孩子气,不过还是有不少玩家喜欢的,那么未来强不强呢,到底是个怎样的角色呢,下面详情>>

阅读: 8
日期: 2021-04-19
光影链接春花厉害吗 光影链接春花角色解析

光影链接春花厉害吗,光影链接春花角色解析,相信很多了解过光影链接这款游戏的小伙伴都知道,春华在光影链接这款游戏中也是个炙手可热的人物,春花是光影链接中焰详情>>

阅读: 7
日期: 2021-04-19
FPS游戏《巫火》全新截图 光影效果绝美,场景真实

备受期待的暗黑魔幻风第一人称射击游戏《巫火》具体发售日期还没有公布,今天(4月16日),开发商Astronauts发布了三张《巫火》的全新游戏截图,展示了游戏中的详情>>

阅读: 6
日期: 2021-04-16
光影链接进不去游戏怎么办 光影链接进不去游戏解决办法

光影链接进不去游戏怎么办,光影链接进不去游戏解决办法,在茫茫的游戏大海中,也是有很多的游戏都出现在大众玩家的视线,今天给大家带来的就是光影链接的攻略,相信详情>>

阅读: 8
日期: 2021-04-16
光影链接黑屏怎么办 光影链接黑屏解决方法

光影链接黑屏怎么办,光影链接黑屏解决方法,光遇链接这款游戏今日也是迎来了公测,很多小伙伴就高高兴兴的去打开了游戏玩了起来,但是很多小伙伴也是遇到了许多的详情>>

阅读: 7
日期: 2021-04-16
光影链接丛怎么样 光影链接丛角色解析

光影链接丛怎么样,光影链接丛角色解析,光影链接丛怎么样?光影链接里丛是个很害羞的少女,喜欢拿面具挡着自己,那么大家觉得这样的她可爱吗,下面小编就为大家介绍一详情>>

阅读: 7
日期: 2021-04-16
光影链接无法连接服务器怎么办 光影链接无法连接服务器解决办法

光影链接无法连接服务器怎么办,光影链接无法连接服务器解决办法,相信很多的小伙伴也是注意到了今天2021年4月16日光影链接也是开启了测试服的,光影链接是一款美详情>>

阅读: 8
日期: 2021-04-16
光影链接日服叫什么 光影链接日服名称一览

光影链接日服叫什么 光影链接日服名称一览,光影链接这款游戏想必各位小伙伴都不陌生,日服已经公测好长时间了,国服也马上就要开启第二次删档测试了,下面就让我们详情>>

阅读: 5
日期: 2021-04-16
光影链接台服叫什么 光影链接台服最全解析

光影链接台服叫什么,光影链接台服最全解析,相信很多的小伙伴也是知道了,光遇链接即将在国服公测的消息了,但是很多的小伙伴也想知道,这款游戏在台服叫什么,好不好详情>>

阅读: 6
日期: 2021-04-16
光影链接测试服怎么进分享 光影链接测试服进入教学

光影链接测试服怎么进分享,光影链接测试服进入教学相信很多的小伙伴也是知道了光影链接的消息,光影链接会在16号开区测试服,但是很多小伙伴还不知道测试服怎么详情>>

阅读: 6
日期: 2021-04-15
光影链接什么时候公测 光影链接公测时间分享

光影链接什么时候公测,光影链接公测时间分享,光影链接是是一款RPG类动作冒险游戏,游戏采用十分精美的二次元动漫画风,玩家可以在这里收集获得各种美丽且强大的美详情>>

阅读: 7
日期: 2021-04-15
光影链接什么时候出 光影链接公测时间一览

光影链接什么时候出,光影链接公测时间一览,光影链接这款游戏出现在我们的视线已经很长时间了,但是很多小伙伴知道,这款游戏公测到底在什么时候,什么时候可以玩,今详情>>

阅读: 6
日期: 2021-04-15
光影链接游戏卡顿怎么办 光影链接卡顿最全解决办法

光影链接游戏卡顿怎么办,光影链接卡顿最全解决办法,相比光影链接这款游戏大家都听说过,这款游戏是由分众游戏自研的和风二次元人气手游,这款游戏主打豪华的声优详情>>

阅读: 6
日期: 2021-04-15
光影链接4月15日计费删档测试即将开启

光影链接4月15日计费删档测试即将开启,光影链接即将上线,还有很多小伙伴不知道,现在就跟随小编一起去看看吧。 非常感谢各位玩家长久以来的等待和支持。详情>>

阅读: 4
日期: 2021-04-14
最高3万!首届文旅光影设计暨光影摄影大赛启动_征集

原标题:最高3万!首届文旅光影设计暨光影摄影大赛启动 圆桌讨论环节。(央广网发 主办方供图) 央广网广州4月7日消息(记者官文清)4月7日,“2021首届文旅光详情>>

阅读: 5
日期: 2021-04-07
《黑暗之魂2》光影重制Mod新截图 改善光影,画面更漂亮

  近日3D光影艺术家Stayd公布了《黑暗之魂2》光影重制Mod的新截图,该Mod将彻底改善游戏光影效果,让画面更漂亮。目前这一款光影重制Mod还在制作中,Stayd表示完成后就会放出,想详情>>

阅读: 4
日期: 2021-03-29
《黑魂2》光影重制Mod新截图增加新光照,改善光影_黑暗

原标题:《黑魂2》光影重制Mod新截图 增加新光照,改善光影 近日3D光影艺术家Stayd公布了《黑暗之魂2》光影重制Mod的新截图,该Mod将彻底改善游戏光影详情>>

阅读: 5
日期: 2021-03-29
《黑魂2》光影重制Mod新截图 增加新光照,改善光影

近日3D光影艺术家Stayd公布了《黑暗之魂2》光影重制Mod的新截图,该Mod将彻底改善游戏光影效果,让画面更漂亮。一起来看看吧!目前这款《黑暗之魂2》光影重详情>>

阅读: 2
日期: 2021-03-29
超炫酷!2021北京国际光影艺术季户外沉浸式体验展亮相玉渊潭公园_万物

原标题:超炫酷!2021北京国际光影艺术季户外沉浸式体验展亮相玉渊潭公园 2021北京国际光影艺术季(玉渊潭站)“万物共生—蔚蓝”户外光影艺术沉浸式体详情>>

阅读: 8
日期: 2021-03-25
2021北京国际光影艺术季(玉渊潭站)——“万物共生-蔚蓝”户外光影艺术沉浸式体验展重装启幕_文化

原标题:2021北京国际光影艺术季(玉渊潭站)—— “万物共生-蔚蓝”户外光影艺术沉浸式体验展重装启幕 启航 邂逅未曾领略的风景,看见未曾想象的世界,“详情>>

阅读: 8
日期: 2021-03-25
精彩推荐