7-进入三维世界
一、学习要点
- 以用户视角而进入三维世界
- 控制三维可视空间
- 裁剪
- 处理物体的前后关系
- 绘制三维立方体
二、立方体由三角形构成
12个三角形组成了一个立方体。既然三维物体由三角形组成,其实我们只需要像前面那样,逐个绘制组成物体的三角形就可以了吗?
三维物体与二维物体的一个显著区别:在二维中我们只需要考虑顶点的x y坐标而三维物体我们要考虑它的深度信息(depth information)。
为此我们要研究一下如何定义三维物体世界的观察者:
- 在什么地方看
- 朝着那边开
- 视野有多宽
- 能看多远
三、视点与视线
我们将观察者所处的位置称为视点,从视点触发沿着观察方向的射线称作视线。
视点、观察模板点和上方向
为了确定观察值的状态,你需要获取两项信息: 视点,即管这种的位置。观察目标点(look-at point),即被观察模板所在的点,他可以用来确定视线。此外 因为我们最后要把观察到的景象会知道屏幕上,还需要知道 上方向。
- 视点:观察者所在的三维空间中的位置,视线的起点。视点坐标用(eyeX,eyeY,eyeZ)表示。
- 观察目标点:被观察目标所在的点。视线从视点触发,穿过观察目标点并继续延伸。坐标表示为(atX,atY,atZ).
- 上方向:最终绘制在屏幕上的影响中的向上的方向。为了固定观察者防止它绕着视线为轴旋转。用(upX,upY,upZ)表示
利用上面三个矢量可以创建一个视图矩阵(view matrix)。然后将该矩阵传递顶点着色器。视图矩阵可以表示观察者的状态。 观察者默认状态应该是
- 视点位于坐标系统的原点(0,0,0)
- 实现为Z轴负方向 观察点为(0,0,-1)
- 上方向为Y轴的负方向 (0,1,0)
程序实例: 在三维空间中观察三角形
// 顶点着色器
var VSHANER_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 =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_ViewMatrix = gl.getUniformLocation(gl.program,'u_ViewMatrix');
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.28,0.25,0.25,0,0,0,0,1,0);
// 将试图矩阵传给 u_ViewMatrix变量
gl.uniformMatrix4fv(u_ViewMatrix,false,viewMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
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();
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');
// 将缓冲区对象分配给 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;
}
视图矩阵与顶点矩阵相乘达到增加观察者的效果。 事实上 根据自定义的观察者状态 绘制观察者看到的景象 与 使用默认的观察状态,但是对三维对象进行评议 旋转等变化 在绘制观察者看到景象 这两种行为是等价的。
视点移动的方向与被观察对象(也就是整个世界)移动的方向正好相反。对于视点的旋转也可以采用类似的方式。
四 从指定视点观察旋转后的三角形
我们需要两个矩阵: 旋转矩阵(表示三角形的旋转) 和 视图矩阵(表示观察世界的方式)。首先的问题是以怎么的顺序相乘两个矩阵。 我们需要先旋转三角形,然后从这个视点观察他。所以要先对三角形进行旋转变换,在对旋转后的三角形进行与“移动视点”等效的变化。我们按照上述顺序相乘矩阵。
- <旋转后顶点坐标> = <旋转矩阵> x <原始顶点坐标>
- <“从视点看上去” 的旋转后顶点坐标> = <视图矩阵> x <旋转后顶点坐标>
模型矩阵:使用平移 缩放等基本变换或他们的组合组成的矩阵
- <“从视点看上去” 的旋转后顶点坐标> = <视图矩阵> x <模型矩阵> x <原始顶点坐标>
// 顶点着色器
var VSHANER_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 =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_ViewMatrix = gl.getUniformLocation(gl.program,'u_ViewMatrix');
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.28,0.25,0.25,0,0,0,0,1,0);
// 将试图矩阵传给 u_ViewMatrix变量
gl.uniformMatrix4fv(u_ViewMatrix,false,viewMatrix.elements);
// 模型矩阵赋值
var u_ModelMatrix = gl.getUniformLocation(gl.program,'u_ModelMatrix');
var modelMatrix = new Matrix4();
modelMatrix.setRotate(-10,0,0,1);
gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
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();
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');
// 将缓冲区对象分配给 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;
}
这样的写法 程序对于每个顶点都要计算 视图矩阵 x 模型矩阵 这是不必要的性能开销。我们可以现在外面吧这两个矩阵相乘结果算出来 在给顶点着色器 这两个矩阵相乘得到的结果成为 模型视图矩阵(model view matrix)
var modelViewMatrix = viewMatrix.multiply(modelMatrix);
// 实际上只需要一行代码就可以计算出模型视图矩阵了
var modelViewMatrix = new Matrix4();
modelViewMatrix.setLookAt(0.20,0.25,0.25,0,0,0,0,1,0).rotate(-10,0,0,1);
五、利用键盘改变视点
// 顶点着色器
var VSHANER_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 =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_ViewMatrix = gl.getUniformLocation(gl.program,'u_ViewMatrix');
// 设置视点 视线 和上方向
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();
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');
// 将缓冲区对象分配给 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_eyeX = 0.20,g_eyeY=0.25,g_eyeZ=0.25;
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)
}
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.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,n);
}
六、可视范围(正射类型)
只有当三维物体卡再可视范围内时 webGL才会绘制它。
- 可视空间分为两类
- 长方形可视空间,也成盒装空间,由正射投影(orthographic projection)产生。
- 四棱锥/ 金字塔可视空间,由透视投影(perspective projection)产生。
基于正射投影的盒装可视空间的工作原理:盒装可视空间的由前后两个矩形表面确定,分别称为近裁剪面和远裁剪面。
定义盒装可视空间 cuon-matrix.js中提供了Matrix4.setOrtho()方法用来设置投影矩阵,定义盒装可视空间。 通过示例程序展示正射投影矩阵的使用方法和效果。
// 可以使用方向键操作投影盒子的近裁剪面 和 远裁剪面
// 顶点着色器
var VSHANER_SOURCE =
`
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;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
// 获取nearFar元素
var nf = document.getElementById('nearFar');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
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);
}
// 视点与 近 远裁剪面的距离
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) {
projMatrix.setOrtho(-1,1,-1,1,g_near,g_far);
// 将投影矩阵传递给u_ProjMatrix变量
gl.uniformMatrix4fv(u_ProjMatrix,false,projMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
nf.innerHTML = 'near:'+Math.round(g_near*100)/100 + ', far:'+Math.round(g_far*100)/100;
gl.drawArrays(gl.TRIANGLES,0,n);
}
改变 projMatrix.setOrtho(-0.3,0.3,-1.0,1.0,g_near,g_far); 可以投影出不同的三角形
七、可视空间(透视投影)
使用透视空间可以使大的画出的图形具有深度感。从图上可以看出随着Z轴的变化 相同大小的三角形会越来越小
透视投影可视空间的定义:
采用setPerspective()函数定义透视投影矩阵。 示例程序如下
// 顶点着色器
var VSHANER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProMatrix;
varying vec4 v_Color;
void main() {
gl_Position =u_ProMatrix * u_ViewMatrix * a_Position;
v_Color = a_Color;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_ViewMatrix = gl.getUniformLocation(gl.program,'u_ViewMatrix');
var u_ProMatrix = gl.getUniformLocation(gl.program,'u_ProMatrix');
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
// 投影矩阵
var projMatrix = new Matrix4();
// 计算视图矩阵和投影矩阵
viewMatrix.setLookAt(0,0,5,0,0,-100,0,1,0);
//第二个参数aspect宽高比应该与 canvas保持一致,我们根据canvas的 width和height属性来计算出该参数。从而使得canvas大小发生变化也不会导致显示出来的图变形
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);
// 将试图矩阵和投影矩阵传递给参数
gl.uniformMatrix4fv(u_ViewMatrix,false,viewMatrix.elements);
gl.uniformMatrix4fv(u_ProMatrix,false,projMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,n);
}
function initVertexBuffers(gl) {
var verticesColors = new Float32Array([
// 右侧的3个三角形
0.75,1.0,-4.0,0.4,1.0,0.4,
0.25,-1.0,-4.0,0.4,1.0,0.4,
1.25,-1.0,-4.0,1.0,0.4,0.4,
0.75,1.0,-2.0,1.0,1.0,0.4,
0.25,-1.0,-2.0,1.0,1.0,0.4,
1.25,-1.0,-2.0,1.0,0.4,0.4,
0.75,1.0,0.0,0.4,0.4,1.0,
0.25,-1.0,0.0,0.4,0.4,1.0,
1.25,-1.0,0.0,1.0,0.4,0.4,
// 左侧的3个三角形
-0.75,1.0,-4.0,0.4,1.0,0.4,
-1.25,-1.0,-4.0,0.4,1.0,0.4,
-0.25,-1.0,-4.0,1.0,0.4,0.4,
-0.75,1.0,-2.0,1.0,1.0,0.4,
-1.25,-1.0,-2.0,1.0,1.0,0.4,
-0.25,-1.0,-2.0,1.0,0.4,0.4,
-0.75,1.0,0.0,0.4,0.4,1.0,
-1.25,-1.0,0.0,0.4,0.4,1.0,
-0.25,-1.0,0.0,1.0,0.4,0.4,
])
var n = 18;
// 创建缓冲区对象
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');
// 将缓冲区对象分配给 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;
}
投影矩阵的作用(矩阵为什么可以定义可是空间)
- 我们看到三角形看上去变小了,也在被不同程度的想着中心新(视线)平移,使得看上去在视线的左右排成了两列。实际上这些三角形完全是相同的,透视矩阵对三小型进行了两次变换:
(1) 根据三小型与视点的距离,按比例对三角形进行了缩小变换;
(2) 对三角形进行偏移变换。
这表明可是空间的规范可以用一系列基本变换(如平移,缩放)来定义。
一个模型矩阵,视图矩阵和投影矩阵的综合例子
我们上面敲了六个三角形的顶点,真是太繁琐了,如何能比较高效的实现呢?
- 沿着Z轴准备三个三角形的顶点数据
- 将其沿着X轴正方向平移0.75单位,绘制这些三角形
- 将其沿着X轴负方向平移0.75单位,绘制这些三角形
// 顶点着色器
var VSHANER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProMatrix;
varying vec4 v_Color;
void main() {
gl_Position =u_ProMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
v_Color = a_Color;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_ModelMatrix = gl.getUniformLocation(gl.program,'u_ModelMatrix');
var u_ViewMatrix = gl.getUniformLocation(gl.program,'u_ViewMatrix');
var u_ProMatrix = gl.getUniformLocation(gl.program,'u_ProMatrix');
// 模型矩阵
var modelMatrix = new Matrix4();
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
// 投影矩阵
var projMatrix = new Matrix4();
// 计算视图矩阵和投影矩阵
modelMatrix.setTranslate(0.75,0,0);// 平移0.75单位
viewMatrix.setLookAt(0,0,5,0,0,-200,0,1,0);
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);
// 将试图矩阵和投影矩阵传递给参数
gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
gl.uniformMatrix4fv(u_ViewMatrix,false,viewMatrix.elements);
gl.uniformMatrix4fv(u_ProMatrix,false,projMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,n);
modelMatrix.setTranslate(-0.75,0,0);
gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
gl.drawArrays(gl.TRIANGLES,0,n);
}
function initVertexBuffers(gl) {
var verticesColors = new Float32Array([
0.0,1.0,-4.0,0.4,1.0,0.4,
-0.5,-1.0,-4.0,0.4,1.0,0.4,
0.5,-1.0,-4.0,1.0,0.4,0.4,
0.0,1.0,-2.0,1.0,1.0,0.4,
-0.5,-1.0,-2.0,1.0,1.0,0.4,
0.5,-1.0,-2.0,1.0,0.4,0.4,
0.0,1.0,0.0,0.4,0.4,1.0,
-0.5,-1.0,0.0,0.4,0.4,1.0,
0.5,-1.0,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');
// 将缓冲区对象分配给 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;
}
也可以在js里面 mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);用一个矩阵的相乘 提前就算出模型视图投影矩阵(model view projection matrix) 这样可以优化性能。
八、正确处理对象的前后关系
webgl 在默认的情况下按照缓冲区中的顺序绘制图形,而且后悔值的图形覆盖先绘制的图形,这是为了提高效率。如果视点不动的话提前定义图形的顺序是没有问题的,但是如果从不同的角度看物体,那就不能这样设定。
隐藏面消除(hidden surface removal)
为了解决这个问题,webgl提供了隐藏慢消除的功能。开启这一功能需要两步。
- 开始隐藏面消除功能 gl.enable(gl.DEPTH_TEST);
- 在绘制之前清除深度缓冲区 gl.clear(gl.DEPTH_BUFFER_BIT);
深度缓冲区的租用是 在片元着色器将图形绘制到颜色缓冲区上之前,如果开启了隐藏面消除,则要进行深度检测(判断每个像素的深度,来决定是否将这个像素画出来),深度缓冲区用来存储像素的深度信息。 (如果要同时清除深度缓冲区和颜色缓冲区 可以使用 ‘|’ 符号进行连接)
// 顶点着色器
var VSHANER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_MvpMatrix;
varying vec4 v_Color;
void main() {
gl_Position =u_MvpMatrix * a_Position;
v_Color = a_Color;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
// 模型矩阵
var modelMatrix = new Matrix4();
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
// 投影矩阵
var projMatrix = new Matrix4();
// 混合投影
var mvpMatrix = new Matrix4();
// 计算视图矩阵和投影矩阵
modelMatrix.setTranslate(0.75,0,0);// 平移0.75单位
viewMatrix.setLookAt(0,0,5,0,0,-200,0,1,0);
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);
mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
// 将试图矩阵和投影矩阵传递给参数
gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
gl.clearColor(0.0,0.0,0.0,1.0);
// 开启隐藏面消除(注释掉这句话试试???)
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,n);
modelMatrix.setTranslate(-0.75,0,0);
mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
gl.drawArrays(gl.TRIANGLES,0,n);
}
function initVertexBuffers(gl) {
var verticesColors = new Float32Array([
0.0,1.0,0.0,0.4,1.0,0.4,
-0.5,-1.0,0.0,0.4,1.0,0.4,
0.5,-1.0,0.0,1.0,0.4,0.4,
0.0,1.0,-2.0,1.0,1.0,0.4,
-0.5,-1.0,-2.0,1.0,1.0,0.4,
0.5,-1.0,-2.0,1.0,0.4,0.4,
0.0,1.0,-4.0,0.4,0.4,1.0,
-0.5,-1.0,-4.0,0.4,0.4,1.0,
0.5,-1.0,-4.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');
// 将缓冲区对象分配给 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;
}
深度冲突
当几何图形或者物体的两个表面极为接近时就会出现新的问题,表面看上去斑驳不堪,这种现行成为深度冲突(修改上面代码三角形的Z轴的值改成一样的 可以看到类似效果)。
- webgl提供一种被成为多边形偏移(polygon offset)的机制来解决这个问题。该机制自动的在Z职上加一个偏移量,偏移量的值由物体表面相对于观察者实现的角度来确定。实现这一机制需要两步:
- 启用多边形偏移 gl.enable(gl.POLYGON_OFFSET_FILL);
- 在绘制执勤啊指定用来计算偏移量的参数 gl.polygonOffset(1.0,1.0);
有些浏览器会自动处理深度冲突问题
九、 立方体
之前我们都是通过绘制简单的三角形展示Webgl的诸多特性,下面我们来真正的进入立方体的绘制。之前我们使用gl.drawArrays()方法进行绘制,如果使用这个方法我们需要绘制两个三小型成为一个矩形表面,然后绘制出所有的面组成立方体。这种方法需要12个三角形 也就是36个顶点。但是实际上一个正方体只需要8个顶点。所以这种方法显然是低效的。webgl提供了一个新的函数 gl.drawElements(),来避免重复的定氮仪顶点,保持顶点数量最小。
我们要将三角形列表传入到gl.ELEMENT_ARRAY_BUFFER中,它管理者具有素银结构的三维模型数据。 下图是正方体的索引结构的三维模型数据。
// 第一个立方体
// 顶点着色器
var VSHANER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_MvpMatrix;
varying vec4 v_Color;
void main() {
gl_Position =u_MvpMatrix * a_Position;
v_Color = a_Color;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
gl.clearColor(0.0,0.0,0.0,1.0);
// 开启隐藏面消除
gl.enable(gl.DEPTH_TEST);
var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
// 混合投影
var mvpMatrix = new Matrix4();
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
// 投影矩阵
var projMatrix = new Matrix4();
viewMatrix.setLookAt(3,3,7,0,0,0,0,1,0);
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);
mvpMatrix.set(projMatrix).multiply(viewMatrix);
// 将试图矩阵和投影矩阵传递给参数
gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
// 开启偏移
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES,n,gl.UNSIGNED_BYTE,0);
}
function initVertexBuffers(gl) {
var verticesColors = new Float32Array([
// 立方体的顶点坐标和颜色
1.0,1.0,1.0,1.0,1.0,1.0,
-1.0,1.0,1.0,1.0,0.0,1.0,
-1.0,-1.0,1.0,1.0,0.0,0.0,
1.0,-1.0,1.0,1.0,1.0,0.0,
1.0,-1.0,-1.0,0.0,1.0,0.0,
1.0,1.0,-1.0,0.0,1.0,1.0,
-1.0,1.0,-1.0,0.0,0.0,1.0,
-1.0,-1.0,-1.0,0.0,0.0,0.0
])
// 顶点索引
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;
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
// 将缓冲区对象分配给 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);
// 主要区别点
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);
return indices.length;
}
这个方法通过循环利用顶点信息,控制内存的开销,但是代价是你需要通过索引来简介的访问顶点,使得程序复杂化了。下图展示了使用索引缓冲区后webgl的内部状态。
十、为每个面指定颜色
我们知道 如果想指定颜色,必须要将颜色定义为逐顶点信息,并传入到顶点着色器中,也就是说你想让一个面为蓝色,那这个面的四个顶点都必须为蓝色。但是我们的顶点是公用的,如果改变一个面的四个顶点为蓝色,那其他面也会染上蓝色。这不是我们想要。那如何解决呢
- 解决方法:创建多个具有相同顶点坐标的顶点,其实就是允余的定义顶点。如下图
// 顶点着色器
var VSHANER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_MvpMatrix;
varying vec4 v_Color;
void main() {
gl_Position =u_MvpMatrix * a_Position;
v_Color = a_Color;
}
`;
// 片元着色器
var FSHADER_SOURCE =
`
precision highp float; // 需要声明浮点数精度否则报错
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
function main() {
let canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if(!gl){
console.log('error');
return;
}
// 初始化着色器
if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
console.log('error');
return;
}
var n = initVertexBuffers(gl);
if(n<0){
console.log("error");
return;
}
gl.clearColor(0.0,0.0,0.0,1.0);
// 开启隐藏面消除
gl.enable(gl.DEPTH_TEST);
var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
// 混合投影
var mvpMatrix = new Matrix4();
// 设置视点 视线 和上方向
var viewMatrix = new Matrix4();
// 投影矩阵
var projMatrix = new Matrix4();
viewMatrix.setLookAt(3,3,7,0,0,0,0,1,0);
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);
mvpMatrix.set(projMatrix).multiply(viewMatrix);
// 将试图矩阵和投影矩阵传递给参数
gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
// 开启偏移
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES,n,gl.UNSIGNED_BYTE,0);
}
function initVertexBuffers(gl) {
let v0 = [1.0,1.0,1.0];
let v1 = [-1.0,1.0,1.0];
let v2 = [-1.0,-1.0,1.0];
let v3 = [1.0,-1.0,1.0];
let v4 = [1.0,-1.0,-1.0];
let v5 = [1.0,1.0,-1.0];
let v6 = [-1.0,1.0,-1.0];
let v7 = [-1.0,-1.0,-1.0];
// 这个坐标为什么是这样 对照上图慢慢理解一下
var vertices = new Float32Array([
// 立方体的顶点坐标和颜色
...v0,...v1,...v2,...v3,
...v0,...v3,...v4,...v5,
...v0,...v5,...v6,...v1,
...v1,...v6,...v7,...v2,
...v2,...v3,...v4,...v7,
...v4,...v7,...v6,...v5
])
// 顶点索引
var indices = new Uint8Array([
0,1,2,0,2,3, // 前
4,5,6,4,6,7, // 右
8,9,10,8,10,11, // 上
12,13,14,12,14,15, // 左
16,17,18,16,18,19, // 下
20,21,22,20,22,23 // 后
])
var color = new Float32Array([
0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,
0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,0.4,1.0,0.4,
1.0,0.4,0.4, 1.0,0.4,0.4, 1.0,0.4,0.4, 1.0,0.4,0.4,
0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,
1.0,1.0,1.0, 1.0,1.0,1.0, 1.0,1.0,1.0, 1.0,1.0,1.0,
0.4,1.0,1.0,0.4,1.0,1.0,0.4,1.0,1.0,0.4,1.0,1.0
])
// 创建缓冲区对象
var indexBuffer = gl.createBuffer();
if(!initArrayBuffer(gl,vertices,3,gl.FLOAT,'a_Position')){
return -1;
}
if(!initArrayBuffer(gl,color,3,gl.FLOAT,'a_Color')){
return -1;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer(gl,data,num,type,attribute) {
// 创建缓冲区对象
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
var a_attribute = gl.getAttribLocation(gl.program,attribute);
// 将缓冲区对象分配给 a_Position变量
gl.vertexAttribPointer(a_attribute,num,type,false,0,0);
gl.enableVertexAttribArray(a_attribute);
return true;
}
如果将所有的颜色改成白色会是神马效果呢? 自己试一下。 看不出是什么东西了,因为颜色没有差异后变得界限也就没有了,但是我们在现实生活中一个白色的盒子还是可以看出是一个正方体,那是因为有光照产生了颜色的细微差异。 下一章 我们学习如果运用三维场景中的光照。