基于上一篇OpenGL的渲染原理,这两周又陆续接触了一些关于WebGL绘图的一些内容,因为刚入门,很多东西又很晦涩,所以特意花了小半天的时间整理了一下,特此记录。

零  画一个多边形吧!

  把一个多边形画上屏幕分几步?

  答:分三步,第一,打开屏幕(1-1.从HTML中获取Canvas对象;1-2.从Canvas拿到WebGL的Context);第二,把数据画好(2-1.编译着色器;2-2.准备数据模型;2-3.顶点缓存VBO的生成和通知);第三,画上去(3-1.发出绘图命令,更新Canvas并渲染)。

   画布和画笔:创建Canvas && 获取WebGL的Context

  在开始WebGL的绘制故事之前,我们得先来认识一下Canvas,因为这玩意是我们之后绘图的基础底板:“Canvas元素创造了一个固定大小画布,并提供了一个或多个渲染上下文,用来绘制和处理要展示的内容”(摘自MDN)。按照定义,我们可以将其理解为渲染任务的中转站,因为最终绘图的输出是要将数据交给屏幕展示的,Canvas只是作为中间暂存待渲染数据的中转站,也可以看作是一个具有仿屏幕像素矩阵数据结构的容器,大概类似于中间缓存一类的概念。那为啥不直接在屏幕上输出画出来呢?之前看到过这样一个解释,缓存的作用在于下一帧没有及时渲染出来的时候(渲染时间超出了人眼的最低感知帧率,最低帧率为24帧),当前帧的数据就能够替代下一帧,以此来保证过程的完整性。

  WebGL的API提供了能够在支持的浏览器中无插件地绘制交互式的2D/3D图像,而WebGL中最重要的对象:渲染上下文WebGLRenderingContext是基于OpenGL ES 2.0的绘图上下文所实现,一般用在HTML5的<canvas>元素内绘图等任务上。而WebGLRenderingContext可以看作是渲染任务的“核心CPU“,所有的操作:从视口剪裁、状态信息、数据缓冲区、着色器的创建和调用、缓冲区绘制等等任务,都由WebGLRenderingContext调配和使用,所以,在任何Web程序开始绘制之前,我们所需要做的第一件事情就是创建一个画布作为数据容器,并将其与WebGL的上下文进行绑定,这样一来,我们既有了画布,又有了画笔,就可以开始绘制我们想要的图形了。

二  调色盘:着色器

  在传统的OpenGL的固定管线中,我们对于渲染过程的控制力度是有限的。从上一篇OpenGL基础里来看,在顶点操作和图元装配、纹理化、片元着色等过程中,我们能控制的只有调用底层硬件厂商提供的接口参数,使用固定的程序去进行上述过程的处理。这种级别的控制非常弱,记得不久前在知乎上看到了一个关于固定管线控制的比喻:“扳开关”,我觉得十分贴切,这个概念有点类似于铁道上的扳道工,火车的前进方向只能在铺设好的轨道上选择,如果没有轨道的地方,火车自然就没法开到。渲染同理,如果对于渲染效果有更高更灵活的要求(或者你无法接受硬件厂商提供的笨重不堪的渲染参数配置,想用更能让人接受的方式去定制属于自己的渲染结果),那么固定管线的处理方式就基本可以不考虑了。

图1 扳开关 

  那懒人就不能拿起画笔在屏幕上作画了吗?幸运的是,我们遇到了可编程渲染管线。它的出现给上面遇到的难题带来的答案,从图2可以看出:可编程渲染管线中出现了两个处理器:顶点着色器vertex shader和片元着色器fragment shader。这两个处理器绕过了传统的顶点操作和图元装配、片元着色等过程,通过自身的可编程特性,分别接手控制了顶点坐标转换、像素颜色计算的工作:

