【笔记】《WebGL编程指南》学习-第7章进入三维世界(1-视点和视线)

本文介绍如何在WebGL中定义观察者的位置和方向,通过视图矩阵实现三维场景的观察,并展示了如何通过键盘控制观察者视点的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

立方体由三角形构成

到目前位置,前几章的示例程序绘制了各种各样的三角形。之前讨论过,三维物体也是由二维图形组成的,如下图,12个三角形组成了一个立方体。

这里写图片描述

既然三维物体是由三角形组成的,那我们只需像前几章那样,逐个绘制组成物体的每个三角形,最终就可以绘制出整个三维物体了。但是,三维与二维还有一个显著区别:在绘制二维图形时,只需要考虑顶点的x 和 y 坐标,而绘制三维物体时,还得考虑它们的深度信息。那就开始吧,首先我们来研究一下如何定义三维世界的观察者:在什么地方、朝哪里看、视野有多宽、能看多远。为了简单起见,我们暂时不去绘制立方体,还是绘制几个简单的三角形,因为不管绘制立方体还是三角形,三维空间的规则是一样的。

视点和视线

三维物体与二维图形的最显著区别就是,三维物体具有深度,也就是Z轴。因此,你会遇到一些之前不曾考虑过的问题。事实上,我们最后还是得把三维场景绘制到二维屏幕上,即绘制观察者看到的世界,而观察者可以处在任意位置观察。为了定义一个观察者,你需要考虑以下两点:

  • 观察方向,即观察者自己在什么位置,在看场景的哪一个部分?
  • 可视距离,即观察者能够看多远?

我们将观察者所处的位置视为视点,从视点出发沿着观察方向的射线称作视线、本节将研究如何通过视点和视线来描述观察者。到下一节我们再来研究“观察者能看多远”的问题。

在 WebGL 系统中,默认情况下的视点处于原点(0, 0, 0),视线为Z轴负半轴。在这一节中,我们把视点从默认位置移动到另一个位置,以观察场景中的三角形。

我们来创建一个新的示例程序 LookAtTriangles。在程序中,视点位于(0.20. 0.25. 0.25),视线沿着原点(0, 0, 0)方向,可以看到原点附近有三个三角形,程序中的这三个三角形错落摆放,以帮助你理解三维场景中深度的概念。

这里写图片描述
这里写图片描述

三角形的颜色比之前的程序中的柔和了一些,这样看上去眼睛会比较舒服。

视点、观察目标点和上方向

为了确定观察者的状态,你需要获取两项信息:视点,即观察者的位置;观察目标点,即被观察目标所在的点,它可以用来确定视线。此外,因为我们最后要把观察到的景象绘制到屏幕上,还需要知道上方向。有了这三项信息,就可以确定观察者的状态了。

这里写图片描述

视点:观察者所在的三维空间中位置,视线的起点。在接下来的几节中,视点坐标都用(eyeX, eyeY, eyeZ)表示。

观察目标点:被观察目标所在的点。视线从视点出发,穿过观察目标点并继续延伸。注意,观察目标点是一个点,而不是视线方向,只有同时知道观察目标点和视点,才能算出视线方向。观察目标点的坐标用(atX, atY, atZ)表示。

上方向:最终绘制在屏幕上的影像中的向上的方向。试想,如果仅仅确定了视点和观察点,观察者还是可能以视线为轴旋转的。所以,为了将观察者固定住,我们还需要指定上方向。上方向是具有3个分量的矢量,用(upX, upY, upZ)表示。

这里写图片描述

在 WebGL 中,我们可以用上述三个矢量创建一个视图矩阵,然后将该矩阵传给顶点着色器。试图矩阵可以表示观察者的状态,含有观察者的视点,观察目标点,上方向等信息。之所以被成为视图矩阵,是因为它最终影响了显示在屏幕上的视图,也就是观察者观察到的场景。 Matrix4.setLookAt()函数可以根据上述三个矢量:视点、观察点和上方向,来创建出视图矩阵。

这里写图片描述

