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

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

零  画一个多边形吧!

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

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

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

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

<!-- 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。spa

  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提供的接口我在这里就不赘述了,这篇仅仅只是为了带给你们一个如何在浏览器中绘制渲染图形的概念,以后应该会有针对各个环节的专题,毕竟才刚刚入坑,来日方长!

相关文章
相关标签/搜索