1)在顶点着色器Vertex Shader的处理阶段,顶点数据从GPU显存中读取出来,顶点着色器Vertex Shader可以对每个顶点进行模型视图变换、投影变换等顶点处理工作,替代了固定管线中的顶点处理管线;

<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec4 a_color; uniform mat3 u_matrix; varying vec4 v_color; void main() {
gl_Position = vec4((vec3(a_position, 1)).xy, 0, 1); v_color = a_color;
}
</script>

2)顶点着色器处理完之后,管线会对各个顶点进行光栅化处理。由于顶点着色器的计算次数由顶点的数量决定,即n个顶点对应着n个顶点像素数据,但在一个由若干个像素组成的图元中,非顶点像素的颜色该如何确定呢?此时就需要给大家介绍一个新对象:数据类型Varyings,从下面一段简短的顶点着色器DEMO中可以看出来,我们在顶点着色器中定义了好几种数据类型,有attribute,uniform以及varying。但来得早不如来得巧,我们先来认识一下Varying。

  Varying是一个变量,作为一个信使连接着顶点着色器Vertex Shader和片元着色器Fragment Shader。在一般情况下,顶点着色器Vertex Shader会计算出每个顶点的颜色、坐标等值,并用Varying变量来存储这些值。回到刚才提出的问题,非顶点的像素如何确定自己的值呢?这就需要片元着色器来理解这个信使了,好在片元着色器和顶点着色器之间有个光栅器牵线搭桥,当顶点着色器传来Varying类型的顶点值时,光栅器会指定一种插值模式,指导片元着色器按像素逐个进行渲染绘制。

3)片元着色器Fragment Shader在光栅化工具(这个工具我也没有仔细研究,暂时当作一个黑盒吧)的指导下进行工作(片元在上一篇OpenGL基础中已经提到过了,所谓片元即指光栅化后的图元)。片元着色器fs的主要工作是为当前光栅化的像素提供颜色值,屏幕中的每一个像素都需要调用一次片元着色器Fragment Shader,每次调用都会从一个特殊的全局变量gl_FragColor中获取颜色信息。

<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment"> precision mediump float; varying vec4 v_color; void main() {
gl_FragColor = v_color;
}
</script>

图2 可编程渲染管线

三 轮廓骨架线

  在前面的步骤大概能够初探一二之后,下一步就是在显存中创建顶点对象VBO了:所谓VBO,顶点缓冲区对象( Vertex buffer object )这个概念来自于OpenGL,其概念定义为一个将顶点Vertex的各类属性信息(如vertex坐标,vertex法向量以及颜色等)存储在显存的一块专用的缓存区中,在执行渲染命令时,可直接从显存中取出VBO。由于整个过程都是在GPU中进行,不同于之前传统的绘制方式(CPU命令GPU执行绘制动作,反复传输大量的顶点数据到GPU中,渲染速度较慢),所以一般将VBO视为一个能够改善数据传输效率的对象。

  那么VBO是如何在WebGL中应用的呢?我们通常第一步通过createBuffer方法创建一个缓存对象VBO,通过图3的MDN中的定义可以看出,返回值VBO可以是颜色或者顶点坐标值的缓冲对象。此时只需调用gl.bufferData向gl_ARRAY_BUFFER中写入数据,再使用gl. bindBuffer方法就可以把buffer数据与gl上下文中的ARRAY_BUFFER关联起来,也就是把顶点数据成功地写入了GPU显存中。

图3 WebGLBuffer

    function main(){
      ......
//Create a buffer & bind buffer
//创建buffer数据,并绑定到gl的上下文中
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
//set Geometry
//填充buffer
setGeometry(gl);
      ......
}
function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-150, -100,
150, -100,
-150, 100,
150, -100,
-150, 100,
150, 100]),
gl.STATIC_DRAW);
}

  进行到上述阶段为止,渲染之前的初始化工作基本完成:画布空间已创建  -->  将画布Canvas与WebGL环境绑定 ---> 创建并指定了相关自定义着色器填充顶点数据 ---> 创建顶点缓存VBO。一切准备就绪,千军万马一般的屏幕像素们都在等着一个明确渲染绘图指令,只待gl令旗一挥,在GPU的指挥下,千万个屏幕像素将会按照规定的位置和颜色,以迅雷不及掩耳之势一蹴而就,以你所规定的样式完美地呈现在你的屏幕上。