在WebGL 中,观察者的默认状态应该是这样的:

  • 视点位于坐标系统原点(0, 0, 0)。
  • 视线为 Z 轴负方向,观察点为(0, 0, -1),上方向为为Y 轴正方向, 即(0, 1, 0)。

如果将上方向改为 X 轴正半轴方向(1, 0, 0),你将看到场景旋转了90度。

创建这样一个矩阵,你只需要简单地使用如下代码。

这里写图片描述

示例程序

我们修改了视点,然后绘制了3个三角形。

LookAtTriangles.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

//片元着色器程序
 var FSHADER_SOURCE=
    '#ifdef GL_ES\n' +
    'precision mediump float;\n' +
    '#endif\n' +
    'varying vec4 v_Color;' +
    'void main() {'+
   'gl_FragColor = v_Color;'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //获取 u_ViewMatrix 变量的存储位置
    var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
    if(u_ViewMatrix < 0){
        console.log("Failed to get the storage location of u_ViewMatrix");
        return;
    }

    //设置视点、视线和上方向
    var viewMatrix = new Matrix4();
    viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

    //将视图矩阵传递给 u_ViewMatrix 变量
    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        0.0, 0.5, -0.4, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -0.5, -0.4, 0.4, 1.0, 0.4,
        0.5, -0.5, -0.4, 1.0, 0.4, 0.4,

        0.5, 0.4, -0.2, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, 0.4, -0.2, 1.0, 1.0, 0.4,
        0.0, -0.6, -0.2, 1.0, 1.0, 0.4,

        0.0, 0.5, 0.0, 0.4, 0.4, 1.0,                  //蓝色三角形在最前面
        -0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
        0.5, -0.5, 0.0, 1.0, 0.4, 0.4
    ]);
    var n=9; //点的个数

    //创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    if(!vertexColorBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE*6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    if(a_Color < 0){
        console.log("Failed to get the storage location of a_Color");
        return -1;
    }

    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*6, FSIZE*3);
    gl.enableVertexAttribArray(a_Color);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

本例基于第5章中的 ColoredTriangle.js 改编。片元着色器、传入定点数据的方式等与 ColoredTriangle.js 中的一样,主要有以下三点区别:

  • 视图矩阵被传给顶点着色器,并与顶点坐标相乘。
  • initVertexBuffers()函数创建了3个三角形的顶点坐标和颜色数据,并在 main()函数中调用。
  • main()函数计算了视图矩阵并传给了顶点着色器的 uniform 变量 u_viewMatrix。视点坐标为(0.25, 0.25, 0.25),观察点坐标为(0, 0, 0),上方向为(0, 1, 0)。

首先,来看一下上述第2点中提到 initVertexBuffers()函数。该函数与 ColorTriangle.js 中的区别在于 verticesColors 数组。原先,该数组中只有一个三角形的顶点坐标和颜色数据,修改后数组包含了3个三角形共计9个顶点的数据,而且顶点坐标的 z 分量也不再是0了。接着我们创建了缓冲区对象,并将数组中的数据填了进去。此外,我们还把 gl.drawArrays()的第3个参数改成了9,因为这里共有9个顶点。

然后,根据上述第3点,需要建立视图矩阵(包含了视点、视线和上方向信息)并传给顶点着色器。为此,我们先创建了一个 Matrix4 对象 viewMatrix,然后用 setLookAt()方法将其设置为视图矩阵,最后将试图矩阵中的元素传给顶点着色器中的 u_viewMatrix 变量。

 //设置视点、视线和上方向
    var viewMatrix = new Matrix4();
    viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

    //将视图矩阵传递给 u_ViewMatrix 变量
    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

JS部分的修改就到这里,下面来看着色器部分的修改:

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

与ColoredTriangle.js 相比,低昂点着色器有两处改动:定义 uniform 变量 u_viewMatrix;将视图矩阵与顶点坐标相乘再赋值给 gl_Position。看上去差不多,不是吗?那么这样的改动会怎样影响观察到的景象呢?接着来看。

LookAtTriangle.js 与 RotatedTriangle_Matrix.js

