什么是延迟着色(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. es数据库基本操作

    1.es建立索引: curl -XPUT 'http://10.xx.xx.xx:9200/索引名称' 2.es查询所有索引: curl -XGET 'http://10.xx.xx.xx:9200/ ...

  2. unity探索者之微信支付,非第三方插件

    版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/8404604.html 相比微信的登录和分享功能,微信支付sdk的接入显得相当简单, ...

  3. 寻找猴王小游戏php代码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. Java数据结构——图的DFS和BFS

    1.图的DFS: 即Breadth First Search,深度优先搜索是从起始顶点开始,递归访问其所有邻近节点,比如A节点是其第一个邻近节点,而B节点又是A的一个邻近节点,则DFS访问A节点后再访 ...

  5. composer源码简单分析(一)

    composer分析(一) 本文内容 基于PSR-4规范的自动加载 请结合文档和下面的代码注释 spl_autoload_register php闭包Closure简单用法(大体使用情景: 生成回调提 ...

  6. python实用小技能分享,教你如何使用 Python 将 pdf 文档进行 加密 解密

    上次说了怎么将word转换为pdf格式 及 实现批量将word转换为pdf格式(点击这里),这次我又get到一个新技能–使用 Python 将 pdf 文档进行 加密 解密,哈哈哈 希望帮到更多人! ...

  7. 神奇的BUG系列-01

    有时候遇见一个bug,感觉就是他了 其实他也不过是你职业生涯中写的千千万万个bug中的一员 你所要做的,是放下 日子还长,bug很多,不差这一个 就此别过,分手快乐 一辈子那么长,一天没放下键盘 你就 ...

  8. 记录一个基于Java的利用快排切分来实现快排TopK问题的代码模板

    使用快排切分实现快排和TopK问题的解题模板 import java.util.Arrays; public class TestDemo { public static void main(Stri ...

  9. Centos7.6系统下docker的安装

    一.环境说明 系统:CentOS7.6 软件:Docker19.03 二.Docker的安装 2.1.在线安装 (1) 设置仓库,安装所需的软件包. yum-utils 提供了 yum-config- ...

  10. 非旋Treap——fhq treap

    https://www.luogu.org/problemnew/show/P3369 知识点:1.拆分split,合并merge 2.split,merge要点:通过传址调用来简便代码 3.记得ro ...