您的位置:首页 > 编程语言

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

2017-10-13 13:32 417 查看

立方体由三角形构成

到目前位置,前几章的示例程序绘制了各种各样的三角形。之前讨论过,三维物体也是由二维图形组成的,如下图,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()函数绘制三角形。

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



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  webgl 三维 图形
相关文章推荐