WebGL之旅(六)動畫

動畫原理

已經知道如何繪製一個三角形了,利用變換舉證可以讓圖形產生相應的變換。

現在要做的是,繪製一個三角色,不斷的更新旋轉矩陣的角度,然後重繪就會產生旋轉動畫。

實現

/**
 * 旋轉動畫
 * [email protected]
 * */

// 頂點着色器源碼
var vertexShaderSrc = `
attribute vec4 a_Position;// 接收傳入位置座標,必須聲明爲全局
uniform mat4 u_Mat;// 旋轉矩陣
void main(){
    gl_Position = u_Mat * a_Position;
}`;

// 片段着色器源碼
var fragmentShaderSrc = `
void main(){
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);// gl_FragColor 內置變量,表示片元顏色,必須賦值
}`;

// 初始化使用的shader
function initShader(gl) {
    var vertexShader = gl.createShader(gl.VERTEX_SHADER);// 創建頂點着色器
    gl.shaderSource(vertexShader, vertexShaderSrc);// 綁定頂點着色器源碼
    gl.compileShader(vertexShader);// 編譯定點着色器

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);// 創建片段着色器
    gl.shaderSource(fragmentShader, fragmentShaderSrc);// 綁定片段着色器源碼
    gl.compileShader(fragmentShader);// 編譯片段着色器

    var shaderProgram = gl.createProgram();// 創建着色器程序
    gl.attachShader(shaderProgram, vertexShader);// 指定頂點着色器
    gl.attachShader(shaderProgram, fragmentShader);// 指定片段着色色器
    gl.linkProgram(shaderProgram);// 鏈接程序
    gl.useProgram(shaderProgram);//使用着色器

    gl.program = shaderProgram;// 保存shaderProgram到gl,方便後面使用
}

var g_LastTime = null;// 上次繪製的時間
function main() {
    var canvas = document.getElementById("container");
    var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    initShader(gl);// 初始化着色器
    var n = initVertexBuffers(gl);// 初始化頂點

    gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色

    var u_Mat = gl.getUniformLocation(gl.program, 'u_Mat');// 獲取着色器中的矩陣變量

    var speed = Math.PI/4;// 角速度
    var rad = 0.0;// 啓始角度
    var tick = function (timestamp) {
        var delta = g_LastTime ? (timestamp - g_LastTime) / 1000 : 0;// 上次繪製到本次繪製過去的時間(單位轉換算成秒)
        g_LastTime = timestamp;// 保存本次時間
        rad = (rad + speed * delta) % (2 * Math.PI);// 當前的弧度
        draw(gl, n, rad, u_Mat);
        requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);// 請求調用tick
}

function draw(gl, n, rad, u_Mat) {
    gl.uniformMatrix4fv(u_Mat, false, getRotationMatrix(rad, 0, 0, 1));// 設置頂點着色器中變量的值
    gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
    gl.drawArrays(gl.TRIANGLES, 0, n);// 繪製
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0, 0.5,// 第1個點座標
        -0.5, -0.5,// 第2個點座標
        0.5, -0.5,// 第3個點座標
    ]);

    var vertexBuffer = gl.createBuffer();// 創建緩衝區對象
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);// 將緩衝區對象綁定到目標
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);// 向緩衝區中寫入數據

    var a_Position = gl.getAttribLocation(gl.program, "a_Position");// 獲取a_Position變量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);// 將緩衝區對象分配給a_Position
    gl.enableVertexAttribArray(a_Position);// 鏈接a_Position與分配給他的緩衝區對象

    return vertices.length / 2;
}

/**
 * 由旋轉弧度和旋轉軸獲取旋轉矩陣
 * */
