什么是延迟着色(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. 《MySQL必知必会》过滤数据,数据过滤(where ,in ,null ,not)

    <MySQL必知必会>过滤数据,数据过滤 1.过滤数据 1.1 使用 where 子句 在SEL ECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤. WHERE子句在表名(FR ...

  2. add application window with unknown token XXX Unable to add window;is your activity is running?

    报错: Attempted to add application window with unknown token XXX Unable to add window——token android.o ...

  3. 使用openpyxl创建excel,设置不显示网格线

    最近在学openpyxl,想设置excel不显示网格线,试了好多种方法都不行,最后发现可以通过修改views文件来实现. 文件路径:虚拟目录\Lib\site-packages\openpyxl\wo ...

  4. [WPF][Rubyer] 写一个自己的 UI 库 (二) - Icon

    前言 制作 WPF 的图标包,主要介绍从 iconfont 下载的图标包导入到 WPF 使用: 1. 添加文件 Ruyber 下添加 自定义控件(WPF) Icon.cs.类 IconType.cs. ...

  5. Quartz:基本用法总结

    OpenSymphony所提供的Quartz是任务调度领域享誉盛名的开源框架.Spring提供了集成Quartz的功能,可以让开发人员以更面向Spring的方式创建基于Quartz的任务调度应用.任务 ...

  6. 超详细的阿里字节Spring面试技术点总结(建议收藏)

    前言 Spring作为现在最流行Java开发技术,其内部源码设计非常优秀. Spring这个词对于Java开发者想必不会陌生,可能你每天都在使用Spring,享受着Spring生态提供的服务.现在很多 ...

  7. 小白学PyTorch 动态图与静态图的浅显理解

    文章来自公众号[机器学习炼丹术],回复"炼丹"即可获得海量学习资料哦! 目录 1 动态图的初步推导 2 动态图的叶子节点 3. grad_fn 4 静态图 本章节缕一缕PyTorc ...

  8. 洛谷p1052过河 路径压缩+dp

    洛谷 P1052 过河 思路部分可以看这篇博客 我将在这里对其进行一些解释与补充 首先我们先看题 乍一看 这不是模板题吗 然后开开心心的敲了一个简单dp上去 #include<iostream& ...

  9. Lombok插件有望被Intellij IDEA收编以改善兼容性问题

    1. 前言 最近两个版本的Intellij IDEA没有办法使用lombok插件了,这种问题已经出现了多次,导致胖哥依然使用2020.1的旧版本.其实很多人和我一样也回滚到了旧版本.我一直认为是lom ...

  10. C言语--冒泡排序

    /* 冒泡排序,从小到大 */ include<stdio.h> int main(void) { int i; int t; int p; int val; int a[6]; for( ...