WebGL入门教程3 - Canvas、Context、API和绘制一个矩形

techbrood 发表于 2016-05-26 00:10:54

标签: webgl, canvas, 教程

- +

教程2中,我们已经讲述了计算机图形处理硬件结构和流水线。

在本文中,我们将开始讲述WebGL的具体应用程序编程接口(API)。

WebGL应用程序编程步骤分为以下几步:

  1. 创建一个canvas元素

  2. 从canvas中获取webgl渲染上下文

  3. 初始化视窗(viewport)

  4. 创建缓存来容纳需要渲染的模型数据(通常是顶点数据)

  5. 创建顶点和片段着色器来实现绘图算法

  6. 初始化着色器并绘制。

Canvas和渲染上下文(Context)

所有的WebGL渲染都是在一个上下文(context)中完成的,context是一个JavaScript DOM对象,提供了完整的WebGL API。这和canvas 2D绘图上下文类似。下面的代码演示了如何从canvas DOM元素中获取WebGL context:

function initWebGL(canvas) {

    var gl;
    try
    {
        gl = canvas.getContext("webgl");
    }
    catch (e)
    {
        var msg = "Error creating WebGL Context!: " + e.toString();
        alert(msg);
        throw Error(msg);
    }

    return gl;
}

上面代码中的try/catch用来处理不支持webgl的浏览器,提示一个用户友好的错误信息。

视窗(Viewport)

接下来,我们需要设定我们的绘图区域,在WebGL中,这被称为Viewport,可以通过context的viewport()方法来获得:

function initViewport(gl, canvas)
{
    gl.viewport(0, 0, canvas.width, canvas.height);
}

上面的gl对象是由initWebGL方法所获取的渲染上下文。

缓存,数组缓存和类型化数组(Typed Arrays)

WebGL的绘制对象是通过原型(primitives)来定义的,比如三角形数组,点和线段,在教程2中我们对此有过详细描述。Primitives使用数组数据,称之为缓存(buffers),定义了顶点的位置信息。下面的例子演示如何创建一个单元正方形 (1 × 1)的顶点数据,通过一个JavaScript对象返回结果,包含顶点缓存数据,三个浮点数来保存x, y和z坐标,要绘制的顶点数目,用来绘制矩形的原型类型,在这里是去除重叠线段的三角形(GL_TRIANGLE_STRIP, 在教程2也有过描述)。

// 创建一个矩形的顶点数据用户绘制
function createSquare(gl) {
    var vertexBuffer;
    vertexBuffer = gl.createBuffer();//创建缓存
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);//设置为gl当前数组缓存
    var verts = [
         .5,  .5,  0.0,
         -.5,  .5,  0.0,
         .5, -.5,  0.0,
         -.5, -.5,  0.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts),
        gl.STATIC_DRAW);
    var square = {buffer:vertexBuffer, vertSize:3, nVerts:4,
        primtype:gl.TRIANGLE_STRIP};
    return square;
}