四 画!

  一切准备都已完成,所有任务执行资源就位后,我们再来一起看看最后一步的绘图渲染命令是如何发出的。

  简单来看,我们可以把绘图渲染部分分为以下三部分:

    1)画布清洁

    2)指定环境

    3)执行着色程序

//Rendering code: 渲染代码
function drawScene() {        //-----------------------画布清洁---------------------
webglUtils.resizeCanvasToDisplaySize(gl.canvas); //covert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); //clear canvas
gl.clear(gl.COLOR_BUFFER_BIT); //direct to our program
gl.useProgram(program);
//------------------------指定环境-------------------------------
//打开属性attribute开关
gl.enableVertexAttribArray(positionAttributeLocation); //对当前状态进行绑定 : 绑定已经完成填充点数据的buffer数据块positionBuffer
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 步长(byte),每个顶点数据所占的字节数:0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer //vertexAttribPointer:顶点属性指路牌
//告诉显卡从当前绑定的缓冲区(drawScene方法中的bindBuffer)中读取顶点数据vertex data
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
//------------------------------执行着色程序-------------------------------------// Draw the geometry.
var primitiveType = gl.TRIANGLES; //绘制图元模式
var offset = 0;//从缓冲区开始读取数据的首地址偏移first下标
var count = 6;//绘制顶点数据的个数,即Shader代码的运行次数
gl.drawArrays(primitiveType, offset, count);
}
//==================================================================================

  具体的WebGLRenderingContext提供的接口我在这里就不赘述了,这篇仅仅只是为了带给大家一个如何在浏览器中绘制渲染图形的概念,之后应该会有针对各个环节的专题,毕竟才刚刚入坑,来日方长!

【GISer&&Painter】GISer的更多相关文章

  1. 【GISER && Painter】矢量切片(Vector tile)

    说明:本月的主要工作都是围绕制作矢量切片这一个核心问题进行的,所以2月的主题就以这个问题为主,目前分支出来的一些内容主要包括了TMS(Tile map service),OpenLayers3中的Pr ...

  2. 【GISER && Painter】Chapter00:OpenGL原理学习笔记

    说明:简单了解一下OpenGL的工作原理,初步认识计算机对于图形渲染的底层设计与实现,第一次接触,也没学过C艹,欢迎各位批评指正. 一  什么是OpenGL? OpenGL是一个开放标准(specif ...

  3. 【GISER&&Painter】Chapter02:WebGL中的模型视图变换

    上一节我们提到了如何在一张画布上画一个简单几何图形,通过创建画布,获取WebGLRendering上下文,创建一个简单的着色器,然后将一些顶点数据绑定到gl的Buffer中,最后通过绑定buffer数 ...

  4. 【GISER && Painter】矢量切片(Vector tile)番外一:Proj4js

    说明:番外篇是对正篇矢量切片(Vector tile)中提到的一些值得继续延伸的关注点继续进行探索和学习,所涉及的内容以解决实际问题为主要导向. 一.新的需求? 在完成了矢量切片的工作后,新的需求出现 ...

  5. 【GISER&&Painter】Chapter01:WebGL渲染初体验

    基于上一篇OpenGL的渲染原理,这两周又陆续接触了一些关于WebGL绘图的一些内容,因为刚入门,很多东西又很晦涩,所以特意花了小半天的时间整理了一下,特此记录. 零  画一个多边形吧! 把一个多边形 ...

  6. 【HAPPY FOREST】用Unreal Engine4绘制实时CG影像

    用Unreal Engine绘制实时CG影像 近年来,对实时CG的关心热度越来越高,但要想弥补与预渲染方式的差异并不是那么容易.这里就有影像业界的先锋进行挑战的MARZA ANIMATION PLAN ...

  7. 【ABAP系列】【第五篇】SAP ABAP7.50 之用户接口

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列][第五篇]SAP ABAP7.5 ...

  8. 【AR实验室】mulberryAR : ORBSLAM2+VVSION

    本文转载请注明出处 —— polobymulberry-博客园 0x00 - 前言 mulberryAR是我业余时间弄的一个AR引擎,目前主要支持单目视觉SLAM+3D渲染,并且支持iOS端,但是该引 ...

  9. 【.net 深呼吸】细说CodeDom(1):结构大观

    CodeDom 是啥东东?Html Dom听过吧,XML Dom听过吧.DOM一般可翻译为 文档对象模型,那 Code + DOM呢,自然是指代码文档模型了.如果你从来没接触过 CodeDom,你大概 ...