仔细观察示例中的顶点着色器,你会发现它和第4章的 RotatedTriangle_Matrix4.js 很像。后者在顶点着色器中创建了一个 Matrix4 类型的旋转矩阵对象,用它去旋转三角形。我们来回顾一下这个着色器:

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'uniform mat4 u_xformMatrix;'+
    'void main(){'+
        'gl_Position = a_Position * u_xformMatrix;'+
    '}';

本例 LookAtTriangle.js 的顶点着色器程序如下所示:

var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

可见,后者与前者相比增加了 attribute 变量 a_Color 以存储顶点颜色值,增加了 varying 变量 v_Color 把颜色传给片元着色器,uniform 变量由 u_RotMatrix 改成了 u_ViewMatrix。尽管存在上述这些差异,但是在两个着色器中,使用 mat4 对象乘以顶点坐标再赋值给 gl_Position 的行为却非常相似。

实际上,“根据自定义的观察者状态,绘制观察者看到的景象”与“使用默认的观察状态,但是对三维对象进行平移,旋转等变换,再绘制观察者看到的景象”,这两种行为是等价的。

举个例子,默认情况下视点在原点,视线沿着Z轴负方向进行观察。加入我们将点移动到(0, 0, 1),如下图所示。这时,视点与被观察的三角形在Z轴上的距离增加了 1.0 个单位。实际上,如果我们使三角形沿着Z轴负方向1.0个单位,也可以达到同样的效果,因为观察者看上去是一样的。

这里写图片描述

事实上,上述过程就发生在示例程序 LookAtTriangles.js 中。根据视点、观察点和上方向参数,setLookAt()方法计算出的视图矩阵恰恰就是“沿着Z轴负方向移动1.0个单位”的变换矩阵。所以,把这个矩阵与顶点坐标相乘,就相当于获得了“将视点设置在(0.0, 0.0, 1.0)”的效果。视点移动的方向与被观察对象移动的方向正好相反。对于视点的旋转,也可以采用类似的方式。

“改变观察者的状态”与“对整个世界进行平移和旋转变换”,本质上是一样的,它们都可以用矩阵来描述。接下来,我们将从一个指定的视点来观察旋转后的三角形。

从指定视点观察旋转后的三角形

第4章 RotatedTriangle_Matrix 程序绘制了一个绕Z轴旋转一定角度后的三角形。本节将修改 LookAtTriangles 程序来绘制一个从指定位置看过去的旋转后的三角形。这时,我们需要两个矩阵:旋转矩阵和视图矩阵。首先有一个问题是,以怎样的顺序相乘这两个矩阵。

我们知道,矩阵乘以顶点坐标,得到的结果是顶点经过矩阵变换之后的新坐标。也就是说,用旋转矩阵乘以顶点坐标,就可以得到旋转后的顶点坐标。

用视同矩阵乘以顶点坐标会把顶点变换到合适的位置,使得观察者(以默认状态)观察新位置的顶点,就好像在观察处在视图矩阵描述的视点上观察观察原始顶点一样。现在要在某个视点处观察旋转后的三角形,我们需要先旋转三角形,然后从这个视点来观察他。换句话说,我们需要先对三角形进行旋转变换,再对旋转后的三角形进行与”移动视点“等效的变换。我们按照上述顺序相乘两个矩阵。具体看一下等式。

我们知道,如果想旋转图形,就需要用旋转矩阵乘以旋转前的顶点坐标:

<旋转后顶点坐标>=<旋转矩阵>x<原始顶点坐标>

用视图矩阵乘以旋转后的顶点坐标,就可以获得”从视点看上去“的旋转后的顶点坐标:

<”从顶点看上去“的旋转后顶点坐标>=<视图矩阵>x<旋转后顶点坐标>

将1个式子带入第2个,可得:

<”从顶点看上去“的旋转后顶点坐标>=<视图矩阵>x<旋转矩阵>x<原始顶点坐标>

除了旋转矩阵,你还可以使用平移、缩放等基本变换矩阵或它们的组合,这时矩阵被成为模型矩阵。这样,上式就可以写成:

