WebGL·四元数旋转小实例
1. 四元数旋转
这个小实例实现的是,圆环按照X、Y和Z轴分别旋转一圈,使用的旋转方法是四元数坐标旋转。关于更多的知识,大家可以参考一下 6. 参考资料 中的两篇文章,原实例和源码也是来自那里。
原实例相机只按照X轴进行旋转,并且模型自转。这里我们停用模型自转,但相机需要按照X、Y和Z轴分别旋转一圈,主要思路:
设置一个开关控制旋转轴,相机每绕一个轴旋转一次,则更改旋转轴
2. 代码解析
很简单,过一遍代码思路。初始化开关和每个轴的变量:
// 0 -> x, 1 -> y, 2 -> z
var rotateWhich = 0;
const degreeTwo = 720;
const speed = 5;
const xAxis = [1, 0, 0];
const yAxis = [0, 1, 0];
const zAxis = [0, 0, 1];
超过一圈,重置旋转状态和旋转轴:
// reset status after rotating 360 degrees
if ( count > degreeTwo ) {
count = 0;
rotateWhich = ++rotateWhich % 3;
}
var axis = null;
switch ( rotateWhich ) {
case 0:
axis = xAxis;
break;
case 1:
axis = yAxis;
break;
case 2:
axis = zAxis;
break;
default:
console.assert( true, "Error" );
}
3. 附录:项目代码
3.1 HTML
<!-- saved from url=(0030)https://wgld.org/s/sample_019/ -->
<html lang=""><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>wgld.org WebGL sample 019</title>
<script src="https://wgld.org/j/minMatrixb.js" type="text/javascript"></script>
<script src="script.js" type="text/javascript"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 mvpMatrix;
uniform mat4 mMatrix;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec4 vColor;
void main(void){
vPosition = (mMatrix * vec4(position, 1.0)).xyz;
vNormal = normal;
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform mat4 invMatrix;
uniform vec3 lightPosition;
uniform vec3 eyeDirection;
uniform vec4 ambientColor;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec4 vColor;
void main(void){
vec3 lightVec = lightPosition - vPosition;
vec3 invLight = normalize(invMatrix * vec4(lightVec, 0.0)).xyz;
vec3 invEye = normalize(invMatrix * vec4(eyeDirection, 0.0)).xyz;
vec3 halfLE = normalize(invLight + invEye);
float diffuse = clamp(dot(vNormal, invLight), 0.0, 1.0) + 0.2;
float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);
vec4 destColor = vColor * vec4(vec3(diffuse), 1.0) + vec4(vec3(specular), 1.0) + ambientColor;
gl_FragColor = destColor;
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
</body></html>
3.2 Javascript
// sample_019
//
// WebGLでクォータニオンによるカメラ制御
onload = function(){
// canvasエレメントを取得
var c = document.getElementById('canvas');
c.width = 300;
c.height = 300;
// webglコンテキストを取得
var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
// 頂点シェーダとフラグメントシェーダの生成
var v_shader = create_shader('vs');
var f_shader = create_shader('fs');
// プログラムオブジェクトの生成とリンク
var prg = create_program(v_shader, f_shader);
// attributeLocationを配列に取得
var attLocation = new Array();
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'normal');
attLocation[2] = gl.getAttribLocation(prg, 'color');
// attributeの要素数を配列に格納
var attStride = new Array();
attStride[0] = 3;
attStride[1] = 3;
attStride[2] = 4;
// トーラスデータ
var torusData = torus(64, 64, 0.5, 1.5);
var tPosition = create_vbo(torusData.p);
var tNormal = create_vbo(torusData.n);
var tColor = create_vbo(torusData.c);
var tVBOList = [tPosition, tNormal, tColor];
var tIndex = create_ibo(torusData.i);
set_attribute(tVBOList, attLocation, attStride);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, tIndex);
// uniformLocationを配列に取得
var uniLocation = new Array();
uniLocation[0] = gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1] = gl.getUniformLocation(prg, 'mMatrix');
uniLocation[2] = gl.getUniformLocation(prg, 'invMatrix');
uniLocation[3] = gl.getUniformLocation(prg, 'lightPosition');
uniLocation[4] = gl.getUniformLocation(prg, 'eyeDirection');
uniLocation[5] = gl.getUniformLocation(prg, 'ambientColor');
// 各種行列の生成と初期化
var m = new matIV();
var mMatrix = m.identity(m.create());
var vMatrix = m.identity(m.create());
var pMatrix = m.identity(m.create());
var tmpMatrix = m.identity(m.create());
var mvpMatrix = m.identity(m.create());
var invMatrix = m.identity(m.create());
// 各種四元数の生成と初期化
var q = new qtnIV();
var xQuaternion = q.identity(q.create());
// 点光源の位置
var lightPosition = [15.0, 10.0, 15.0];
// 環境光の色
var ambientColor = [0.1, 0.1, 0.1, 1.0];
// カメラの座標
var camPosition = [0.0, 0.0, 10.0];
// カメラの上方向を表すベクトル
var camUpDirection = [0.0, 1.0, 0.0];
// カウンタの宣言
var count = 0;
// カリングと深度テストを有効にする
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.CULL_FACE);
// 0 -> x, 1 -> y, 2 -> z
var rotateWhich = 0;
const degreeTwo = 720;
const speed = 5;
const xAxis = [1, 0, 0];
const yAxis = [0, 1, 0];
const zAxis = [0, 0, 1];
// 恒常ループ
(function(){
// canvasを初期化
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// カウンタをインクリメントする
count += speed;
// reset status after rotating 360 degrees
if ( count > degreeTwo ) {
count = 0;
rotateWhich = ++rotateWhich % 3;
}
var axis = null;
switch ( rotateWhich ) {
case 0:
axis = xAxis;
break;
case 1:
axis = yAxis;
break;
case 2:
axis = zAxis;
break;
default:
console.assert( true, "Error" );
}
// カウンタを元にラジアンを算出
var rad = (count % 180) * Math.PI / 90;
var rad2 = count * Math.PI / 360;
// クォータニオンによる回転
q.rotate(rad2, axis, xQuaternion);
q.toVecIII([0.0, 0.0, 10.0], xQuaternion, camPosition);
q.toVecIII([0.0, 1.0, 0.0], xQuaternion, camUpDirection);
// ビュー×プロジェクション座標変換行列
m.lookAt(camPosition, [0, 0, 0], camUpDirection, vMatrix);
m.perspective(45, c.width / c.height, 0.1, 100, pMatrix);
m.multiply(pMatrix, vMatrix, tmpMatrix);
// モデル座標変換行列の生成
m.identity(mMatrix);
// commented out the following line to prohibit model's rotation
// m.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
m.inverse(mMatrix, invMatrix);
// uniform変数の登録と描画
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, mMatrix);
gl.uniformMatrix4fv(uniLocation[2], false, invMatrix);
gl.uniform3fv(uniLocation[3], lightPosition);
gl.uniform3fv(uniLocation[4], camPosition);
gl.uniform4fv(uniLocation[5], ambientColor);
gl.drawElements(gl.TRIANGLES, torusData.i.length, gl.UNSIGNED_SHORT, 0);
// コンテキストの再描画
gl.flush();
// ループのために再帰呼び出し
setTimeout(arguments.callee, 1000 / 30);
})();
// シェーダを生成する関数
function create_shader(id){
// シェーダを格納する変数
var shader;
// HTMLからscriptタグへの参照を取得
var scriptElement = document.getElementById(id);
// scriptタグが存在しない場合は抜ける
if(!scriptElement){return;}
// scriptタグのtype属性をチェック
switch(scriptElement.type){
// 頂点シェーダの場合
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);
break;
// フラグメントシェーダの場合
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);
break;
default :
return;
}
// 生成されたシェーダにソースを割り当てる
gl.shaderSource(shader, scriptElement.text);
// シェーダをコンパイルする
gl.compileShader(shader);
// シェーダが正しくコンパイルされたかチェック
if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
// 成功していたらシェーダを返して終了
return shader;
}else{
// 失敗していたらエラーログをアラートする
alert(gl.getShaderInfoLog(shader));
}
}
// プログラムオブジェクトを生成しシェーダをリンクする関数
function create_program(vs, fs){
// プログラムオブジェクトの生成
var program = gl.createProgram();
// プログラムオブジェクトにシェーダを割り当てる
gl.attachShader(program, vs);
gl.attachShader(program, fs);
// シェーダをリンク
gl.linkProgram(program);
// シェーダのリンクが正しく行なわれたかチェック
if(gl.getProgramParameter(program, gl.LINK_STATUS)){
// 成功していたらプログラムオブジェクトを有効にする
gl.useProgram(program);
// プログラムオブジェクトを返して終了
return program;
}else{
// 失敗していたらエラーログをアラートする
alert(gl.getProgramInfoLog(program));
}
}
// VBOを生成する関数
function create_vbo(data){
// バッファオブジェクトの生成
var vbo = gl.createBuffer();
// バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// バッファにデータをセット
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
// バッファのバインドを無効化
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// 生成した VBO を返して終了
return vbo;
}
// VBOをバインドし登録する関数
function set_attribute(vbo, attL, attS){
// 引数として受け取った配列を処理する
for(var i in vbo){
// バッファをバインドする
gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]);
// attributeLocationを有効にする
gl.enableVertexAttribArray(attL[i]);
// attributeLocationを通知し登録する
gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
}
}
// IBOを生成する関数
function create_ibo(data){
// バッファオブジェクトの生成
var ibo = gl.createBuffer();
// バッファをバインドする
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// バッファにデータをセット
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
// バッファのバインドを無効化
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
// 生成したIBOを返して終了
return ibo;
}
};
4. 参考资料
5. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;