原生webgl學習(八) WebGL實現動畫:平移、縮放和旋轉
筆者在前面的文章主要是針對二維的靜態圖形進行開發;但有時候我們需要模型動起來,就像真實世界中的一切運動變化一樣。場景如果不是動態的,那麼可想而知,我們的世界是多麼枯燥乏味。為了讓我們開發的圖形應用看上去更加高大上,這一節筆者將和大家一起做一個動畫的例子;本節的內容用到了前面文章提到過的平移、縮放和旋轉變換,如果對這方面還不是很熟悉的同學,可以參考這篇文章:原生webgl學習(三) WebGL中的矩陣運算:平移、旋轉和縮放。圖形動畫的根本在於改變圖形頂點的位置,打個比方,例如現在的你坐或站在一個位置,咱以這個位置為座標系原點,下一刻你起身走動,你所處的位置也就發生了變化,跟著組成你身體的頂點位置也相對於座標系發生了偏移,這種偏移並不是說你身上少了一塊肉或者多了一塊肉,這只是你在世界座標系的位置發生了變化而已,如果你持續不斷的在走動,就構成了我們所認知的動畫,所以動畫的本質還是頂點在某個座標系內位置發生了變化,這種變化是持續不斷的。如下圖所示:
如果對筆者的部落格上的程式碼編寫方式還不是很熟悉請參考筆者其他的博文:原生 WebGL開發文章目錄(持續更新),裡面的文章對程式碼有詳細註釋和原理解釋。說到這裡,相信大家已經期待看見動畫的效果,筆者只做了一個gif圖,呈現了本文demo的動畫實現過程,忽略上面的網址(一個錄屏軟體生成的水印)。
接下來我們一起來看一下程式碼,我們主要看關鍵功能實現的程式碼,如果需要完整程式碼,讀者可以自行下載:https://gitee.com/babyogl/learnWebGL,本例程式碼在資料夾chapter-04中的animate-rotating.html。首先,我們先定義一些與動畫相關的變數:
let translate = [100, 400];//初始平移
const TRANSLATE_STEP = 100;//平移幅度
let angle = 0;//旋轉角
const ANGLE_STEP = 1.0;//旋轉幅度
let scale = [1, 1];//縮放比例
其次,要將定義的平移、旋轉和縮放的值傳遞給著色器:
let tMatrix = m3.translation(translate[0], translate[1]); let rMatrix = m3.rotation(angle); let sMatrix = m3.scaling(scale[0],scale[1]); var matrix = m3.multiply(tMatrix, rMatrix); matrix = m3.multiply(matrix, sMatrix);
接下來,實現圖形旋轉、平移和縮放動畫的功能:
//實現旋轉
let r_last = Date.now();//記錄開始時間
function updateAngle(angle) {
let now = Date.now();
let elapsed = now - r_last;
r_last = now;
let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
//實現平移
let t_last = Date.now();//記錄開始時間
function updateTranslate(x, y) {
let now = Date.now();
let elapsed = now - t_last;
t_last = now;
let dx = x + elapsed * TRANSLATE_STEP / 1000;
let tx = dx;
if (tx > 800) {
tx = tx - dx;
scale[0] = 1;
scale[1] = 1;
}
let translate =[tx, y];
return translate;
}
//實現縮放
function updateSale(sx, sy) {
sx += Math.random() / 200;
sy += Math.random() / 200;
return [sx, sy];
}
最後我們通過resquestAnimationFrame功能實現持續不斷的渲染功能,使場景看起來一直在動:
let animate = function() {
angle = updateAngle(angle);
translate = updateTranslate(translate[0], translate[1]);
scale = updateSale(scale[0], scale[1]);
drawScene(gl, program);
requestAnimationFrame(animate);
};
整個domo程式碼碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>旋轉動畫</title>
<style>
body {
margin: 0px;
overflow: hidden;
}
canvas {
border: 3px solid blue;
display: block;
}
</style>
</head>
<body>
<canvas id="rotating" width="1000" height="800"></canvas>
<script type="text/javascript" src="../libs/webgl-utils.js"></script>
<script type="text/javascript" src="../libs/shader.js"></script>
<script id="vShader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
varying vec4 v_color;
void main()
{
vec2 position = (u_matrix * vec3(a_position, 1.0)).xy;
vec2 zeroToone = position / u_resolution;
vec2 clipSpace = zeroToone * 2.0 - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_color = vec4(gl_Position.xyz*0.5, 0.6);
}
</script>
<script id="fShader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_color;
void main()
{
gl_FragColor = v_color.gbra;
}
</script>
<script type="text/javascript">
"use strict";
let translate = [100, 400];//初始平移
const TRANSLATE_STEP = 100;//平移幅度
let angle = 0;//旋轉角
const ANGLE_STEP = 1.0;//旋轉幅度
let scale = [1, 1];//縮放比例
//3*3矩陣運算
let m3 = {
identity: function() {
return [
1, 0, 0,
0, 1, 0,
0, 0, 1
]
},
translation: function(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
]
},
rotation: function(angle) {
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
c, -s, 0,
s, c, 0,
0, 0, 1
]
},
scaling: function(sx, sy) {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1
]
},
multiply: function(a, b) {
let a00 = a[0 * 3 + 0];
let a01 = a[0 * 3 + 1];
let a02 = a[0 * 3 + 2];
let a10 = a[1 * 3 + 0];
let a11 = a[1 * 3 + 1];
let a12 = a[1 * 3 + 2];
let a20 = a[2 * 3 + 0];
let a21 = a[2 * 3 + 1];
let a22 = a[2 * 3 + 2];
let b00 = b[0 * 3 + 0];
let b01 = b[0 * 3 + 1];
let b02 = b[0 * 3 + 2];
let b10 = b[1 * 3 + 0];
let b11 = b[1 * 3 + 1];
let b12 = b[1 * 3 + 2];
let b20 = b[2 * 3 + 0];
let b21 = b[2 * 3 + 1];
let b22 = b[2 * 3 + 2];
return [
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
];
}
};//3*3矩陣
let position = [
0, -100,
150, 125,
-175, 100
];//三角形頂點位置
function main() {
//獲取canvas元素,並判斷瀏覽器是否支援webgl
let canvas = document.getElementById('rotating');
let gl = canvas.getContext('webgl', {antialias:true, depth: false});
if(!gl) {
alert("您的瀏覽器不支援WebGL!");
}
//建立、編譯並連線著色器
let vShaderText = document.getElementById('vShader').text;
let fShaderText = document.getElementById('fShader').text;
let program = initShader(gl, vShaderText, fShaderText);
//獲取著色器中相關變數的位置
program.pLocation = gl.getAttribLocation(program, 'a_position');
program.reLocation = gl.getUniformLocation(program, 'u_resolution');
program.mLocation = gl.getUniformLocation(program, 'u_matrix');
//建立緩衝區,並繫結
program.pBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, program.pBuffer);
//動畫
let animate = function() {
angle = updateAngle(angle);
translate = updateTranslate(translate[0], translate[1]);
scale = updateSale(scale[0], scale[1]);
drawScene(gl, program);
requestAnimationFrame(animate);
};
animate();
}
//畫圖
function drawScene(gl, program) {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.enableVertexAttribArray(program.pLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, program.pBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
gl.vertexAttribPointer(program.pLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(program.reLocation, gl.canvas.width, gl.canvas.height);
//平移、旋轉、縮放運算
let tMatrix = m3.translation(translate[0], translate[1]);
let rMatrix = m3.rotation(angle);
let sMatrix = m3.scaling(scale[0],scale[1]);
var matrix = m3.multiply(tMatrix, rMatrix);
matrix = m3.multiply(matrix, sMatrix);
gl.uniformMatrix3fv(program.mLocation, false, matrix);
gl.drawArrays(gl.TRIANGLES, 0, position.length / 2);
}
//實現旋轉
let r_last = Date.now();//記錄開始時間
function updateAngle(angle) {
let now = Date.now();
let elapsed = now - r_last;
r_last = now;
let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
//實現平移
let t_last = Date.now();//記錄開始時間
function updateTranslate(x, y) {
let now = Date.now();
let elapsed = now - t_last;
t_last = now;
let dx = x + elapsed * TRANSLATE_STEP / 1000;
let tx = dx;
if (tx > 800) {
tx = tx - dx;
scale[0] = 1;
scale[1] = 1;
}
console.log(tx);
// let ty = y + elapsed * TRANSLATE_STEP / 1000;
let translate =[tx, y];
return translate;
}
//實現縮放
function updateSale(sx, sy) {
sx += Math.random() / 200;
sy += Math.random() / 200;
return [sx, sy];
}
main();
</script>
</body>
</html>