WebGL正射投影

目录

可视范围(正射类型)

可视空间

正射投影的盒状可视空间的工作原理

盒状可视空间

定义盒状可视空间

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()方法可用来设置投影矩阵,定义盒装可视空间。

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值。这样我们就能直观地看到这两个值具体对可视空间有什么影响。下面列出了各按键的作用。

按键控制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);       // 清除 <canvas>
  // 显示当前的near和far值
  nf.innerHTML = 'near: ' + Math.round(g_near * 100) / 100 + ', far: ' + Math.round(g_far * 100) / 100;
  gl.drawArrays(gl.TRIANGLES, 0, n);   // 绘制三角形
}

本例定义了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和far值

运行程序,按下右方向键逐渐增加near值,你会看到三角形逐个消失了,如下图所示。

通过右方向键增大near的值 

 默认情况下,near值为0.0,此时3个三角形都出现了。当我们首次按下右方向键,将near值增加至0.01时,处在最前面的蓝色的三角形消失了,如上图中)所示。这是因为,蓝色三角形就在XY平面上,近裁剪面越过了蓝色三角形,使其处在了可视空间外,如下图所示。

蓝色三角形处于可视空间外

同样,如果你改变far的值,也会产生类似的效果,如下图所示。随着far值的逐渐减小,当值小于0.4时,绿色三角形首先消失,小于0.2时,黄色三角形消失,最终只剩下蓝色三角形。

通过下方向键减小far的值 

 这个示例程序清晰地展示了可视空间的作用。如果你想绘制任何东西,就必须把它置于可视空间中。

修改近裁截面宽高导致物体“变形”

在上述“可视空间”中曾说过,如果可视空间近裁剪面的宽高比与<canvas>不一致,显示出的物体就会被压缩变形。现在就来研究这一点。本例将近裁剪面的宽度和高度改为了原来的一半,但是保持了宽高比:

宽高缩小一半,保持宽高比

  

结果如下图所示,三角形变成了之前大小的两倍。这是由于<canvas>的大小没有发生变化,但是它表示的可视空间却缩小了一半。注意,三角形的有些部分越过了可视空间并被裁剪了。

宽高缩小一半,高度不变,改变宽高比

结果如下图所示,由于近裁剪面宽度缩小而高度不变,相当于把长方形的近裁剪面映射到了正方形的<canvas>上,所以绘制出来的三角形就在宽度上拉伸而导致变形了。 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山楂树の

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值