WebGLとは?
前項までの内容を,Webブラウザ上で実行してみる.
ソースコードはどのように分けて書いても良いのだが,一番楽なのが,OpenGLのコード自体はJavaScriptファイルに,GLSLのソースコードはhtmlファイル内に記述する,という方法である.(実際の製品アプリケーションではGLSLのソースコードを露出させるこのような形は取らないが,一番楽)
WebGL用のglm
前項で変換行列生成に用いたglmであるが,glmはC++用のライブラリなので使えない.
様々なJavaScript用のglm類似ライブラリが存在するが,この授業では日本語でアクセスできるWebGLの教科書サイトであるhttps://wgld.orgで提供されているminMatrix.jsを用いる.
httpsで接続する必要
現在JavaScriptはhttpsによるセキュリティを高めた接続でないと実行されない.実験用Webサーバの自分のスペース,もしくはローカルサーバにファイルをアップした上で,Webブラウザでアクセスする必要がある.
最初のWebGLのコード
HTMLファイル
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>とりあえず動かすWebGL</title>
<script src="https://wgld.org/j/minMatrix.js"></script>
<script id="vertex_shader" type="x-shader/x-vertex">
attribute vec3 vertexPositionFromMain;
attribute vec3 vectorNormalFromMain;
attribute vec4 vertexColorFromMain;
uniform mat4 mvpMatrix;
varying vec4 vertexColorInShader;
varying vec3 vectorNormalInShader;
void main(void){
vertexColorInShader = vertexColorFromMain;
vectorNormalInShader = vectorNormalFromMain;
gl_Position = mvpMatrix * vec4(vertexPositionFromMain, 1.0);
}
</script>
<script id="fragment_shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vertexColorInShader;
void main(void){
gl_FragColor = vertexColorInShader;
}
</script>
<script type="text/javascript" src="FirstWebGL.js">
</script>
</head>
<body>
<div id="view">
<canvas id="gl_canvas"></canvas>
</div>
<div id="controller">
<input type="button" name="startButton" value="start" onclick="window.drawLoop()" />
<input type="button" name="stopButton" value="stop" onclick="window.stopDrawLoop()" />
</div>
</body>
</html>
JavaScriptファイル
'use strict';
class MyFirstWebGL {
constructor(canvasWidth, canvasHeight, canvasId){
// GLのコンテキストを取得
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.canvas = document.getElementById(canvasId);
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
// 頂点シェーダとフラグメントシェーダを読み込んでコンパイルする
this.vertexShader = this.createShader('vertex_shader');
this.fragmentShader = this.createShader('fragment_shader');
this.program =
this.createProgram(this.vertexShader, this.fragmentShader);
// シェーダ内の頂点用attrib変数を取得する
this.vertexPosAttribLocation =
this.gl.getAttribLocation(this.program, 'vertexPositionFromMain');
this.vertexColorAttribLocation =
this.gl.getAttribLocation(this.program, 'vertexColorFromMain');
// 上記のシェーダ内の受け取る変数がvec3かvec4か
this.vertexPosAttribStride = 3;
this.vertexColorAttribStride = 4;
// perspective用のパラメータの初期値
this.perspectiveAngle = 45.0; //画角
this.perspectiveArea = new Array(0.1, 100000.0); //表示領域: 手前と奥
// lookAt様のパラメータの初期値
// カメラの位置x, y, z座標,
// カメラが見ているポイントのx, y, z: 0, 0, 0だと原点
// カメラの上がどこか: 0.0, 1.0, 0.0
this.cameraPosXDirection = 1.0;
this.lookAtArray = new Array([4.0, 3.0, 3.0],
[0.0, 0.0, 0.0], //原点
[0.0, 1.0, 0.0]);
this.mativ = new matIV();
this.projectionMatrix = this.mativ.identity(this.mativ.create());
this.mativ.perspective(this.perspectiveAngle,
this.canvasWidth / this.canvasHeight,
this.perspectiveArea[0], this.perspectiveArea[1],
this.projectionMatrix);
this.modelMatrix = this.mativ.identity(this.mativ.create());
// 頂点座標
// x1, y1, z1, x2, y2, z2, x3, y3, z3という感じで並べる
this.vertexPosArray = new Array(-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
0.0, 1.0, 0.0);
// 頂点色情報
// r1, g1, b1, alfa1, r2, b2....
this.vertexColorArray = new Array(1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0);
}
draw(){
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// 視点変更
this.viewMatrix = this.mativ.identity(this.mativ.create());
this.lookAtArray[0][0] += 0.1 * this.cameraPosXDirection;
this.mativ.lookAt(this.lookAtArray[0],
this.lookAtArray[1],
this.lookAtArray[2],
this.viewMatrix);
if(this.lookAtArray[0][0] > 4.0)
this.cameraPosXDirection = -1;
else if(this.lookAtArray[0][0] < -4.0)
this.cameraPosXDirection = 1;
// MVP行列の作成
let vpMatrix = this.mativ.identity(this.mativ.create());
this.mativ.multiply(this.projectionMatrix,
this.viewMatrix,
vpMatrix);
this.mvpMatrix = this.mativ.identity(this.mativ.create());
this.mativ.multiply(vpMatrix, this.modelMatrix, this.mvpMatrix);
// VBOの作成
this.vboPos = this.createVBO(this.vertexPosArray);
this.vboColor = this.createVBO(this.vertexColorArray);
// VBOをバインド
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vboPos);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vboColor);
// attribute属性を有効にする
this.gl.enableVertexAttribArray(this.vertexPosAttribLocation);
this.gl.enableVertexAttribArray(this.vertexColorAttribLocation);
// attribute属性を登録
this.gl.vertexAttribPointer(this.vertexPosAttribLocation,
this.vertexPosAttribStride,
this.gl.FLOAT,
false, 0, 0);
this.gl.vertexAttribPointer(this.vertexColorAttribLocation,
this.vertexColorAttribStride,
this.gl.FLOAT,
false, 0, 0);
// シェーダ用のMVP行列のuniform変数領域を取得する
this.mvpMatrixUniformLocation =
this.gl.getUniformLocation(this.program, 'mvpMatrix');
// シェーダ内のMVP行列へ登録
this.gl.uniformMatrix4fv(this.mvpMatrixUniformLocation,
false,
this.mvpMatrix);
// 頂点の描画
this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
//
this.gl.flush();
}
createShader(id){
// シェーダを格納する変数
let shader;
// HTMLからscriptタグへの参照を取得
let scriptElement = document.getElementById(id);
// scriptタグが存在しない場合は抜ける
if(!scriptElement){return;}
// scriptタグのtype属性をチェック
switch(scriptElement.type){
// 頂点シェーダの場合
case 'x-shader/x-vertex':
shader = this.gl.createShader(this.gl.VERTEX_SHADER);
break;
// フラグメントシェーダの場合
case 'x-shader/x-fragment':
shader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
break;
default :
return;
}
// 生成されたシェーダにソースを割り当てる
this.gl.shaderSource(shader, scriptElement.text);
// シェーダをコンパイルする
this.gl.compileShader(shader);
// シェーダが正しくコンパイルされたかチェック
if(this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){
// 成功していたらシェーダを返して終了
return shader;
}else{
// 失敗していたらエラーログをアラートする
console.log(this.gl.getShaderInfoLog(shader));
}
}
createProgram(vs, fs){
// プログラムオブジェクトの生成
let program = this.gl.createProgram();
// プログラムオブジェクトにシェーダを割り当てる
this.gl.attachShader(program, vs);
this.gl.attachShader(program, fs);
// シェーダをリンク
this.gl.linkProgram(program);
// シェーダのリンクが正しく行なわれたかチェック
if(this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){
// 成功していたらプログラムオブジェクトを有効にする
this.gl.useProgram(program);
// プログラムオブジェクトを返して終了
return program;
}else{
// 失敗していたらエラーログをアラートする
console.log(this.gl.getProgramInfoLog(program));
}
}
createVBO(vertexArray){
let vbo = this.gl.createBuffer();
// バッファをバインドする
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vbo);
// バッファにデータをセット
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertexArray), this.gl.STATIC_DRAW);
// バッファのバインドを無効化
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
// 生成した VBO を返して終了
return vbo;
}
}
window.addEventListener('load', () => {
window.myWebGL = new MyFirstWebGL(640, 480, 'gl_canvas');
});
window.drawLoop = () => {
window.webGlAnimationId =
window.requestAnimationFrame(window.drawLoop);
window.myWebGL.draw();
};
window.stopDrawLoop = () => {
window.cancelAnimationFrame(window.webGlAnimationId);
}