<视图矩阵>x<模型军阵>x<原始顶点坐标>

示例程序在着色器中实现了该式。很简单,直接照着该式修改顶点着色器。修改后的 LookAtTriangles 程序实现了上述变换。

示例程序(LookAtRotatedTriangles.js)

LookAtTriangles.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewMatrix;'+
    'uniform mat4 u_ModelMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    '#ifdef GL_ES\n' +
    'precision mediump float;\n' +
    '#endif\n' +
    'varying vec4 v_Color;' +
    'void main() {'+
    'gl_FragColor = v_Color;'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //获取 u_ViewMatrix 、u_ModelMatrix 变量的存储位置
    var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
    if(u_ViewMatrix < 0){
        console.log("Failed to get the storage location of u_ViewMatrix");
        return;
    }

    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    if(u_ModelMatrix < 0){
        console.log("Failed to get the storage location of u_ModelMatrix");
        return;
    }

    //设置视点、视线和上方向
    var viewMatrix = new Matrix4();
    viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

    //计算旋转矩阵
    var modelMatrix = new Matrix4();
    modelMatrix.setRotate(-10, 0, 0, 1);

    //将视图矩阵传递给 u_ViewMatrix 变量
    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        0.0, 0.5, -0.4, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -0.5, -0.4, 0.4, 1.0, 0.4,
        0.5, -0.5, -0.4, 1.0, 0.4, 0.4,

        0.5, 0.4, -0.2, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, 0.4, -0.2, 1.0, 1.0, 0.4,
        0.0, -0.6, -0.2, 1.0, 1.0, 0.4,

        0.0, 0.5, 0.0, 0.4, 0.4, 1.0,                  //蓝色三角形在最前面
        -0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
        0.5, -0.5, 0.0, 1.0, 0.4, 0.4
    ]);
    var n=9; //点的个数

    //创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    if(!vertexColorBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE*6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    if(a_Color < 0){
        console.log("Failed to get the storage location of a_Color");
        return -1;
    }

    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*6, FSIZE*3);
    gl.enableVertexAttribArray(a_Color);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

首先,顶点着色器中添加了 uniform 变量 u_ModelMatrix,该变量从 JS 中接受模型矩,以实现等式。

'gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;'+

JS 的 main()函数已经有了与视图矩阵相关的代码,只需要添加几行计算和传入旋转矩阵的代码,将三角形绕Z轴旋转10度。

运行示例程序,顶点坐标依次与旋转矩阵和视图矩阵相乘,最终获得了预期的效果。即先用 u_ModelMatrix 旋转三角形,再将旋转后的坐标用 u_ViewMatrix 变换到正确的位置,使其看上去就像是从指定观点出观察一样。

这里写图片描述

利用键盘改变视点

这一节将在LookAtTriangles 的基础上记性修改,使得当键盘上的方向键被按下时,观察者的视点也随之移动。在新程序 LookAtTrianglesWithKeys 中,如果右方向键被按下,视点 X 坐标将增大 0.01; 如果左方向键被按下,视点的 X 坐标将减少 0.01。

LookAtTrianglesWithKeys.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'attribute vec4 a_Color;'+
    'uniform mat4 u_ViewMatrix;'+
    'varying vec4 v_Color;'+
    'void main(){'+
    'gl_Position = u_ViewMatrix * a_Position;'+
    'v_Color = a_Color;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    '#ifdef GL_ES\n' +
    'precision mediump float;\n' +
    '#endif\n' +
    'varying vec4 v_Color;' +
    'void main() {'+
    'gl_FragColor = v_Color;'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //获取 u_ViewMatrix 变量的存储位置
    var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
    if(u_ViewMatrix < 0){
        console.log("Failed to get the storage location of u_ViewMatrix");
        return;
    }

    //设置视点、视线和上方向
    var viewMatrix = new Matrix4();
    // 注册键盘事件响应函数
    document.onkeydown = function(ev){
        keydown(ev, gl, n, u_ViewMatrix, viewMatrix);
    };

    draw(gl, n, u_ViewMatrix, viewMatrix);

}

