目录
可视范围(正射类型)
可视空间
正射投影的盒状可视空间的工作原理
盒状可视空间
定义盒状可视空间
Matrix4.setOrtho()
按键控制near、far
编辑 示例效果
示例代码
代码详解
修改near和far值
通过右方向键增大near的值
通过下方向键减小far的值
修改近裁截面宽高导致物体“变形”
宽高缩小一半,保持宽高比
宽高缩小一半,高度不变,改变宽高比
虽然你可以将三维物体放在三维空间中的任何地方,但是只有当它在可视范围内时,WebGL才会绘制它。事实上,不绘制可视范围外的对象,是基本的降低程序开销的手段。绘制可视范围外的对象没有意义,即使把它们绘制出来也不会在屏幕上显示。从某种程序上来说,这样做也模拟了人类观察物体的方式,如下图所示。我们人类也只能看到眼前的东西,水平视角大约200度左右。总之,WebGL就是以类似的方式,只绘制可视范围内的三维对象。
除了水平和垂直范围内的限制,WebGL还限制观察者的可视深度,即“能够看多远”。所有这些限制,包括水平视角、垂直视角和可视深度,定义了可视空间(view volume)。由于我们没有显式地指定可视空间,默认的可视深度又不够远,所以三角形的一个角看上去就消失了,如下图所示。
有两类常用的可视空间:
● 长方体可视空间,也称盒状空间,由正射投影(orthographic projection)产生。
● 四棱锥/金字塔可视空间,由透视投影(perspective projection)产生。
在透视投影下,产生的三维场景看上去更是有深度感,更加自然,因为我们平时观察真实世界用的也是透视投影。在大多数情况下,比如三维射击类游戏中,我们都应当采用透视投影。相比之下,正射投影的好处是用户可以方便地比较场景中物体(比如两个原子的模型)的大小,这是因为物体看上去的大小与其所在的位置没有关系。在建筑平面图等技术绘图的相关场合,应当使用这种投影。
盒状可视空间的形状如下图所示。可视空间由前后两个矩形表面确定,分别称近裁界面(near clipping plane)和远裁截面(far clipping plane),前者的四个顶点为(right,top,-near),(-left,top,-near),(-left,-bottom,-near),(right,-bottom,-near),而后者的四个顶点为(right,top,far),(-left,top,far),(-left,-bottom,far),(right,-bottom,far)。
<canvas>上显示的就是可视空间中物体在近裁剪面上的投影。如果裁剪面的宽高比和<canvas>不一样,那么画面就会被按照<canvas>的宽高比进行压缩,物体会被扭曲(稍后详细讨论)。近裁剪面与远裁剪面之间的盒形空间就是可视空间,只有在此空间内的物体会被显示出来。如果某个物体一部分在可视空间内,一部分在其外,那就只显示空间内的部分。
矩阵库WebGL矩阵变换库_山楂树の的博客-CSDN博客提供的Matrix4.setOrtho()方法可用来设置投影矩阵,定义盒装可视空间。
我们在这里又用到了矩阵。这个矩阵被称为正射投影矩阵(orthographic projection matrix)。示例程序OrthoView将使用这种矩阵定义盒状可视空间,本例把视点置于原点处,视线为Z轴负方向。可视空间定义下图所示,near=0.0,far=0.5,left=-1.0,right=1.0,bottom=-1.0,top=1.0,三角形处于Z轴0.0到-0.4区间上。
此外,示例程序还允许通过键盘按键修改可视空间的near和far值。这样我们就能直观地看到这两个值具体对可视空间有什么影响。下面列出了各按键的作用。
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ProjMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ProjMatrix * a_Position;\n' +
' v_Color = a_Color;\n' +
'}\n';
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
var canvas = document.getElementById('webgl');
// 获取dom对象nearFar
var nf = document.getElementById('nearFar');
var gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
gl.clearColor(0, 0, 0, 1);
var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');
// 创建矩阵以设置视点和视线
var projMatrix = new Matrix4();
// 注册键盘事件响应事件
document.onkeydown = function (ev) { keydown(ev, gl, n, u_ProjMatrix, projMatrix, nf); };
draw(gl, n, u_ProjMatrix, projMatrix, nf);
}
function initVertexBuffers(gl) {
var verticesColors = new Float32Array([
// 顶点和颜色数据
0.0, 0.6, -0.4, 0.4, 1.0, 0.4, // 最后面的绿色三角形
-0.5, -0.4, -0.4, 0.4, 1.0, 0.4,
0.5, -0.4, -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();
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');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);
return n;
}
// 视点与近远裁面的距离
var g_near = 0.0, g_far = 0.5;
function keydown(ev, gl, n, u_ProjMatrix, projMatrix, nf) {
switch (ev.keyCode) {
case 39: g_near += 0.01; break; // 按下右方向键
case 37: g_near -= 0.01; break; // 按下左方向键
case 38: g_far += 0.01; break; // 按下上方向键
case 40: g_far -= 0.01; break; // 按下下方向键
default: return; // 按下了其他键
}
draw(gl, n, u_ProjMatrix, projMatrix, nf);
}
function draw(gl, n, u_ProjMatrix, projMatrix, nf) {
// 使用矩阵设置可视空间(left right bottom top 近裁面 远裁面)
projMatrix.setOrtho(-1.0, 1.0, -1.0, 1.0, g_near, g_far);
// 将投影矩阵传给u_ProjMatrix变量
gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
gl.clear(gl.COLOR_BUFFER_BIT); // 清除
本例定义了keydown()函数(第36行),每当按下按键时,匿名的事件响应函数就会调用keydown()函数。keydown()函数首先更新near和far的值,然后调用draw()函数进行绘制(第37行)。draw()函数将设置可视空间,更新页面上文本显示的near和far的值,并绘制3个三角形(第84行)。最关键的事情是设置可视空间,就发生在draw()函数中。
uniform变量u_ProjMatrix存储了可视空间的投影矩阵,我们将投影矩阵与顶点坐标相乘,再赋值给gl_Position(第7行)。
当键盘的上方向键被按下时,事件响应函数就会执行(第36行)并调用keydown()。注意我们将nf作为最后一个参数传入,这样keydown()函数就能够访问并修改dom元素了。keydown()函数最后调用了draw()函数绘制三角形,这样每次按键后都会重绘整个图形。
keydown()函数首先检查哪个键被按下,然后根据按下的键,修改g_near和g_far的值(稍后这两个值将被传给setOrtho()函数以创建投影矩阵,设置可视空间),最后调用draw()函数。注意,这里g_near和g_far是全局变量(第72行),不管是keydown()还是draw()函数都可以访问它。
再看一下draw()函数(第91行)它修改了网页上的文本信息。
draw()函数计算出可视空间对应的投影矩阵projMatrix(第86行),将其传递给着色器中的u_ProjMatrix变量(第88行),接着在页面上更新near和far的值(第91行),最后绘制出三角形(第92行)。
运行程序,按下右方向键逐渐增加near值,你会看到三角形逐个消失了,如下图所示。
默认情况下,near值为0.0,此时3个三角形都出现了。当我们首次按下右方向键,将near值增加至0.01时,处在最前面的蓝色的三角形消失了,如上图中)所示。这是因为,蓝色三角形就在XY平面上,近裁剪面越过了蓝色三角形,使其处在了可视空间外,如下图所示。
蓝色三角形处于可视空间外
同样,如果你改变far的值,也会产生类似的效果,如下图所示。随着far值的逐渐减小,当值小于0.4时,绿色三角形首先消失,小于0.2时,黄色三角形消失,最终只剩下蓝色三角形。
这个示例程序清晰地展示了可视空间的作用。如果你想绘制任何东西,就必须把它置于可视空间中。
在上述“可视空间”中曾说过,如果可视空间近裁剪面的宽高比与<canvas>不一致,显示出的物体就会被压缩变形。现在就来研究这一点。本例将近裁剪面的宽度和高度改为了原来的一半,但是保持了宽高比:
结果如下图所示,三角形变成了之前大小的两倍。这是由于<canvas>的大小没有发生变化,但是它表示的可视空间却缩小了一半。注意,三角形的有些部分越过了可视空间并被裁剪了。
结果如下图所示,由于近裁剪面宽度缩小而高度不变,相当于把长方形的近裁剪面映射到了正方形的<canvas>上,所以绘制出来的三角形就在宽度上拉伸而导致变形了。