function getRotationMatrix(rad, x, y, z) {
    if (x > 0) {
        // 繞x軸的旋轉矩陣
        return new Float32Array([
            1.0, 0.0, 0.0, 0.0,
            0.0, Math.cos(rad), -Math.sin(rad), 0.0,
            0.0, Math.sin(rad), Math.cos(rad), 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else if (y > 0) {
        // 繞y軸的旋轉矩陣
        return new Float32Array([
            Math.cos(rad), 0.0, -Math.sin(rad), 0.0,
            0.0, 1.0, 0.0, 0.0,
            Math.sin(rad), 0.0, Math.cos(rad), 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else if(z > 0) {
        // 繞z軸的旋轉矩陣
        return new Float32Array([
            Math.cos(rad), Math.sin(rad), 0.0, 0.0,
            -Math.sin(rad), Math.cos(rad), 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    } else {
        // 沒有指定旋轉軸,報個錯,返回一個單位矩陣
        console.error("error: no axis");
        return new Float32Array([
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ]);
    }
}

相應於前面的代碼,做了兩個修改:

  1. 加入了定時處理函數tick,然他重複執行;
  2. 加入了繪製函數draw,根據傳入的弧度,得到旋轉矩陣,然後繪製。

requestAnimationFrame函數請求瀏覽器執行一個callback,在調用callback時會傳遞一個參數,表示程序已運行的時間,單位爲毫秒。

解釋:
* 前面我們設置的角速度是每秒旋轉的弧度,在繪製時只需要計算距離上次過去了多長時間;
* 用跟這個時間乘以角速度,就可以知道角度增加了多少;
* 根據當前的弧度加上這個新增的弧度,就可以算出當前的弧度。
* 然後根據這個弧度產生旋轉矩陣,就可以繪製出圖像了。

除此之外,我們還可以給他加上一個平移,複合變換。修改draw函數

function draw(gl, n, rad, u_Mat) {
    var rot = getRotationMatrix(rad, 0, 0, 1);
    var trans = getTranslationMatrix(0.5, 0.0, 0.0);
    var scale = getScaleMatrix(0.3, 0.5, 0.5);
    var model = multiMatrix44(rot, trans);
    model = multiMatrix44(model, scale);
    gl.uniformMatrix4fv(u_Mat, false, model);// 設置頂點着色器中變量的值
    gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
    gl.drawArrays(gl.TRIANGLES, 0, n);// 繪製
}

先縮放,然後平移,再旋轉。

另外,還可以修改旋轉的角速度。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>WebGL</title>
</head>
<body onload="main()">
    <canvas id="container" width="1280px" height="720px"></canvas>
    <br>
    <button onclick="speedUp()">加速</button>
    <button onclick="speedDown()">減速</button>
</body>
</html>
<script type="text/javascript" src="main.js"></script>

添加了兩個按鈕,分別表示加速和減速。

這裏寫圖片描述

在js中,將speed改爲全局g_Speed,然後在按鈕的響應函數中去改變速度。

var g_LastTime = null;// 上次繪製的時間
var g_Speed = Math.PI/4;// 旋轉角速度

function main() {
    var canvas = document.getElementById("container");
    var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    initShader(gl);// 初始化着色器
    var n = initVertexBuffers(gl);// 初始化頂點

    gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色

    var u_Mat = gl.getUniformLocation(gl.program, 'u_Mat');// 獲取着色器中的矩陣變量

    var rad = 0.0;// 啓始角度
    var tick = function (timestamp) {
        var delta = g_LastTime ? (timestamp - g_LastTime) / 1000 : 0;// 上次繪製到本次繪製過去的時間(單位轉換算成秒)
        g_LastTime = timestamp;// 保存本次時間
        rad = (rad + g_Speed * delta) % (2 * Math.PI);// 當前的弧度
        draw(gl, n, rad, u_Mat);
        requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);// 請求調用tick
}

function speedUp() {
    g_Speed += Math.PI/8;
}

function speedDown() {
    g_Speed -= Math.PI/8;
}
發佈了364 篇原創文章 · 獲贊 148 · 訪問量 89萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章