随机推荐

  1. java 随记

    后台开发的过程中积累的关于java的杂记 架构 SSH框架 为什么要分层? 因为分层使代码变得清晰,容易写也容易阅读,更重要的是让代码扩展性更好,层与层之间的改动不会互相影响 各层的分工 dao--与 ...

  2. IdentityServer4 登录使用数据库

    业务场景: IdentityServer4 默认使用TestUser和UserStore,需要模拟和加载所有的用户数据,正式环境肯定不能这样实现,我们想从自己的数据库中读取用户信息,另外,因为 Ide ...

  3. mysql别名的使用

    在项目中遇到别名的问题,抽时间整理了一下 在sql中,合理的使用别名可以让sql更容易写并且提高可读性.别名使用 as 来表示,可以分为表别名和列别名. 别名应该是先定义后使用才对,所以首先要了解sq ...

  4. seajs源码

    /*** Sea.js 3.0.0 | seajs.org/LICENSE.md 中文注释由 李祥威 添加,为个人对细节的理解,官方解释很详细的地方就不说了 难免有错漏,联系我: chuangweil ...

  5. 【Kafka源码】broker被选为controller之后的连锁反应

    [TOC] 今天我们主要分析下broker被选为controller之后,主要干了什么.门面代码先列出来: def onControllerFailover() { if (isRunning) { ...

  6. [Bayesian] “我是bayesian我怕谁”系列 - Gaussian Process

    科班出身,贝叶斯护体,正本清源,故拿”九阳神功“自比,而非邪气十足的”九阴真经“: 现在看来,此前的八层功力都为这第九层作基础: 本系列第九篇,助/祝你早日hold住神功第九重,加入血统纯正的人工智能 ...

  7. java的linux命令

    1.查找文件find / -name filename.txt 根据名称查找/目录下的filename.txt文件.find . -name “*.xml” 递归查找所有的xml文件2.查看一个程序是 ...

  8. Oracle的Recyclebin策略

    1.从oracle10g开始删除数据库表的时候并不是真正删除,而是放到了recyclebin中,这个过程类似 windows里面删除的文件会被临时放到回收站中. 2.删除的表系统会自动给他重命名就是你 ...

  9. 通过hibernate封装数据库持久化过程回顾泛型/继承/实现等概念

    前言 在开发过程中,我们不难发现,客户的需求以及产品的定位对开发内容的走向有很大的决策作用,而这些往往需要在一开始就尽可能考虑周全和设计完善.为什么说是尽可能,因为我们都知道,需求这种东西,一言难尽. ...

  10. netty 入门二 (传输bytebuf 或者pojo)

    基于流的数据传输:在基于流的传输(如TCP / IP)中,接收的数据被存储到套接字接收缓冲器中. 不幸的是,基于流的传输的缓冲区不是数据包的队列,而是字节队列. 这意味着,即使您将两个消息作为两个独立 ...