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. 参考资料

  1. [WebGL入门]三十二,四元数和minMatrixb.js
  2. [WebGL入门]三十一,Quaternions(四元数)

5. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值