什么是延迟着色(Deferred Shading)?它是相对于正常使用的正向着色(Forward Shading)而言的,正向着色的工作模式:遍历光源,获取光照条件,接着遍历物体,获取物体的几何数据,最后根据光照和物体几何数据进行计算。

但是正向着色(Forward Shading)在光源非常多的情况下,对性能的消耗非常大。因为程序要对每一个光源,每一个需要渲染的物体,每一个需要渲染的片段进行迭代!还有片段着色器的输出会被之后的输出覆盖,正向渲染会在场景中因多个物体重合在一个像素上浪费大量的片段着色器运行时间。

延迟着色(Deferred Shading),就是为了解决上述问题而生,尤其是需要渲染几百上千个光源的场景。

本节实现的效果请看:延迟着色 deferred sharding

正向着色伪代码:

foreach light {
foreach visible mesh {
if (light volume intersects mesh) {
render using this material/light shader;
accumulate in framebuffer using additive blending;
}
}
}

延迟着色

延迟着色(Deferred Shading)工作模式就是将计算量大的渲染光照部分 延迟(Defer) 到后期进行处理,因此它包含两个处理阶段(Pass):

  1. 第一个 几何处理阶段(Geometry Pass) 是将对象的各种几何信息进行渲染并储存在叫做G缓冲(G-buffer)的纹理中。主要包括位置向量(Position Vector)颜色向量(Color Vector)法向量(Normal Vector)。这些储存在G缓冲中的几何信息将会用来做之后的光照计算。
  2. 第二个 光照处理阶段(Lighting Pass) 是对G缓冲中的几何数据的每一个片段进行光照计算。光照处理阶段不是直接将每个对象从顶点着色器带到片段着色器,而是对G缓冲中的每个像素进行迭代,从对应的G缓冲纹理获取输入变量。

延迟着色伪代码:

// g-buffer pass
foreach visible mesh {
write material properties to g-buffer;
} // light accumulation pass
foreach light {
compute light by reading g-buffer;
accumulate in framebuffer;
}

帧缓冲

延迟着色(Deferred Shading)G缓冲(G-buffer) 是基于 帧缓冲(frameBuffer) 实现的,涉及到高级应用,帧缓冲 真的是无处不在啊!该demo的几何处理阶段分别对位置(position)法向量(normal)颜色(color) 进行缓存,那么对应就要建立3个颜色附件,别忘了同时建立用于深度测试用的 深度缓冲(Z-Buffer)

const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
const fbo = {
framebuffer: fb,
textures: []
}; // 创建颜色纹理
for(let i = 0; i < 3; i++){
const tex = initTexture(gl, { informat: gl.RGBA16F, type: gl.FLOAT }, width, height);
framebufferInfo.textures.push(tex);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, 0);
} // 创建深度渲染缓冲区
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

多渲染目标 draw buffers

WebGL 实现多渲染目标需要打开 WEBGL_draw_buffers 这个扩展,但是 WebGL 2.0 直接就能使用的。我这里为了方便就基于 WebGL 2.0 来实现,多渲染目标调用方式如下:

gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);

着色器

因为延迟着色器分两个阶段,那么对应就需要两队着色器,首先来看几何处理阶段的着色器。

几何处理阶段 顶点着色器(vertex)

#version 300 es
in vec4 aPosition;
in vec4 aNormal;
uniform mat4 modelMatrix;
uniform mat4 vpMatrix;
out vec3 vPosition;
out vec3 vNormal; void main() {
gl_Position = vpMatrix * modelMatrix * aPosition;
vNormal = vec3(transpose(inverse(modelMatrix)) * aNormal);
vPosition = vec3(modelMatrix * aPosition);
}

几何处理阶段 片段着色器(fragment),这里的三个输出变量对应就是帧缓冲中的三个颜色纹理。

#version 300 es
precision highp float;
layout (location = 0) out vec3 gPosition;// 位置
layout (location = 1) out vec3 gNormal; // 法向量
layout (location = 2) out vec4 gColor; // 颜色
uniform vec4 color;
in vec3 vPosition;
in vec3 vNormal; void main() {
gPosition = vPosition;
gNormal = normalize(vNormal);
gColor = color;
}

接着就是光照处理阶段的着色器组了。

光照处理阶段 顶点着色器(vertex),这个非常简单,对应到帧缓冲,也就是个平面贴图而已。