上面使用了一个Float32Array类型,这是一个类型化数组,是为了WebGL而引入的新JS数据类型,和普通数组的访问方法类似,但是要快得多并且占用更少的内存。(关于类型化数组的最新规格,请阅读http://www.khronos.org/registry/typedarray/specs/latest/)。我们知道JavaScript是弱类型语言,你使用一个变量时,不需要定义其数据类型,但C语言则相反是一个强类型语言,如果未定义类型,编译时就会报错,类似的,GLSL中的变量都有限定符(qualifier)、类型(type)和精度(precision),从JavaScript传递给GL的变量也必须具备指定的类型。对于数组,我们基本上就是使用Float32Array,暂时不用考虑浮点精度。

限定符(Qualifiers)

  1. const – 用于声明编译期的常量,对于整个应用程序是恒定不变的

  2. attribute – 可能随顶点而变的全局变量。从WebGL应用程序传递给顶点着色器。该限定符仅能用于顶点着色器。对于着色器而言,是只读变量。

  3. uniform – 可能随图元(Primitive)而变的全局变量,从WebGL应用程序传递给着色器。该限定符可被用于顶点和片段着色器。对于着色器而言,是只读变量。

  4. varying一般而言,这是一个你从顶点着色器传递给片段着色器的变量。它是“变化的”因为片段的生成是通过在顶点之间执行线性插值算法来得到的,对于顶点着色器,该变量可写,而对于片段着色器,该变量只读。开发人员可以控制每个片段,这是非常强大的功能,可以轻易实现渐变效果。

类型(Types)

矢量(Vector):

如果你不熟悉几何向量或元组,你可以想他们作为一个固定大小的数组,每个数组元素代表某个维度上的度量。通常,我们用矢量来表示空间坐标(x,y,z)或颜色(r,g,b,a)。vec4(1,1,1,1)构造一个4维的向量。uniform vec3 u_myuniform; 声明一个三维恒量。片段的颜色始终是一个vec4(red,green,blue,alpha),元素取值范围在0 - 1之间。

矩阵变换(Matrices)

我们在教程2中讲述顶点着色器的时候,提到了多个矩阵变换,模型变换、视图变换和投影变换。

首先我们需要一个矩阵来定义正方形在3D坐标系统中相对于camera的位置,这个就是模型加视图变换(ModelView matrix),在下面的例子中,我们把正方形放到远离相机3.333单元的地方(z=-3.333)。还有一个矩阵定义投影变换,用来把3D相机空间的对象映射到2D的Viewport上。本例中,我们定义45°的观察角度:

function initMatrices()
{
    // The transform matrix for the square - translate back in Z
    // for the camera
    modelViewMatrix = new Float32Array(
        [1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, −3.333, 1]);

    // The projection matrix (for a 45 degree field of view)
    projectionMatrix = new Float32Array(
        [2.41421, 0, 0, 0,
        0, 2.41421, 0, 0,
        0, 0, −1.002002, −1,
        0, 0, −0.2002002, 0]);

}

第2个矩阵难于阅读和编写,实际上不需要来自己算矩阵的值,我们可以利用已有的JS矩阵计算开发库((https://github.com/toji/gl-matrix))来简化编程。

坐标系统(Coordinate System)

WebGL中的坐标系统是一个Clipspace(裁剪空间/投影坐标),Clipspace的坐标范围从-1到1,坐标中心点位于 (0,0)。注意这和显示屏幕的坐标系不同,显示屏幕的坐标原点位于窗口的左上角,且坐标值使用实际的像素单位。

着色器(Shader)

如前所述,Shader是像C一样的系统级语言,用来确定如何把3D模型绘制为显示屏幕上的像素。WebGL要求开发者为每一个对象提供着色器,但Shader可以用于多个对象,因此实际上我们只需要创建共享的Shader,传入不同的参数来绘制不同对象。顶点着色负责将对象的坐标转换为二维空间显示,片段着色负责根据输入(如颜色、纹理、照明和材料值)生成变换后的顶点每个像素的最终颜色输出。

顶点着色器最主要的变量是gl_Position,这是一个vec4类型的变量,用来表示顶点(vertex)在3D裁剪空间中的位置:(x,y,z,w),那么为什么是vec4,而不是vec3呢,x/y/z很清楚是3D坐标,最后的w用来表示偏差属性,在大多数情况下并不被使用,我们可以暂时忽略它。

片段着色器用来处理像素着色,片段(fragment)就是一种分辨率无关的3D空间像素,其最主要的变量是gl_FragColor,也使用一个vec4类型的变量来表示:(Red,Green,Blue,Alpha),也就是我们常用的RGBA颜色值。

这两个着色器的代码如下所示:

<script id="vert-shader" type="x-shader/x-vertex">
  // 获取当前顶点位置
    attribute vec3 a_position;
    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;

  void main() {
   // 返回转换过的坐标
   gl_Position = projectionMatrix * modelViewMatrix * vec4(a_position, 1.0);
  }
</script>

<script id="frag-shader" type="x-shader/x-fragment">
  precision mediump float;

  void main() {
    // 设置像素颜色
    gl_FragColor = vec4(0, 1, 0, 1); // r=0%, g=100%, b=0%, a=100%, 表示绿色
  }
</script>

你可以看到着色器代码为C语言风格的代码,main函数是程序主入口。

我们现在需要把这两段GLSL代码加载到WebGL的着色器程序中:

function createShader(gl, source, type) {
    var shader = gl.createShader(type);
    source = document.getElementById(source).text;
    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    return shader;
}

//根据ID从DOM中获取GLSL代码并分别创建顶点着色器和片段着色器(并编译代码)
var vertexShader = createShader(gl, 'vert-shader', gl.VERTEX_SHADER);
var fragShader = createShader(gl, 'frag-shader', gl.FRAGMENT_SHADER);

//创建着色器程序并加载着色器代码、链接并设置为正在使用状态
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragShader);

gl.linkProgram(program); 
gl.useProgram(program);

记住GLSL的代码和JavaScript代码是不同的,GLSL是编译语言,需要编译(Compile)和链接(Link)后才能在GPU上执行,而JS是解释型语言,不需要编译。

绘制图形

到此,我们已经准备好了WebGL的绘制上下文、绘制对象(矩形数据)以及绘制程序(着色器),接下来只要把数据和程序通过缓存连接起来,程序从缓存中依次读取顶点数据并变换、着色,完成绘制。

function draw(gl, obj) {

    // 清除背景色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 连接着色器参数:顶点位置和投影/模型视图矩阵
    // 查找属性在顶点代码中的地方,返回一个索引,并启用该索引处的顶点属性数组
    var positionLocation = gl.getAttribLocation(shaderProgram, "vertexPos");
    gl.enableVertexAttribArray(positionLocation);
    // 把位置属性数据和缓存绑定
    gl.vertexAttribPointer(shaderVertexPositionAttribute, obj.vertSize, gl.FLOAT, false, 0, 0);
    // 把我们创建的变换矩阵(数组数据)赋值给着色器的uniform变量
    gl.uniformMatrix4fv(shaderProjectionMatrixUniform, false, projectionMatrix);
    gl.uniformMatrix4fv(shaderModelViewMatrixUniform, false, modelViewMatrix);

    // draw it  - drawArrays(Mode,first,count)
    // 从索引0开始一直到nVerts逐个绘制顶点/图元
    gl.drawArrays(obj.primtype, 0, obj.nVerts);
}

这样,我们总算完成了第一个WebGL应用程序,画了一个远离我们的长方形!

靠!看起来用Canvas 2D几行代码就可以完成的任务。

不过WebGL显然不是用来画2D图形的,WebGL强大的地方在于3D渲染。

而且为了便于理解基本概念,我们并没有使用一些WebGL的JS库,如Three.js。

我们后面会介绍如何使用Three.js来简化编程,并结合一些经典案例来进行剖析,才能真正体会到WebGL惊人的能量。

本例代码,你可以自己在线试试看

possitive(20) views29196 comments0

发送私信

最新评论

请先 登录 再评论.
相关文章
  • 微信公众号在线生成二维码带参数怎么搞?

    带参数二维码是微信公众号渠道二维码的一种实现
    微信的带参数二维码有两种,一种是临时二维码,一种是永久二维码,但是永久二维码的生成是有个数限制的,微...

  • 常见面试题JS语言中四种函数调用方式实例讲解

    JS的语言世界中函数(function)是一等公民,函数的调用有多种方法。普通调用这个是最常见和直接的方式:function

  • 前端开发框架技术选型:Angular2 VS React VS jQuery

    Angular和React是主流的2个前端开发框架,但是严格来说两者并非对等的概念。Angular是一个基于MVC(或者MVVM)的框架,包含model(模型)/view(视图)/controll...

  • 深入理解JS和CSS3动画性能问题和技术选择

    本文对比了JS及其框架和CSS3的动画性能,并深入剖析了其内在原因。技术结论大致如下:1. jQuery出于设计原因,在动画性能上表现最差2. CSS3由于把动画逻辑推给了...

  • 使用HTML5 FileReader和Canvas压缩用户上传的图片

    手机用户拍的照片通常会有2M以上,这对服务器带宽产生较大压力。因此在某些应用下(对图片要求不那么高)我们可以在客户端来压缩图片,然后再提交给服务器。总体...

  • 纹理基础知识和过滤模式详解

    1、 为什么在纹理采样时需要texture filter(纹理过滤)。
    我们的纹理是要贴到三维图形表面的,而三维图形上的pixel中心和纹理上的texel中心并不一至(pixe...

  • Three.js入门教程4 - 创建粒子系统动画

    嗨,又见面了。这么说我们已经开始学习Three.js了,如果你还没有看过之前三篇教程,建议你先读完。如果你已经读完前面的教程了,你可能会想做一些关于粒子的东西。让我们直面这个话题吧,每个人都爱粒子效果。不管你是否知道,你可以很轻易地创建它们。

  • Three.js入门教程2 - 着色器(下)

    这是WebGL着色器教程的后半部分,如果你没看过前一篇,阅读这一篇教程可能会使你感到困惑,建议你翻阅前面的教程。

  • S3TC(S3 Texture Compression)纹理压缩格式详解

    使用S3TC格式存储的压缩纹理是以4X4的纹理单元块(texel blocks)为基本单位存储的,每纹理单元块(texel blocks)有64bit或者128bit的纹理单元数据(texel data)。这...

  • CSS3图片混合(Blend)效果及其参考计算公式一览表

    在Photoshop软件中,混合是将两个图层的色彩值进行合成,从而创造出大量的效果。在这些效果的背后实际是一些简单的数学公式在起作用。下面所介绍的公式仅适用于R...

  • 如何使用WebGL实现空气高温热变形动画特效

    我们在炎炎夏日,或者在火堆旁,经常会观察到热源周围空气的不稳定波动现象。本文将讲解如何通过WebGL来实现这个特效。该效果可用于热变形、波浪、水面波光等场...

  • 使用纯CSS3实现一个3D旋转的书本

    有一些前沿的电商网站已经开始使用3D模型来展示商品并支持在线定制,而其中图书的展示是最为简单的一种,无需复杂的建模过程,使用图片和CSS3的一些变换即可实现...

  • 更多...