function initVertexBuffers(gl) {
    var verticesColors = new Float32Array([
        0.0, 0.5, -0.4, 0.4, 1.0, 0.4,              //绿色三角形在最后面
        -0.5, -0.5, -0.4, 0.4, 1.0, 0.4,
        0.5, -0.5, -0.4, 1.0, 0.4, 0.4,

        0.5, 0.4, -0.2, 1.0, 0.4, 0.4,              //黄色三角形在中间
        -0.5, 0.4, -0.2, 1.0, 1.0, 0.4,
        0.0, -0.6, -0.2, 1.0, 1.0, 0.4,

        0.0, 0.5, 0.0, 0.4, 0.4, 1.0,                  //蓝色三角形在最前面
        -0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
        0.5, -0.5, 0.0, 1.0, 0.4, 0.4
    ]);
    var n=9; //点的个数

    //创建缓冲区对象
    var vertexColorBuffer = gl.createBuffer();
    if(!vertexColorBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

    var FSIZE = verticesColors.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE*6, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
    if(a_Color < 0){
        console.log("Failed to get the storage location of a_Color");
        return -1;
    }

    gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE*6, FSIZE*3);
    gl.enableVertexAttribArray(a_Color);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return n;
}

var g_eyeX = 0.20,g_eyeY = 0.25,g_eyeZ = 0.25;
function draw(gl, n, u_ViewMatrix, viewMatrix) {
    //设置视点和视线
    viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0);

    //将视图矩阵传递给 u_ViewMatrix 变量
    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
    if(ev.keyCode == 39){  //按下右键
        g_eyeX += 0.01;
    }else if(ev.keyCode == 37){  //按下左键
        g_eyeX -= 0.01;
    }else{
        return;
    }

    draw(gl, n, u_ViewMatrix, viewMatrix);
}

在本例中,我们注册了键盘事件响应函数。每当左方向键或右方仙剑被按下时,就会改变视点的位置,然后调用 draw()函数重绘场景。在研究键盘事件响应函数前,先来看一下 draw()函数。

draw()函数的流程十分直接:首先根据全局变量 g_eyeX、g_eyeY、g_eyeZ 计算视图矩阵,这三个变量的初始值分别是0.2、0.25、0.25;然后将计算得到的视图矩阵传给顶点着色器中的 u_ViewMatrix 变量。注意 main()函数调用 draw()函数以参数的形式传入了之前获取的着色器中 u_ViewMatrix 的存储地址,和一个新创建的 Matrix4 对象。这样做的目的是为了提高 draw()函数的效率,否则我们就得在每次调用 draw()函数时都重新获取 u_ViewMarix 的地址并新建 Matrix4 对象。

全局变量 g_eyeX、g_eyeY、g_eyeZ 中存储这视点的坐标,键盘事件响应函数将更新 g_eyeX 的值。为了在按键被按下时调用该函数,我们必须把函数注册到 document 对象的 onkeydown 属性上去。我们定义了一个匿名函数作为键盘事件响应函数:

document.onkeydown = function(ev){
        keydown(ev, gl, n, u_ViewMatrix, viewMatrix);
    };

匿名函数调用了 keydown()函数,并传入了相关的参数。让我们来看一下 keydown()函数的实现。

function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
    if(ev.keyCode == 39){  //按下右键
        g_eyeX += 0.01;
    }else if(ev.keyCode == 37){  //按下左键
        g_eyeX -= 0.01;
    }else{
        return;
    }

    draw(gl, n, u_ViewMatrix, viewMatrix);
}

keydown()函数的第1个参数 ev 是一个事件对象,该函数的逻辑很直接,首先根据 ev.keyCode 属性检查哪个案件被按下,然后更新 g_eyeX。如果是右方向键,就令 g_eyeX 增加0.01,如果是左方向键,就令 g_eyeX 减少0.01.最后调用 draw()函数绘制三角形。

运行程序,每当你按下左或右方向键时,三角形都会改变以下方向,实际上这时因为观察者的位置发生了变化。

这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值