WebGL系列教程九(动画)

快速导航(持续更新中)

WebGL系列教程一(开篇)
WebGL系列教程二(环境搭建及着色器初始化)
WebGL系列教程三(使用缓冲区绘制三角形)
WebGL系列教程四(绘制彩色三角形)
WebGL系列教程五(使用索引绘制彩色立方体)
WebGL系列教程六(纹理映射与立方体贴图)
WebGL系列教程七(二维及三维旋转、平移、缩放)
WebGL系列教程八(GLSL着色器基础语法)
WebGL系列教程九(动画)
WebGL系列教程十(模型Model、视图View、投影Projection变换)
WebGL系列教程十一(光照原理及Blinn Phong着色模型)

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 动画思路

  现在我们要让这个立方体动起来。那怎么动呢?让立方体始终旋转就可以了。那怎么让它旋转呢?乘以我们之前讲到的旋转矩阵就可以了。那怎么让它一直动呢?用定时器可以实现,每秒都去执行,但是还有性能更好的实现方案,那就是requestAnimationFramerequestAnimationFrame能够让浏览器每一帧都去调用一个函数。

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轴旋转,形成了一个简单的动画。本篇的内容结合了之前讲过的所有内容,是一个比较综合的例子,希望读者仔细体会,回见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AIGIS.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值