目录
- 1 前言
- 2 绘制立方体并进行纹理映射
- 3 动画思路
- 4 开始绘制
- 4.1 在顶点着色器中声明旋转矩阵
- 4.2 获取旋转矩阵变量并进行赋值
- 4.3 计算角度
- 4.4 每一帧都去绘制
- 4.5 效果
- 4.6 完整代码
- 5 总结
1 前言
上一篇我们讲了WebGL
中的基础语法,现在我们已经讲过了三维物体的绘制,着色及纹理映射,现在我们可以讲一些稍微高级点的操作了,这一节我们来讲动画,我们考虑怎么让一个立方体动起来。
2 绘制立方体并进行纹理映射
这一节的代码是在WebGL系列教程六(纹理映射与立方体贴图)的基础上修改而来的,因此绘制立方体和纹理映射的代码我们直接拿过来。
<script id="vertex-shader" type="x-shader/x-vertex">
//顶点位置
attribute vec4 a_Position;
//纹理坐标
attribute vec2 a_TexCoord;
//传递纹理坐标
varying vec2 v_TexCoord;
void main(){
gl_Position = a_Position;
//直接将纹理坐标赋值给传递变量
v_TexCoord = a_TexCoord;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision highp float;
//采样器,固定写法
uniform sampler2D u_Sampler;
//接收顶点着色器传过来的值
varying vec2 v_TexCoord;
void main(){
//到某个纹理坐标去采样,也是固定写法
gl_FragColor = texture2D(u_Sampler,v_TexCoord);
}
</script>
// Create a cube
// v6----- v7
// /| /|
// v1------v0|
// | | | |
// | |v5---|-|v4
// |/ |/
// v2------v3
const verticesColors = new Float32Array([
// 前面
-1.0, -1.0, 1.0, 0.0, 0.0,//v2 图片左下角纹理坐标
1.0, -1.0, 1.0, 1.0, 0.0,//v3 图片左下角纹理坐标
1.0, 1.0, 1.0, 1.0, 1.0,//v0 图片右下角纹理坐标
-1.0, 1.0, 1.0, 0.0, 1.0,//v1 图片左上角纹理坐标
// 后面
-1.0, -1.0, -1.0, 0.0, 0.0,//v5 同上
1.0, -1.0, -1.0, 1.0, 0.0,//v4 同上
1.0, 1.0, -1.0, 1.0, 1.0,//v7 同上
-1.0, 1.0, -1.0, 0.0, 1.0,//v6 同上
// 上面
-1.0, 1.0, 1.0, 0.0, 0.0,//v1 同上
1.0, 1.0, 1.0, 1.0, 0.0,//v0 同上
1.0, 1.0, -1.0, 1.0, 1.0,//v7 同上
-1.0, 1.0, -1.0, 0.0, 1.0,//v6 同上
// 下面
-1.0, -1.0, 1.0, 0.0, 0.0,//v2 同上
1.0, -1.0, 1.0, 1.0, 0.0,//v3 同上
1.0, -1.0,-1.0, 1.0, 1.0,//v4 同上
-1.0, -1.0,-1.0, 0.0, 1.0,//v5 同上
// 左面
-1.0, -1.0, -1.0, 0.0, 0.0,//v5 同上
-1.0, -1.0, 1.0, 1.0, 0.0,//v2 同上
-1.0, 1.0, 1.0, 1.0, 1.0,//v1 同上
-1.0, 1.0, -1.0, 0.0, 1.0,//v6 同上
// 右面
1.0, -1.0, 1.0, 0.0, 0.0,//v3 同上
1.0, -1.0, -1.0, 1.0, 0.0,//v4 同上
1.0, 1.0, -1.0, 1.0, 1.0,//v7 同上
1.0, 1.0, 1.0, 0.0, 1.0,//v0 同上
]);
const 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 // 右面
]);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.enable(gl.DEPTH_TEST);
//顶点
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER,verticesColors,gl.STATIC_DRAW);
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(program,'a_Position');
gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSIZE*5,0);
gl.enableVertexAttribArray(a_Position);
//指定纹理坐标值
let a_TexCoord = gl.getAttribLocation(program,'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,5*FSIZE,3*FSIZE);
gl.enableVertexAttribArray(a_TexCoord);
let image = new Image();
image.src = 'static/sky.jpg';
image.onload = function(){
console.log('image ok');
//创建纹理对象
let texture = gl.createTexture();
//获取采样器
let u_Sampler = gl.getUniformLocation(program,'u_Sampler');
//反转Y轴,canvas的Y轴和WebGL的Y轴方向是反的
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);
//启用0号纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D,texture);
//设置纹理为,缩小纹理时,取纹理坐标周围四个像素的颜色均值
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
//设置对象使用的图片,mipmap层级,图像的格式,纹理的格式,纹理数据类型,图片
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);
//将0号纹理赋值给采样器
gl.uniform1i(u_Sampler,0);
//绑定索引缓冲
let indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);
//清空颜色缓冲和深度缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//绘制
//顶点索引数组如果是Uint8Array,就是UNSIGNED_BYTE,表示数组里的值在0-2^8-1(255)
//................Uint16Array,就是UNSIGNED_SHORT,表示数组里的值在0-2^16-1(65535)
//................Uint32Array,就是UNSIGNED_INT,表示数组里的值在0-2^32-1(4294967295)
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
};
效果:
3 动画思路
现在我们要让这个立方体动起来。那怎么动呢?让立方体始终旋转就可以了。那怎么让它旋转呢?乘以我们之前讲到的旋转矩阵就可以了。那怎么让它一直动呢?用定时器可以实现,每秒都去执行,但是还有性能更好的实现方案,那就是requestAnimationFrame
。requestAnimationFrame
能够让浏览器每一帧都去调用一个函数。
4 开始绘制
4.1 在顶点着色器中声明旋转矩阵
<script id="vertex-shader" type="x-shader/x-vertex">
//顶点位置
attribute vec4 a_Position;
//旋转矩阵
uniform mat4 uRotateMatrix;
//纹理坐标
attribute vec2 a_TexCoord;
//传递纹理坐标
varying vec2 v_TexCoord;
void main(){
//旋转矩阵乘以顶点的位置,表示每个顶点都经过了旋转
gl_Position = uRotateMatrix * a_Position;
//直接将纹理坐标赋值给传递变量
v_TexCoord = a_TexCoord;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision highp float;
//采样器,固定写法
uniform sampler2D u_Sampler;
//接收顶点着色器传过来的值
varying vec2 v_TexCoord;
void main(){
//到某个纹理坐标去采样,也是固定写法
gl_FragColor = texture2D(u_Sampler,v_TexCoord);
}
</script>
4.2 获取旋转矩阵变量并进行赋值
//创建旋转矩阵
var modelMatrix = new Matrix4();
//绕Y轴旋转60度,0,1,0,表示Y轴上的单位向量
modelMatrix.setRotate(60,0,1,0);
var uRotateMatrix = gl.getUniformLocation(program, 'uRotateMatrix');
gl.uniformMatrix4fv(uRotateMatrix,false,modelMatrix .elements);
4.3 计算角度
var ANGLE_STEP = 30.0;//假设每秒旋转30度
var currentAngle = 0.0; //当前是0度
var g_last = Date.now();//记录一个开始时间
function animate(angle) {
// 计算角度
var now = Date.now();//获取当前时间
var elapsed = now - g_last;//两个时间相减,得到两个时间之差,单位为毫秒
g_last = now;
// 更新角度,一秒是1000毫秒,先算出过了几秒,再乘以角度的步长
var newAngle = angle + ANGLE_STEP * (elapsed / 1000.0);
return newAngle %= 360;
}
4.4 每一帧都去绘制
let tick = function(){
currentAngle = animate(currentAngle);
modelMatrix.setRotate(currentAngle,0,1,0);
//绘制
//略。。。
//每一帧都去调用
requestAnimationFrame(tick);
};
tick();
4.5 效果
4.6 完整代码
const verticesColors = new Float32Array([
// 前面
-1.0, -1.0, 1.0, 0.0, 0.0,//v2 图片左下角纹理坐标
1.0, -1.0, 1.0, 1.0, 0.0,//v3 图片左下角纹理坐标
1.0, 1.0, 1.0, 1.0, 1.0,//v0 图片右下角纹理坐标
-1.0, 1.0, 1.0, 0.0, 1.0,//v1 图片左上角纹理坐标
// 后面
1.0, -1.0, -1.0, 0.0, 0.0,//v4 同上
-1.0, -1.0, -1.0, 1.0, 0.0,//v5 同上
-1.0, 1.0, -1.0, 1.0, 1.0,//v6 同上
1.0, 1.0, -1.0, 0.0, 1.0,//v7 同上
// 上面
-1.0, 1.0, 1.0, 0.0, 0.0,//v1 同上
1.0, 1.0, 1.0, 1.0, 0.0,//v0 同上
1.0, 1.0, -1.0, 1.0, 1.0,//v7 同上
-1.0, 1.0, -1.0, 0.0, 1.0,//v6 同上
// 下面
-1.0, -1.0, 1.0, 0.0, 0.0,//v2 同上
1.0, -1.0, 1.0, 1.0, 0.0,//v3 同上
1.0, -1.0,-1.0, 1.0, 1.0,//v4 同上
-1.0, -1.0,-1.0, 0.0, 1.0,//v5 同上
// 左面
-1.0, -1.0, -1.0, 0.0, 0.0,//v5 同上
-1.0, -1.0, 1.0, 1.0, 0.0,//v2 同上
-1.0, 1.0, 1.0, 1.0, 1.0,//v1 同上
-1.0, 1.0, -1.0, 0.0, 1.0,//v6 同上
// 右面
1.0, -1.0, 1.0, 0.0, 0.0,//v3 同上
1.0, -1.0, -1.0, 1.0, 0.0,//v4 同上
1.0, 1.0, -1.0, 1.0, 1.0,//v7 同上
1.0, 1.0, 1.0, 0.0, 1.0,//v0 同上
]);
const 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 // 右面
]);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.enable(gl.DEPTH_TEST);
//顶点
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER,verticesColors,gl.STATIC_DRAW);
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(program,'a_Position');
gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSIZE*5,0);
gl.enableVertexAttribArray(a_Position);
//指定纹理坐标值
let a_TexCoord = gl.getAttribLocation(program,'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,5*FSIZE,3*FSIZE);
gl.enableVertexAttribArray(a_TexCoord);
let image = new Image();
image.src = 'static/sky.jpg';
// Rotation angle (degrees/second)
let ANGLE_STEP = 30.0;
let currentAngle = 0.0;
let modelMatrix = new Matrix4();
// Last time that this function was called
let g_last = Date.now();
function animate(angle) {
// Calculate the elapsed time
let now = Date.now();
let elapsed = now - g_last;
g_last = now;
// Update the current rotation angle (adjusted by the elapsed time)
let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
image.onload = function(){
let tick = function(){
// console.log('image ok');
currentAngle = animate(currentAngle);
modelMatrix.setRotate(currentAngle,0,1,0);
let texture = gl.createTexture();
let u_Sampler = gl.getUniformLocation(program,'u_Sampler');
//反转Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);
//启用0号纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D,texture);
//设置纹理为,缩小纹理,取纹理坐标周围四个像素的颜色均值
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
//设置对象使用的图片,mipmap层级,图像的格式,纹理的格式,纹理数据类型,图片
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);
//将0号纹理赋值给采样器
gl.uniform1i(u_Sampler,0);
//绑定索引缓冲
let indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);
//赋值给u_MvpMatrix
var uRotateMatrix= gl.getUniformLocation(program, 'uRotateMatrix');
gl.uniformMatrix4fv(uRotateMatrix,false,uRotateMatrix.elements);
//清空颜色缓冲和深度缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//绘制
//顶点索引数组如果是Uint8Array,就是UNSIGNED_BYTE,表示数组里的值在0-2^8-1(255)
//................Uint16Array,就是UNSIGNED_SHORT,表示数组里的值在0-2^16-1(65535)
//................Uint32Array,就是UNSIGNED_INT,表示数组里的值在0-2^32-1(4294967295)
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
requestAnimationFrame(tick, canvas);
};
tick();
};
5 总结
本文中我们讲解了动画的原理,并在绘制的立方体及纹理贴图后,绕Y
轴旋转,形成了一个简单的动画。本篇的内容结合了之前讲过的所有内容,是一个比较综合的例子,希望读者仔细体会,回见~