使用 WebGL 创建 2D 内容

Posted H5技术社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 WebGL 创建 2D 内容相关的知识,希望对你有一定的参考价值。

一旦创建WEBGL上下文成功,你就可以在这个上下文里渲染画图了。而对我们而言最简单的事,莫过于绘制一个没有纹理的2D图形了。那就让我们从画出一个正方形开始吧。

渲染场景

在开始前,我们首先需要明确最重要的一点,就是虽然我们的例子只是画一个二维物体,但我们仍然是在把它画在一个三维空间里。所以,我们依然需要创建着色器,通过它来渲染我们的简单场景并画出我们的物体。往下,我们将展示正方形是怎样一步步被画出来的。

初始化着色器

WebGL着色器使用 OpenGL ES Shading Language. 为了更方便地处理和更新内容,事实上我们可以将着色器代码写在html文档上,而不是把所有代码都写在 javascript里。下面我们看一下 initShaders() 函数是怎么实现的:

  1. function initShaders() {

  2.   var fragmentShader = getShader(gl, "shader-fs");

  3.   var vertexShader = getShader(gl, "shader-vs");

  4.   

  5.   // 创建着色器

  6.   

  7.   shaderProgram = gl.createProgram();

  8.   gl.attachShader(shaderProgram, vertexShader);

  9.   gl.attachShader(shaderProgram, fragmentShader);

  10.   gl.linkProgram(shaderProgram);

  11.   

  12.   // 如果创建着色器失败

  13.   

  14.   if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {

  15.     alert("Unable to initialize the shader program.");

  16.   }

  17.   

  18.   gl.useProgram(shaderProgram);

  19.   

  20.   vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");

  21.   gl.enableVertexAttribArray(vertexPositionAttribute);

  22. }

复制代码

这段程序中加载了两个着色器。首先,片段着色器是从 ID 为 "shader-fs" 的 script 元素中加载的。然后,顶点着色器是从ID为"shader-vs"的 script 元素中加载的。我们将在下一节看到 getShader() 函数的细节,实现将着色器程序从DOM元素加载到着色器。

接着我们调用 WebGL 对象的 createProgram()函数来创建着色器,并将WebGL 对象与两个着色器关联起来,然后链接着色器程序。完成以上步骤后,gl 对象的 LINK_STATUS 参数被检查以确定着色器程序是否成功连接。如果成功的话,我们激活新着色器程序。

从DOM中加载着色器

如下,getShader() 函数从DOM元素里抓取着色器程序,并返回一个编译好的着色器程序,但是,如果它加载失败或编译失败的话,将返回 null 。

  1. function getShader(gl, id) {

  2.   var shaderScript, theSource, currentChild, shader;

  3.   

  4.   shaderScript = document.getElementById(id);

  5.   

  6.   if (!shaderScript) {

  7.     return null;

  8.   }

  9.   

  10.   theSource = "";

  11.   currentChild = shaderScript.firstChild;

  12.   

  13.   while(currentChild) {

  14.     if (currentChild.nodeType == currentChild.TEXT_NODE) {

  15.       theSource += currentChild.textContent;

  16.     }

  17.     

  18.     currentChild = currentChild.nextSibling;

  19.   }

复制代码

一旦找到指定ID的元素,其文本内容将被读取保存到变量 theSource 。

  1. if (shaderScript.type == "x-shader/x-fragment") {

  2.     shader = gl.createShader(gl.FRAGMENT_SHADER);

  3.   } else if (shaderScript.type == "x-shader/x-vertex") {

  4.     shader = gl.createShader(gl.VERTEX_SHADER);

  5.   } else {

  6.      // Unknown shader type

  7.      return null;

  8.   }

复制代码

一旦读取到着色器源码,我们就根据着色器对象的 MIME 属性来判断它是顶点着色器(MIME type "x-shader/x-vertex"),还是片段着色(MIME type "x-shader-x-fragment"),从而创建相应的着色器。

  1. gl.shaderSource(shader, theSource);

  2.     

  3.   // Compile the shader program

  4.   gl.compileShader(shader);  

  5.     

  6.   // See if it compiled successfully

  7.   if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {  

  8.       alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));  

  9.       return null;  

  10.   }

  11.     

  12.   return shader;

  13. }

复制代码

最后,源码将传到着色器上并编译。如果在编译途中发生错误,我们会显示一个警告并返回 null,否则,就返回一个新的编译好的着色器。

着色器

接着,我们需要将着色器程序代码加入到 HTML 文档。但是,着色器的语法和具体怎样工作已经超出本篇文章讨论的范围。

片段着色器

在 WebGL 多边形中的每一个像素都叫一个片段。这个片段着色器的工作就是建立每个像素的色彩。在这个案例中,我们会简单地给每个像素填上白色。