#version 300 es
in vec3 aPosition;
in vec2 aTexcoord;
out vec2 texcoord; void main() {
texcoord = aTexcoord;
gl_Position = vec4(aPosition, 1.0);
}

光照处理阶段 片段着色器(fragment),需要从对应的纹理贴图取出对应的几何数据。也就是使用 texture 函数结合贴图和 贴图坐标(texcoord) 就可以计算出对应的几何数据,接着就是根结合照进行渲染最终结果。

#version 300 es
precision highp float;
uniform vec3 viewPosition;
uniform vec3 lightDirection;
uniform vec3 lightColor;
uniform vec3 ambientColor;
uniform float shininess;
// 各种自定义变量 ...
uniform sampler2D gPosition;// 位置
uniform sampler2D gNormal; // 法向量
uniform sampler2D gColor; // 颜色
in vec2 texcoord; // 坐标
out vec4 FragColor; void main() {
vec3 fragPos = texture(gPosition, texcoord).rgb;
vec3 normal = texture(gNormal, texcoord).rgb;
vec3 color = texture(gColor, texcoord).rgb; // todo: 各种计算过程...
// 环境光
vec3 ambient = ambientColor * color; // 漫反射
// ...
vec3 diffuse = lightColor * color * cosTheta; // 高光
// ...
vec3 specular = lightColor * specularIntensity; FragColor = vec4(ambient + diffuse + specular, 1.0);
}

WebGL 流程

最后就是使用 JavaScript 将整个流程串起来,WebGL 的其他技术细节不再详细介绍了,具体可以看我之前的 WebGL 教程。这里介绍一下大体的流程:

  1. 几何处理阶段:绑定帧缓冲,切换到对应的program,设置各种变量,输出到帧缓冲;
  2. 光照处理阶段:切换回正常缓冲,切换到对应的program,设置各种变量,同时将前面几何处理阶段的得到纹理作为输入变量,输出渲染结果;
/**
* 场景绘制到帧缓冲区
*/
gl.bindFramebuffer(target, fbo.framebuffer); // 绑定帧缓冲
gl.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清屏
gl.useProgram(program);
//采样到3个颜色附件(对应的几何纹理)
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
setUniforms(program, uniforms);// 设置uniform变量
setBuffersAndAttributes(gl, vao);// 设置缓存和attribute变量
drawBufferInfo(gl, vao); // 写入缓冲区 /**
* 从帧缓存渲染到正常缓冲区
*/
gl.bindFramebuffer(target, null); // 切换回正常缓冲
gl.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(fProgram);
const uniforms = {
// 其他变量 ...
gPosition: fbo.textures[0],// 位置纹理
gNormal: fbo.textures[1],// 法向量纹理
gColor: fbo.textures[2], // 颜色纹理
};
setUniforms(fProgram, uniforms);
setBuffersAndAttributes(gl, fVao);
drawBufferInfo(gl, fVao); // 输出画面

本节实现的效果请看:延迟着色 deferred sharding

demo 使用了1个平行光源,10个点光源,3个聚光灯实现了类似舞厅五彩斑斓的渲染效果。

最后

延迟着色(Deferred Shading) 在复杂光照条件下有着性能优势,但它也有缺点:大内存开销。还有在光源不是很多的场景中,延迟渲染并不一定会更快,甚至有时候由于开销过大还会变得更慢。当然在更复杂的场景中,延迟渲染会变成一个重要的性能优化手段。

