基于上一篇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】Chapter01:WebGL渲染初体验的更多相关文章

  1. (4)Flask项目模板渲染初体验

    一.准备静态资源 将项目使用到的静态资源拷贝到static目录 二.创建前台首页html 创建templates/home/home.html页面,内容包含导航和底部版权两部分,中间内容区域为模板标签 ...

  2. Handlebars的基本用法 Handlebars.js使用介绍 http://handlebarsjs.com/ Handlebars.js 模板引擎 javascript/jquery模板引擎——Handlebars初体验 handlebars.js 入门(1) 作为一名前端的你,必须掌握的模板引擎:Handlebars 前端数据模板handlebars与jquery整

    Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...

  3. 【腾讯Bugly干货分享】基于 Webpack & Vue & Vue-Router 的 SPA 初体验

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d13a57132ff21c38110186 导语 最近这几年的前端圈子,由于 ...

  4. 基于 Webpack & Vue & Vue-Router 的 SPA 初体验

    基于 Webpack & Vue & Vue-Router 的 SPA 初体验 本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com ...

  5. YII学习,初体验 ,对YII的一些理解.

    先说点没用的: 不会选择,选择后不坚持,不断的选择.这是人生中的一个死循环,前两一直迷茫.觉得自己前进方向很不明朗.想去学的东西有很多.想学好YII,想学PYTHON 想学学hadoop什么的,又想研 ...

  6. vue.js2.0 自定义组件初体验

    理解 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能.在有些情况 ...

  7. flutter初体验

    flutter初体验 和flutter斗争了两个周末,基本弄清楚了这个玩意的布局和一些常用组件了. 在flutter里面,所有东西都是组件Widget.我们像拼接积木一样拼接Widget,拼接的关键词 ...

  8. django初体验 学习笔记

    django环境搭建     1.安装Python     2.ipython         sudo apt-get install ipython         sudo pip instal ...

  9. "xaml+cs"桌面客户端跨平台初体验

    "Xaml+C#"桌面客户端跨平台初体验 前言   随着 .Net 5的到来,微软在 .Net 跨平台路上又开始了一个更高的起点.回顾.Net Core近几年的成果,可谓是让.Ne ...

随机推荐

  1. Fedora下rstudio-server安装

    安装 sudo yum install openssl sudo yum install mysql-server wget https://download2.rstudio.org/rstudio ...

  2. window下线程同步之(Event Objects(事件))

    Event 方式是最具弹性的同步机制,因为他的状态完全由你去决定,不会像 Mutex 和 Semaphores 的状态会由类似:WaitForSingleObject 一类的函数的调用而改变,所以你可 ...

  3. git用法大全

    转载自实验楼,之前有更新过两篇git的文章,毕竟内容太少,而git还有很多更丰富的技能,在实验楼上有一系列全的教程,这里做一下备案.需要时查阅. Git 实战教程 目录 一.实验说明 二.git的初始 ...

  4. zookeeper的简单使用

    前言 最近项目中要使用基于zookeeper的集中配置管理系统,而对于zookeeper仅在当初使用阿里开源分布式服务调用框架dubbo时简单的了解一下.本 文的主要目的,调用zkclient (ma ...

  5. css 字符图标浏览器自带

    项目中用到的一些特殊字符和图标 html代码 <div class="cross"></div> css代码 .cross{ width: 20px; he ...

  6. 【LOJ】 #2132. 「NOI2015」荷马史诗

    题解 k叉哈夫曼树,但是没有了二叉那样的最后一定能合并成一个树根的优秀性质,我们就不断模拟操作看看到了哪一步能用的节点数< k,然后先拿这些节点数合并起来 然后就可以k个k个合并了,大小一样先拿 ...

  7. Django+Nginx+uwsgi搭建自己的博客(四)

    由于在上篇博文中仍然介绍了相当多的后端部分,导致原定于上篇介绍的前端部分“跳票”到了这篇.在此篇博文中,我将会介绍Users App和主页的前端部分,从而形成我们博客的一个雏形. 在前端部分,我们主要 ...

  8. Docker应用系列(三)| 构建Consul集群

    本示例基于Centos 7,在阿里云的三台机器上部署consul集群,假设目前使用的账号为release,拥有sudo权限. 由于Docker官方镜像下载较慢,可以开启阿里云的Docker镜像下载加速 ...

  9. I/O 多路复用之select、poll、epoll详解

    select,poll,epoll都是IO多路复用的机制.I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但s ...

  10. 【codeforces.com/gym/100240 J】

    http://codeforces.com/gym/100240 J [分析] 这题我搞了好久才搞出样例的11.76....[期望没学好 然后好不容易弄成分数形式.然后我‘+’没打..[于是爆0... ...