變換又叫仿射變換,包括平移,縮放,旋轉。
一 變換前
首先,我們繪製一個三角形,後面對齊進行變換,代碼:
/**
* 變換前
* [email protected]
* */
// 頂點着色器源碼
var vertexShaderSrc = `
attribute vec4 a_Position;// 接收傳入位置座標,必須聲明爲全局
void main(){
gl_Position = a_Position;// gl_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;
}
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的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
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;
}
如圖:
二 平移變換
平移是指將所有頂點的分量按照某個向量分量進行移動。下面是將上面的三角形沿向量(0.5, 0.5, 0.0)移動的示例。
/**
* 平移變換
* [email protected]
* */
// 頂點着色器源碼
var vertexShaderSrc = `
attribute vec4 a_Position;// 接收傳入位置座標,必須聲明爲全局
uniform vec4 u_Translation;// 平移向量
void main(){
gl_Position = a_Position + u_Translation;// gl_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;
}
function main() {
var canvas = document.getElementById("container");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
initShader(gl);// 初始化着色器
var n = initVertexBuffers(gl);// 初始化頂點
var u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');// 獲取u_Translation變量
// 將u_Translation變量的值設置爲(0.5, 0.5, 0.0, 0.0),最後一個分量爲0.0
// 其次座標最後一個分量爲0.0時,前三個分量表示一個三維座標點
// a_Position的最後一個分量會默認設置爲1.0,爲了使相加之後仍然表示一個點,所以最後一個分量置爲0.0
gl.uniform4f(u_Translation, 0.5, 0.5, 0.0, 0.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
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;
}
平移之後如圖:
三 旋轉
旋轉比移動複雜一點,需要三個變量才能表示:
- 旋轉軸
- 旋轉方向(右手法則旋轉:右手握拳,大拇指指向旋轉軸正向,其餘手指指向旋轉的正方向)
- 旋轉角度
計算原理可以參考我之前的這篇博客。
且看示例:
/**
* 旋轉變換
* [email protected]
* */
// 頂點着色器源碼
var vertexShaderSrc = `
attribute vec4 a_Position;// 接收傳入位置座標,必須聲明爲全局
uniform float u_Sin, u_Cos;// 旋轉角的正餘弦值
void main(){
gl_Position.x = a_Position.x * u_Cos - a_Position.y * u_Sin;
gl_Position.y = a_Position.x * u_Sin + a_Position.y * u_Cos;
gl_Position.z = a_Position.z;
gl_Position.w = 1.0;
}`;
// 片段着色器源碼
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;
}
function main() {
var canvas = document.getElementById("container");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
initShader(gl);// 初始化着色器
var n = initVertexBuffers(gl);// 初始化頂點
var rad = Math.PI * 0.5;// 旋轉角90度
var u_Sin = gl.getUniformLocation(gl.program, 'u_Sin');
gl.uniform1f(u_Sin, Math.sin(rad));
var u_Cos = gl.getUniformLocation(gl.program, 'u_Cos');
gl.uniform1f(u_Cos, Math.cos(rad));
gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
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;
}
結果如圖:
四 縮放
縮放比較簡單,將頂點的每個分乘以一個縮放因子即可。
代碼:
/**
* 伸縮變換
* [email protected]
* */
// 頂點着色器源碼
var vertexShaderSrc = `
attribute vec4 a_Position;// 接收傳入位置座標,必須聲明爲全局
uniform float u_Rate;// 旋轉角的正餘弦值
void main(){
gl_Position.x = a_Position.x * u_Rate;
gl_Position.y = a_Position.y * u_Rate;
gl_Position.z = a_Position.z * u_Rate;
gl_Position.w = 1.0;
}`;
// 片段着色器源碼
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;
}
function main() {
var canvas = document.getElementById("container");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
initShader(gl);// 初始化着色器
var n = initVertexBuffers(gl);// 初始化頂點
var u_Rate = gl.getUniformLocation(gl.program, 'u_Rate');
gl.uniform1f(u_Rate, 0.5);
gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
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;
}
結果:
這裏所有的變換都單一的一種,當各種變換組合疊加起來之後,就需要使用矩陣。
五 矩陣變換
在組合變換之前,首先看看如何用矩陣實現上面幾種變換。
/**
* 使用矩陣變換
* [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;
}
function main() {
var canvas = document.getElementById("container");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
initShader(gl);// 初始化着色器
var n = initVertexBuffers(gl);// 初始化頂點
// 平移矩陣,列主序
// var mat = 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.5, 0.5, 0.0, 1.0,
// ]);
// 旋轉矩陣,列主序
// var rad = Math.PI * 0.5;
// var mat = 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,
// ]);
// 縮放矩陣,列主序
var mat = new Float32Array([
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
var u_Mat = gl.getUniformLocation(gl.program, 'u_Mat');
gl.uniformMatrix4fv(u_Mat, false, mat);
gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0, 0.5,
-0.5, -0.5,
0.5, -0.5,
]);
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 getTranslationMatrix(x, y, z) {
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,
x, y, z, 1.0,
]);
}
/**
* 由旋轉弧度和旋轉軸獲取旋轉矩陣
* */
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,
]);
}
}
/**
* 由縮放因子獲取縮放矩陣
* */
function getScaleMatrix(xScale, yScale, zScale) {
return new Float32Array([
xScale, 0.0, 0.0, 0.0,
0.0, yScale, 0.0, 0.0,
0.0, 0.0, zScale, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
}
然後修改main方法:
function main() {
var canvas = document.getElementById("container");
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
initShader(gl);// 初始化着色器
var n = initVertexBuffers(gl);// 初始化頂點
// 平移矩陣,列主序
// var mat = getTranslationMatrix(0.5, 0.5, 0.0);
// 旋轉矩陣,列主序
// var mat = getRotationMatrix(Math.PI * 0.5, 0.0, 0.0, 1.0);
// 縮放矩陣,列主序
var mat = getScaleMatrix(0.5, 0.5, 1.0);
var u_Mat = gl.getUniformLocation(gl.program, 'u_Mat');
gl.uniformMatrix4fv(u_Mat, false, mat);
gl.clearColor(0.0, 0.0, 0.0, 1.0);// 指定清空canvas的顏色
gl.clear(gl.COLOR_BUFFER_BIT);// 清空canvas
gl.drawArrays(gl.TRIANGLES, 0, n);
}
運行的結果都跟前面的一樣。