WebGL之延迟着色的更多相关文章

  1. Deferred Shading 延迟着色(翻译)

    原文地址:https://en.wikipedia.org/wiki/Deferred_shading 在3D计算机图形学领域,deferred shading 是一种屏幕空间着色技术.它被称为Def ...

  2. WebGL编程指南案例解析之多数据存储于一个缓冲区以及着色器通信

    //顶点着色器往片元着色器传值 //多个参数值存于一个缓冲对象中 var vShader = ` attribute vec4 a_Position; attribute float a_PointS ...

  3. WebGL简易教程(二):向着色器传输数据

    目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...

  4. WebGL入门教程(五)-webgl纹理

    前面文章: WebGL入门教程(一)-初识webgl WebGL入门教程(二)-webgl绘制三角形 WebGL入门教程(三)-webgl动画 WebGL入门教程(四)-webgl颜色 这里就需要用到 ...

  5. WebGL入门教程(四)-webgl颜色

    前面文章: WebGL入门教程(一)-初识webgl WebGL入门教程(二)-webgl绘制三角形 WebGL入门教程(三)-webgl动画 颜色效果图: 操作步骤: 1.创建HTML5 canva ...

  6. WebGL入门教程(二)-webgl绘制三角形

    前面已经介绍过了webgl,WebGL入门教程(一)-初识webgl(http://www.cnblogs.com/bsman/p/6128447.html),也知道了如何绘制一个点,接下来就用web ...

  7. Web3D编程入门总结——WebGL与Three.js基础介绍

    /*在这里对这段时间学习的3D编程知识做个总结,以备再次出发.计划分成“webgl与three.js基础介绍”.“面向对象的基础3D场景框架编写”.“模型导入与简单3D游戏编写”三个部分,其他零散知识 ...

  8. [js] webgl 初探 - 绘制三角形

    摘要: 1. webgl 概念挺多的, 顶点着色器.片段着色器, 坐标 2. 绘制前期准备工作好多 目前看的比较好的教材: https://developer.mozilla.org/zh-CN/do ...

  9. Javascript高级编程学习笔记(96)—— WebGL(2) 类型化视图

    类型化视图 类型化视图一般也被认为是一种类型化数组. 因为其元素必须是某种特定的数据类型,类型化视图都继承自 Dataview Int8Array: 表示8位二补整数(即二进制补数) Uint8Arr ...

随机推荐

  1. 走正确的路 - IT业没有护城河 - 机器翻译新锐Deepl

    最近发生了一件很令我震惊的事情:新的一个机器翻译网站出现了 - www.deepl.com (DeepL 或许会成为你今年首选的翻译工具) 机器翻译早就是红海市场了.我就不从1954年IBM发布俄翻英 ...

  2. Vue生命周期,我奶奶看了都懂了

    最近一直在学习Vue,而vue生命周期是我们不可能绕开的一个很核心的知识点,今天来简单的梳理一下大概的内容. 一.钩子函数 在一开始学习的时候,总有钩子函数这个名词冒出来,而且在vue官网文档中也频繁 ...

  3. 操作系统-中断(2)IA-32/Linux的向量中断方式

    一.Intel定义下的异常和中断 不同体系和教材往往对异常和中断有不同的定义. Intel定义:中断是一种典型的由I/O设备触发的.与当前正在执行的指令无关的异步事件:而异常是处理器执行一条指令时,由 ...

  4. 第五篇Scrum冲刺博客--Interesting-Corps

    第五篇Scrum冲刺博客 站立式会议 1.会议照片 2.队友完成情况 团队成员 昨日完成 今日计划 鲍鱼铭 音乐详情页面跳转.设计及布局实现设计 搜索页面以及音乐详情页面数据导入及测试 叶学涛 编写分 ...

  5. 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单

    接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...

  6. 如何检查nofollow超链接属性是否有效

    http://www.wocaoseo.com/thread-88-1-1.html     nofollow 标签的重要性就不用阐述了,在这里武汉SEO与大家分享一些nofollow 标签的基本知识 ...

  7. Qt QString字符串分割、截取

    在做项目中不可避免的会使用到一串字符串中的一段字符,因此常常需要截取字符串. 有两种方式可以解决这个问题: 方法一:QString分割字符串: QString date=dateEdit.toStri ...

  8. C#封装定时执行任务类

    a.日常开发中经常会遇到定时去执行一些操作,比如定时更新数据.A类需要做我们写个Timer定时去取数据,这时候B类,C类也需要做这样的事情,是不是需要写三次重复代码? 这时候把timer封装成一个帮助 ...

  9. 基于.NetCore3.1系列 —— 日志记录之初识Serilog

    一.前言 对内置日志系统的整体实现进行了介绍之后,可以通过使用内置记录器来实现日志的输出路径.而在实际项目开发中,使用第三方日志框架(如: Log4Net.NLog.Loggr.Serilog.Sen ...

  10. Kubernetes实战总结 - 阿里云ECS自建K8S集群

    一.概述 详情参考阿里云说明:https://help.aliyun.com/document_detail/98886.html?spm=a2c4g.11186623.6.1078.323b1c9b ...