- 前言: 三维物体由三角形组成的,那么我们需要像前面一样逐个绘制物体的每个三角形,最终绘制整个三维物体,但是三维与二维有一个显著的区别,绘制二维物体的时候,我们只需要考虑x,y信息,但是在绘制三维物体的时候,我们还得考虑他们的深度信息.
视点和视线
我们将观察点所处的位置称为视点,从视点出发沿着观察方向的射线称为视线
视点,观察目标点和上方向
为了确定观察者的状态的状态,需要获取两项信息:视点:即观察者的位置,观察目标点,即观察目标所在的点,它可以用来确定视线.此外我们要把观察到的景色绘制到屏幕上面,还需要上方向.有了这三项,我们就可以确定观察者的状态了.
视点,视线,观察点
- 视点:观察者所在三维空间位置,视线的起点
- 观察目标点:被观察目标所在的点,视线从视点出发,穿过目标点并继续延伸,
- 上方向:最终绘制在屏幕上的影像中的向上的方向
在 webgl 中,我们可以用上述三个矢量创建一个视图矩阵,然后将该矩阵传递给着色器.视图矩阵可以表示观察者的状态,包含观察者的视点,观察目标点,上方向信息.在 THREE.JS 中封装的视图矩阵.setLookAt方法
Matrix4.setLookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ)
//eyex,eyez,eyeZ 代表视点
//atX,atY,atZ 代表观察点
//upX,upY,upZ 代表上方向
在webgl中,观察者默认状态应该是
- 视点默认在坐标原点
- 视线为Z的负方向,观察点为(0,0,-1),上方向为Y的负半轴,即(0,1,0)
示例代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
<script id="vertextShader" type="x-shader/x-vertex">
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;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
varying vec4 v_Color;
void main () {
gl_FragColor = v_Color;
}
</script>
<script src="./jsm/util.js"></script>
<script src="tool/cuon-matrix.js"></script>
<script>
function main() {
const canvas = document.getElementById('webgl')
const gl = canvas.getContext('webgl')
const vertextShader = document.getElementById('vertextShader').innerText
const fragmentShader = document.getElementById('fragmentShader').innerText
if (!initShaders(gl, vertextShader, fragmentShader)) return
if (!gl) return
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
const u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix')
if (a_Position < 0) return
// 创建缓冲区域
let n = initVertexBuffers(gl, a_Position, a_Color)
// 向缓冲区域写入数据
let viewMatrix = new Matrix4()
viewMatrix.setLookAt(
0.20, 0.25, 0.25, //视点
0.0, 0.0, 0.0, // 观察点
0, 1, 0, //上方向
)
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements)
gl.drawArrays(gl.TRIANGLES, 0, n)
}
function initVertexBuffers(gl, a_Position, a_Color) {
let verticesColors = new Float32Array([
0.0, 0.5, -0.4, 1.0, 0.0, 0.0,
-0.5, -0.5, -0.4, 1.0, 0.0, 0.0, //红色三角形
0.5, -0.5, -0.4, 0.1, 0.0, 0.0,
0.5, 0.4, -0.2, 0.0, 1.0, 0.0,
-0.5, 0.4, -0.2, 0.0, 1.0, 0.0, // 绿色三角形
0.0, -0.6, -0.2, 0.0, 1.0, 0.0,
0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, //蓝色
0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
])
var n = 9
var vertexColorbuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer)
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)
let fsize = verticesColors.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, fsize * 6, 0)
gl.enableVertexAttribArray(a_Position)
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, fsize * 6, fsize * 3)
gl.enableVertexAttribArray(a_Color)
return n
}
</script>
</html>
实际上,根据自定义的观察者状态,绘制观察者看到的景色与使用默认观察者状态与三维物体进行平移,旋转,缩放是等价的.
可视范围
虽然我们可以将三维物体放在三维空间中的任何地方,但是只有在它的可视范围内时,webgl 才能绘制它.事实上,不绘制可视范围外的对象,是基于降低程序开销的手段.除了水平和垂直范围内的限制,webgl 还限制观察者的可视深度,即能够看多远,所有这些限制,包括水平视角,垂直视角和可视深度,定义了可视空间
可视空间
有两种可视空间
- 长方体可视空间,也称为盒装空间,由正投影产生
- 金字塔可视空间,由透视投影产生
在透视投影下,产生的三维场景看上去更加有深度感,更加自然相比之下正射投影可以让用户方便的比较场景中的物体的大小,这是因为物体看上去的大小和所在的位置无关
正射投影的盒状可视空间的工作原理
盒状可视空间的形状如下图所示.可视空间由前后两个矩形表面确定,分别称近裁剪面,远裁剪面.前面的四个顶点分别为(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 的宽度比进行压缩,物体会变形
image.png
定义盒状可视空间
THREE.JS 提供 Matrix4.setOrtho方法可用来设置投影矩阵
参数 | 参数说明 |
---|---|
left,right,bottom,top | 指定近裁面的左右边界 |
bottom,top | 近裁面的上下边界 |
near,far | 指定近裁面和远裁面的位置,即可视空间的近边界和远边界 |
- 注意:物体总是从 near 看上 far,如果 near 的值大于 far 的值,说明从反面看向物体
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
<script id="vertextShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ProjMatrix;
varying vec4 v_Color;
void main () {
gl_Position = u_ProjMatrix*a_Position;
v_Color = a_Color;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
varying vec4 v_Color;
void main () {
gl_FragColor = v_Color;
}
</script>
<script src="./jsm/util.js"></script>
<script src="tool/cuon-matrix.js"></script>
<script>
let g_near =0.0,g_far = 0.5;
function main() {
const canvas = document.getElementById('webgl')
const gl = canvas.getContext('webgl')
const vertextShader = document.getElementById('vertextShader').innerText
const fragmentShader = document.getElementById('fragmentShader').innerText
if (!initShaders(gl, vertextShader, fragmentShader)) return
if (!gl) return
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
const u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix')
if (a_Position < 0) return
let projMatrix = new Matrix4()
// 创建缓冲区域
let n = initVertexBuffers(gl, a_Position, a_Color);
draw(gl,n,u_ProjMatrix,projMatrix);
document.onkeydown = function(ev) {
keyDown(ev, gl, n, u_ProjMatrix, projMatrix)
}
}
function keyDown(ev, gl, n, u_ViewMatrix, viewMatrix) {
// 按钮
console.log(ev.keyCode)
switch(ev.keyCode) {
case 39: {
g_near +=0.1;break
};
case 37: g_near -= 0.01;break;
case 38: g_far +=0.01;break;
case 40: g_far -=0.01;break;
}
draw(gl,n, u_ViewMatrix, viewMatrix)
}
function draw(gl,n, u_ViewMatrix, viewMatrix) {
// debugger
viewMatrix.setOrtho(-1,1,-1,1,g_near,g_far)
console.log( g_near,g_far)
// viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0)
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLES, 0, n)
}
function initVertexBuffers(gl, a_Position, a_Color) {
let verticesColors = new Float32Array([
0.0, 0.5, -0.4, 1.0, 0.0, 0.0,
-0.5, -0.5, -0.4, 1.0, 0.0, 0.0, //红色三角形
0.5, -0.5, -0.4, 0.1, 0.0, 0.0,
0.5, 0.4, -0.2, 0.0, 1.0, 0.0,
-0.5, 0.4, -0.2, 0.0, 1.0, 0.0, // 绿色三角形
0.0, -0.6, -0.2, 0.0, 1.0, 0.0,
0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, //蓝色
0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
])
var n = 9
var vertexColorbuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer)
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)
let fsize = verticesColors.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, fsize * 6, 0)
gl.enableVertexAttribArray(a_Position)
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, fsize * 6, fsize * 3)
gl.enableVertexAttribArray(a_Color)
return n
}
</script>
</html>
从上面例子可以知道,当面 near 在原点的时候,far 的具体在第三个三角形后面可以看到完成的三个三角形,far 不懂,near 移动到第一个三角形和第二个三角形之间,只能看到第二个和第三个三角形.这是因为第一个三角形不在可视范围内了,同理,如果 near 移动到第二个和第三个三角形之间,第一个第二个也看不到了,如果 near 移动到第三个三角形之后,全部都看不到了
透视投影
透视投影可视空间如下图所示.透视投影也有视点,视线,近裁面和远裁面.这样可视空间内的物体才会被显示,可视空间外的物体不会显示.
透视投影可视空间
在 three.js Matrix 中定义了 setPerspective 方法来设置投影矩阵
参数 | 参数说明 | 参数位置 |
---|---|---|
fov | 指定垂直视角 | 0 |
aspect | 指定近裁面的宽高比 | 1 |
near,far | 近裁面和远裁面的距离 | 2,3 |
正确处理对象的前后关系
webgl 在默认的情况下会按照缓冲区的顺序绘制图形,而且后绘制的图形覆盖先绘制的图形.因为这样做很高效.如果场景中的对象不发生变化,观察者的状态唯一的.这样的做法没有问题,但是如果你希望不断的移动视点,从不同的角度看物体,那么你不可能事先决定对象出现的顺序.为了解决这个问题,webgl 提供了隐藏面消除功能.这个功能会帮助我们消除那些被遮挡的表面,我们可以放心的绘制场景而不必顾忌各个物体在缓冲区的顺序.开启隐藏面消除功能,需要遵守以下几步
- 开启隐藏面消除功能
gl.enable(gl.DEPTH_TEST) - 在绘制之前,清除深度缓冲区
gl.clear(gl.DEPTH_BUFFER_BIT)
使用 gl.clear()方法清除深度缓冲区.深度缓冲区是一个中间对象,其作用就是帮助 webgl 进行隐藏面消除.webgl在颜色冲绘制几何图形,绘制完成之后将颜色缓冲区显示到canvas上.如果要将隐藏面消除,那必须知道每个几何图形的深度信息,而深度缓冲区就是用来存储深度信息,由于深度方向通常是z轴方向,所以我们也称为 Z 缓存区
深度冲突
隐藏面消除是 webgl 的一项复杂而又强大的特性,在绝大多数的情况下,它都能很好的完成任务.然而,当几何图形或者物体的两个表面即为接近的时候,就会出现问题.使得表面看上去斑斑皱皱的,这种现象称为深度冲突.webgl为了解决这个问题,提供了一种称为多边形偏移的机制来解决这个问题.该机制将自动在 Z 值上加上一个偏移量.这个偏移量的值由物体表面相对观察者的角度来确定.启动该机制需要进行下面两部
- 启动多边形偏移
gl.enable(gl.POLY_OFFSET_FILL) - 在绘制之前指定计算偏移量的参数
gl.polygonOffset(1.0,1.0)
立方体
前文介绍过绘制立方体,通过给立方体的每个面绘制两个三角形即定义 36 个顶点绘制的立方体.但是实际上立方体只有 8 个顶点,但是却使用了 36 个顶点,增加了代码的复杂度.现在提供一种更加方面的方式来绘制立方体.首先把立方体拆成 6 个面,每个面由 2 个三角形组成,与三角形表中国的两个三角形相关联系.如下图所示
image.png
webgl 提供通过顶点索引绘制物体.gl.drawElement(mode,count,type,offset)
参数 | 参数说明 |
---|---|
mode | 指定绘制的方式 |
count | 指定绘制的顶点数 |
type | 索引数据类型 |
offset | 指定索引数组中开始绘制的位置 |
绘制立方体示例代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
<script id="vertextShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
uniform mat4 u_MvpMatrix;
attribute vec4 a_Color;
varying vec4 v_Color;
void main () {
gl_Position = u_MvpMatrix *a_Position;
v_Color = a_Color;
}
</script>
<script src="tool/cuon-matrix.js"></script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_Color;
void main () {
gl_FragColor = v_Color;
}
</script>
<script src="./jsm/util.js"></script>
<script src="tool/cuon-matrix.js"></script>
<script>
function main() {
const canvas = document.getElementById('webgl')
const gl = canvas.getContext('webgl')
const vertextShader = document.getElementById('vertextShader').innerText
const fragmentShader = document.getElementById('fragmentShader').innerText
if (!initShaders(gl, vertextShader, fragmentShader)) return
if (!gl) return
const u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix')
// if (a_Position < 0) return
// debugger
let n = initVertexBuffers(gl)
var viewMatrix = new Matrix4()
viewMatrix.setLookAt(3,3,7,0,0,0,0,1,0)
var modeMatrix = new Matrix4()
modeMatrix.setRotate(0,0,0,1)
let projMatrix = new Matrix4()
projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100)
var modeViewMatrix = projMatrix.multiply(viewMatrix.multiply(modeMatrix))
gl.enable(gl.DEPTH_TEST)
// mvpMatrix.setLookAt(3,3,7,0,0,0,0,1,0)
gl.uniformMatrix4fv(u_MvpMatrix, false, modeViewMatrix.elements)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
}
function initVertexBuffers(gl) {
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
var verticesColors = new Float32Array([
1.0, 1.0, 1.0, 1.0, 0.0, 0.0, // 0号点色
-1.0, 1.0, 1.0, 1.0, 1.0, 0.0, // 1号点色
-1.0, -1.0, 1.0, 1.0, 1.0, 1.0, //2点色
1.0, -1.0, 1.0, 0.8, 0.9, 0.2, //3 点色
1.0, -1.0, -1.0, 0.3, 0.5, 0.7, //4 点色
1.0, 1.0, -1.0, 0.4, 0.6, 0.8, //5 点色
-1.0, 1.0, -1.0, 0.5, 0.6, 0.7, //6 点色
-1.0, -1.0, -1.0, 0.9, 0.6, 0.3 //7 号色
])
var indices = new Uint8Array([
0,1,2,0,2,3,
0,3,4,0,4,5,
0,5,6,0,6,1,
1,6,7,1,7,2,
7,4,3,7,3,2,
4,7,6,4,6,5
])
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
gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSize*6,0)
gl.enableVertexAttribArray(a_Position)
gl.vertexAttribPointer(a_Color,3,gl.FLOAT,false,FSize*6,FSize*3)
gl.enableVertexAttribArray(a_Color)
var indicesBuffer = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indicesBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW)
return indices.length
}
</script>
</html>
网友评论