gl_FragColor 是一个 GL 内置的特殊变量,用于片段的色彩填充。如下所示,设定它的值就是设定每个像素的颜色。

  1. <script id="shader-fs" type="x-shader/x-fragment">

  2.   void main(void) {

  3.     gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

  4.   }

  5. </script>

复制代码

顶点着色器

顶点着色器定义了每个顶点的位置和形状。

  1. <script id="shader-vs" type="x-shader/x-vertex">

  2.   attribute vec3 aVertexPosition;


  3.   uniform mat4 uMVMatrix;

  4.   uniform mat4 uPMatrix;

  5.   

  6.   void main(void) {

  7.     gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);

  8.   }

  9. </script>

复制代码

创建对象

在画正方形前,我们需要创建一个缓冲器来存储它的顶点。我们会用到名字为 initBuffers() 的函数。当我们了解到更多WebGL 的高级概念时,这段代码会将有更多参数,变得更加复杂,并且用来创建更多的三维物体。

  1. var horizAspect = 480.0/640.0;


  2. function initBuffers() {

  3.   squareVerticesBuffer = gl.createBuffer();

  4.   gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);

  5.   

  6.   var vertices = [

  7.     1.0,  1.0,  0.0,

  8.     -1.0, 1.0,  0.0,

  9.     1.0,  -1.0, 0.0,

  10.     -1.0, -1.0, 0.0

  11.   ];

  12.   

  13.   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  14. }

复制代码

这段代码简单给出了绘画场景的本质。首先,它调用 gl 的成员函数 createBuffer() 得到了缓冲对象并存储在顶点缓冲器。然后调用 bindBuffer() 函数绑定上下文。

当上一步完成,我们创建一个Javascript 数组去记录每一个正方体的每一个顶点。然后将其转化为 WebGL 浮点型类型的数组,并将其传到 gl 对象的  bufferData() 方法来建立对象的顶点。

绘制场景

当着色器和物体都创建好后,我们可以开始渲染这个场景了。因为我们这个例子不会产生动画,所以 drawScene() 方法非常简单。它还使用了几个工具函数,稍后我们会介绍。

  1. function drawScene() {

  2.   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  3.   

  4.   perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0);

  5.   

  6.   loadIdentity();

  7.   mvTranslate([-0.0, 0.0, -6.0]);

  8.   

  9.   gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);

  10.   gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);

  11.   setMatrixUniforms();

  12.   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

  13. }

复制代码

第一步,用背景色擦除上下文,接着建立摄像机透视矩阵。设置45度的视图角度,并且宽高比设为 640/480(画布尺寸)。 指定在摄像机距离0.1到100单位长度的范围内,物体可见。

接着加载特定位置,并把正方形放在距离摄像机6个单位的的位置。然后,我们绑定正方形的顶点缓冲到上下文,并配置好,再通过调用 drawArrays() 方法来画出对象。

如果你的浏览器支持WebGL的话,可以点击这里看看DEMO

矩阵通用计算

矩阵计算是一个很复杂的运算。 没人会想去自己写完所有代码来处理这些运算。幸运的是,这里有 Sylvester, 一个使用方便的 JavaScript处理向量和矩阵运算的库。

在这个例子中使用的 glUtils.js 文件同时也被使用到很多的其它WebGL例子中去了。似乎没有人完全清楚它的来源,但是它简化了对Sylvester 的使用,甚至添加了一些生成特殊矩阵和输出显示它们的方法函数。

另外,这个例子给出了几个有用的代码,它结合这些库来解决特定的任务。准确来说它们已经超出了这个例子讨论的范围,但是这里有一些非常好的有关于矩阵运算的在线文档。可以参考看See also 栏目找到相关资料。

  1. function loadIdentity() {

  2.   mvMatrix = Matrix.I(4);

  3. }


  4. function multMatrix(m) {

  5.   mvMatrix = mvMatrix.x(m);

  6. }


  7. function mvTranslate(v) {

  8.   multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4());

  9. }


  10. function setMatrixUniforms() {

  11.   var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");

  12.   gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));


  13.   var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");

  14.   gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));

  15. }

复制代码



以上是关于使用 WebGL 创建 2D 内容的主要内容,如果未能解决你的问题,请参考以下文章

通过HTML5和WebGL来制作2D游戏

Three.js/WebGL 和 2D Canvas -- 将 getImageData() 数组传递给 Three.DataTexture()

教你用webgl快速创建一个小世界

教你用 webgl 快速创建一个小世界

WebGL-从2D开始

如何使用 WebGL 异步加载图像、创建纹理、渲染和保存图像?