WebGL 教科書の写経、その2
複数モデルのレンダリング
https://wgld.org/d/webgl/w016.html
教科書にあるマトリクスのライブラリなのだが、使い方がオブジェクト指向ではなく、たんなるマトリクス関数の集合体だけの使い方のように思えてきた。
そのライブラリは少しずつ自分用のライブラリに移植している。
<!DOCTYPE html> <meta charset="utf-8"> <title>WebGL</title> <style> </style> <body> <canvas width="1024" height="512"</canvas> <!-- ※頂点シェーダ --> <script id="vshader" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main (void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <!-- ※フラグメントシェーダ --> <script id="fshader" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> <script> { class Matrix4 { constructor (ary = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) { let F = Math.fround, M = new Float32Array (16); for (let i = 0; i < 16; i++) M[i] = F (ary[i] || 0); this.M = M; } multiply (mat1) { let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M, [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M; this.M = new Float32Array ([ A * a + B * e + C * i + D * m, A * b + B * f + C * j + D * n, A * c + B * g + C * k + D * o, A * d + B * h + C * l + D * p, E * a + F * e + G * i + H * m, E * b + F * f + G * j + H * n, E * c + F * g + G * k + H * o, E * d + F * h + G * l + H * p, I * a + J * e + K * i + L * m, I * b + J * f + K * j + L * n, I * c + J * g + K * k + L * o, I * d + J * h + K * l + L * p, M * a + N * e + O * i + P * m, M * b + N * f + O * j + P * n, M * c + N * g + O * k + P * o, M * d + N * h + O * l + P * p ]); return this; } scale ([x,y,z]) { let m = this.M; this.M = new Float32Array ([ m[0] * x, m[1] * x, m[2] * x, m[3] * x, m[4] * y, m[5] * y, m[6] * y, m[7] * y, m[8] * z, m[9] * z, m[10]* z, m[11]* z, m[12], m[13], m[14], m[15] ]); return this; } translate ([x, y, z]) { let [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M; this.M = new Float32Array ([ a, b, c, d, e, f, g, h, i, j, k, l, a * x + e * y + i * z + m, b * x + f * y + j * z + n, c * x + g * y + k * z + o, d * x + h * y + l * z + p ]); return this; } rotate (mat, angle, [a, b, c]) { let sq = Math.sqrt(a * a + b * b + c * c); if (! sq) return null; if (sq != 1) { sq = 1 / sq; a *= sq; b *= sq; c *= sq; //?angle } let d = Math.sin (angle), e = Math.cos (angle), f = 1 - e, g = mat[0], h = mat[1], i = mat[2], j = mat[3], k = mat[4], l = mat[5], m = mat[6], n = mat[7], o = mat[8], p = mat[9], q = mat[10], r = mat[11], s = a * a * f + e, t = b * a * f + c * d, u = c * a * f - b * d, v = a * b * f - c * d, w = b * b * f + e, x = c * b * f + a * d, y = a * c * f + b * d, z = b * c * f - a * d, A = c * c * f + e; if (angle) { if(mat != dest) { dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; } } else { dest = mat; } return new Matrix4([ g * s + k * t + o * u, h * s + l * t + p * u, i * s + m * t + q * u, j * s + n * t + r * u, g * v + k * w + o * x, h * v + l * w + p * x, i * v + m * w + q * x, j * v + n * w + r * x, g * y + k * z + o * A, h * y + l * z + p * A, i * y + m * z + q * A, j * y + n * z + r * A ]); } copy () { return new Matrix4 (this.M); } static lookAt (eye, center, up) { let [eyeX, eyeY, eyeZ] = eye, [upX, upY, upZ] = up, [centerX, centerY, centerZ] = center; if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) return (new this).identity (); let sqrt = Math.sqrt, z0 = eyeX - centerX, z1 = eyeY - centerY, z2 = eyeZ - centerZ, l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2); z0 *= l; z1 *= l; z2 *= l; let x0 = upY * z2 - upZ * z1, x1 = upZ * z0 - upX * z2, x2 = upX * z1 - upY * z0; l = sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (! l) { x0 = 0; x1 = 0; x2 = 0; } else { l = 1 / l; x0 *= l; x1 *= l; x2 *= l; } let y0 = z1 * x2 - z2 * x1, y1 = z2 * x0 - z0 * x2, y2 = z0 * x1 - z1 * x0; l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (! l) { y0 = 0; y1 = 0; y2 = 0; } else { l = 1 / l; y0 *= l; y1 *= l; y2 *= l; } return new this ([ x0, y0, z0, 0, x1, y1, z1, 0, x2, y2, z2, 0, -(x0 * eyeX + x1 * eyeY + x2 * eyeZ), -(y0 * eyeX + y1 * eyeY + y2 * eyeZ), -(z0 * eyeX + z1 * eyeY + z2 * eyeZ), 1 ]); } static perspective (fovy, aspect, near, far) { let t = near * Math.tan (fovy * Math.PI / 360), r = t * aspect, a = r * 2, b = t * 2, c = far - near; return new this ([ near * 2 / a, 0, 0, 0, 0, near * 2 / b, 0, 0, 0, 0, -(far + near) / c, -1, 0, 0, -(far * near * 2) / c, 0 ]); } } this.Matrix4 = Matrix4; } function matIV(){ this.create = function(){ return new Float32Array(16); }; this.transpose = function(mat, dest){ dest[0] = mat[0]; dest[1] = mat[4]; dest[2] = mat[8]; dest[3] = mat[12]; dest[4] = mat[1]; dest[5] = mat[5]; dest[6] = mat[9]; dest[7] = mat[13]; dest[8] = mat[2]; dest[9] = mat[6]; dest[10] = mat[10]; dest[11] = mat[14]; dest[12] = mat[3]; dest[13] = mat[7]; dest[14] = mat[11]; dest[15] = mat[15]; return dest; }; this.inverse = function(mat, dest){ var a = mat[0], b = mat[1], c = mat[2], d = mat[3], e = mat[4], f = mat[5], g = mat[6], h = mat[7], i = mat[8], j = mat[9], k = mat[10], l = mat[11], m = mat[12], n = mat[13], o = mat[14], p = mat[15], q = a * f - b * e, r = a * g - c * e, s = a * h - d * e, t = b * g - c * f, u = b * h - d * f, v = c * h - d * g, w = i * n - j * m, x = i * o - k * m, y = i * p - l * m, z = j * o - k * n, A = j * p - l * n, B = k * p - l * o, ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); dest[0] = ( f * B - g * A + h * z) * ivd; dest[1] = (-b * B + c * A - d * z) * ivd; dest[2] = ( n * v - o * u + p * t) * ivd; dest[3] = (-j * v + k * u - l * t) * ivd; dest[4] = (-e * B + g * y - h * x) * ivd; dest[5] = ( a * B - c * y + d * x) * ivd; dest[6] = (-m * v + o * s - p * r) * ivd; dest[7] = ( i * v - k * s + l * r) * ivd; dest[8] = ( e * A - f * y + h * w) * ivd; dest[9] = (-a * A + b * y - d * w) * ivd; dest[10] = ( m * u - n * s + p * q) * ivd; dest[11] = (-i * u + j * s - l * q) * ivd; dest[12] = (-e * z + f * x - g * w) * ivd; dest[13] = ( a * z - b * x + c * w) * ivd; dest[14] = (-m * t + n * r - o * q) * ivd; dest[15] = ( i * t - j * r + k * q) * ivd; return dest; }; } { class Shader { constructor (canvas, vertex, fragment) { this.canvas = canvas; this.gl = canvas.getContext('webgl'); this.v_shader = create_shader.call (this, vertex); this.f_shader = create_shader.call (this, fragment); this.prg = create_program.call (this); this.vbo = {}; this.clear (); } clear (r = 0, g = 0, b = 0, a = 1) { let gl = this.gl; gl.clearColor (r, g, b, a); gl.clearDepth (1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); return this; } setVBO (data = [], type = 'position') { let gl = this.gl, attLocation = gl.getAttribLocation (this.prg, type), attStride = {position: 3, color: 4}[type], vbo = create_vbo.call (this, data); gl.bindBuffer (gl.ARRAY_BUFFER, vbo); gl.enableVertexAttribArray (attLocation); gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0); this.vbo[type] = vbo; return this; } draw (mvpMatrix, flash = true) { let {gl, prg} = this, uniLocation = gl.getUniformLocation (prg, 'mvpMatrix'); gl.uniformMatrix4fv (uniLocation, false, mvpMatrix); gl.drawArrays (gl.TRIANGLES, 0, 3); if (flash) gl.flush (); return this; } } const SHADER_TYPE = { 'x-shader/x-vertex': 'VERTEX_SHADER', 'x-shader/x-fragment': 'FRAGMENT_SHADER'}, create_shader = function (target = null) { if (target) { let gl = this.gl, {text, type} = target, shader = gl.createShader (gl[SHADER_TYPE[type]]); gl.shaderSource (shader, text); gl.compileShader (shader); if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS)) throw new Error (['シェーダーのコンパイルエラーがありました', gl.getShaderInfoLog (shader)].join ('\n')) return shader; } throw new Error ('要素がありません'); }, // プログラムオブジェクトを生成しシェーダをリンクする関数 create_program = function () { let gl = this.gl, program = gl.createProgram(); gl.attachShader (program, this.v_shader); gl.attachShader (program, this.f_shader); gl.linkProgram (program); if (! gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error (['プログラムのオブジェクトの生成に失敗しました', gl.getProgramInfoLog (program)].join ('\n')); gl.useProgram (program); return program; }, create_vbo = function (data) { let gl = this.gl, 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); return vbo; }; //_______ this.Shader = Shader; } {//実行スクリプト let doc = document, canvas = doc.querySelector ('canvas'), shader = new Shader ( canvas, doc.querySelector ('script[type="x-shader/x-vertex"]'), doc.querySelector ('script[type="x-shader/x-fragment"]'), ), // モデル(頂点)データ vertex_position = [ 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ], vertex_color = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ], // 各種行列の生成と初期化 matrix = Matrix4.perspective (90, canvas.width / canvas.height, 0.1, 100) .multiply (Matrix4.lookAt ([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0])) .multiply (new Matrix4); shader.setVBO (vertex_position, 'position'); shader.setVBO (vertex_color, 'color'); shader.draw (matrix.copy ().translate ([1.5,0,0]).M, false); shader.draw (matrix.translate ([-1.5,0,0]).M); } </script>
再帰処理と移動・回転・拡大縮小
https://wgld.org/d/webgl/w017.html
マトリクスのライブラリの移植終了。
これまでの勉強がかなり役立っている。何をしているのかスルスルわかる。
移植は脳内変換でスムーズに移行中、タイマーイベントから、アニメーションに変更。
<!DOCTYPE html> <meta charset="utf-8"> <title>WebGL</title> <style> </style> <body> <canvas width="1024" height="512"</canvas> <!-- ※頂点シェーダ --> <script id="vshader" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main (void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <!-- ※フラグメントシェーダ --> <script id="fshader" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> <script> { class Matrix4 { constructor (ary = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) { let F = Math.fround, M = new Float32Array (16); for (let i = 0; i < 16; i++) M[i] = F (ary[i] || 0); this.M = M; } multiply (mat1) { let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M, [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M; this.M = new Float32Array ([ A * a + B * e + C * i + D * m, A * b + B * f + C * j + D * n, A * c + B * g + C * k + D * o, A * d + B * h + C * l + D * p, E * a + F * e + G * i + H * m, E * b + F * f + G * j + H * n, E * c + F * g + G * k + H * o, E * d + F * h + G * l + H * p, I * a + J * e + K * i + L * m, I * b + J * f + K * j + L * n, I * c + J * g + K * k + L * o, I * d + J * h + K * l + L * p, M * a + N * e + O * i + P * m, M * b + N * f + O * j + P * n, M * c + N * g + O * k + P * o, M * d + N * h + O * l + P * p ]); return this; } scale ([x,y,z]) { let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M; this.M = new Float32Array ([ a * x, b * x, c * x, d * x, e * y, f * y, g * y, h * y, i * z, j * z, k * z, l * z, m, n, o, p ]); return this; } translate ([x, y, z]) { let [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M; this.M = new Float32Array ([ a, b, c, d, e, f, g, h, i, j, k, l, a * x + e * y + i * z + m, b * x + f * y + j * z + n, c * x + g * y + k * z + o, d * x + h * y + l * z + p ]); return this; } rotate (angle, [a, b, c]) {//a,b,cは多分ベクトルだろう。後で四元数で回せるから let sq = Math.sqrt(a * a + b * b + c * c); if (! sq) throw new Error ('ベクトルが値が0です'); if (sq != 1)//一応これが高速化? sq = 1 / sq; a *= sq; b *= sq; c *= sq; //単位ベクトルにしている let d = Math.sin (angle), e = Math.cos (angle), f = 1 - e, [g,h,i,j,k,l,m,n,o,p,q,r, B,C,D,E] = this.M, s = a * a * f + e, t = b * a * f + c * d, u = c * a * f - b * d, v = a * b * f - c * d, w = b * b * f + e, x = c * b * f + a * d, y = a * c * f + b * d, z = b * c * f - a * d, A = c * c * f + e; this.M = new Float32Array ([ g * s + k * t + o * u, h * s + l * t + p * u, i * s + m * t + q * u, j * s + n * t + r * u, g * v + k * w + o * x, h * v + l * w + p * x, i * v + m * w + q * x, j * v + n * w + r * x, g * y + k * z + o * A, h * y + l * z + p * A, i * y + m * z + q * A, j * y + n * z + r * A, B, C, D,E ]); return this; } inverse (mat, dest) { let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M, q = a * f - b * e, r = a * g - c * e, s = a * h - d * e, t = b * g - c * f, u = b * h - d * f, v = c * h - d * g, w = i * n - j * m, x = i * o - k * m, y = i * p - l * m, z = j * o - k * n, A = j * p - l * n, B = k * p - l * o, ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); this.M = new Float32Array ([ ( f * B - g * A + h * z) * ivd, (-b * B + c * A - d * z) * ivd, ( n * v - o * u + p * t) * ivd, (-j * v + k * u - l * t) * ivd, (-e * B + g * y - h * x) * ivd, ( a * B - c * y + d * x) * ivd, (-m * v + o * s - p * r) * ivd, ( i * v - k * s + l * r) * ivd, ( e * A - f * y + h * w) * ivd, (-a * A + b * y - d * w) * ivd, ( m * u - n * s + p * q) * ivd, (-i * u + j * s - l * q) * ivd, (-e * z + f * x - g * w) * ivd, ( a * z - b * x + c * w) * ivd, (-m * t + n * r - o * q) * ivd, ( i * t - j * r + k * q) * ivd ]); return this; }; copy () { return new Matrix4 (this.M); } transpose () { return new Matrix4 (this.M); } static lookAt (eye, center, up) { let [eyeX, eyeY, eyeZ] = eye, [upX, upY, upZ] = up, [centerX, centerY, centerZ] = center; if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) return (new this).identity (); let sqrt = Math.sqrt, z0 = eyeX - centerX, z1 = eyeY - centerY, z2 = eyeZ - centerZ, l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2); z0 *= l; z1 *= l; z2 *= l; let x0 = upY * z2 - upZ * z1, x1 = upZ * z0 - upX * z2, x2 = upX * z1 - upY * z0; l = sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (! l) { x0 = 0; x1 = 0; x2 = 0; } else { l = 1 / l; x0 *= l; x1 *= l; x2 *= l; } let y0 = z1 * x2 - z2 * x1, y1 = z2 * x0 - z0 * x2, y2 = z0 * x1 - z1 * x0; l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (! l) { y0 = 0; y1 = 0; y2 = 0; } else { l = 1 / l; y0 *= l; y1 *= l; y2 *= l; } return new this ([ x0, y0, z0, 0, x1, y1, z1, 0, x2, y2, z2, 0, -(x0 * eyeX + x1 * eyeY + x2 * eyeZ), -(y0 * eyeX + y1 * eyeY + y2 * eyeZ), -(z0 * eyeX + z1 * eyeY + z2 * eyeZ), 1 ]); } static perspective (fovy, aspect, near, far) { let t = near * Math.tan (fovy * Math.PI / 360), r = t * aspect, a = r * 2, b = t * 2, c = far - near; return new this ([ near * 2 / a, 0, 0, 0, 0, near * 2 / b, 0, 0, 0, 0, -(far + near) / c, -1, 0, 0, -(far * near * 2) / c, 0 ]); } } this.Matrix4 = Matrix4; } { class Shader { constructor (canvas, vertex, fragment) { this.canvas = canvas; this.gl = canvas.getContext('webgl'); this.v_shader = create_shader.call (this, vertex); this.f_shader = create_shader.call (this, fragment); this.prg = create_program.call (this); this.vbo = {}; this.clear (); } clear (r = 0, g = 0, b = 0, a = 1) { let gl = this.gl; gl.clearColor (r, g, b, a); gl.clearDepth (1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); return this; } setVBO (data = [], type = 'position') { let gl = this.gl, attLocation = gl.getAttribLocation (this.prg, type), attStride = {position: 3, color: 4}[type], vbo = create_vbo.call (this, data); gl.bindBuffer (gl.ARRAY_BUFFER, vbo); gl.enableVertexAttribArray (attLocation); gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0); this.vbo[type] = vbo; return this; } draw (mvpMatrix, flash = true) { let {gl, prg} = this, uniLocation = gl.getUniformLocation (prg, 'mvpMatrix'); gl.uniformMatrix4fv (uniLocation, false, mvpMatrix); gl.drawArrays (gl.TRIANGLES, 0, 3); if (flash) gl.flush (); return this; } } const SHADER_TYPE = { 'x-shader/x-vertex': 'VERTEX_SHADER', 'x-shader/x-fragment': 'FRAGMENT_SHADER'}, create_shader = function (target = null) { if (target) { let gl = this.gl, {text, type} = target, shader = gl.createShader (gl[SHADER_TYPE[type]]); gl.shaderSource (shader, text); gl.compileShader (shader); if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS)) throw new Error (['シェーダーのコンパイルエラーがありました', gl.getShaderInfoLog (shader)].join ('\n')) return shader; } throw new Error ('要素がありません'); }, // プログラムオブジェクトを生成しシェーダをリンクする関数 create_program = function () { let gl = this.gl, program = gl.createProgram(); gl.attachShader (program, this.v_shader); gl.attachShader (program, this.f_shader); gl.linkProgram (program); if (! gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error (['プログラムのオブジェクトの生成に失敗しました', gl.getProgramInfoLog (program)].join ('\n')); gl.useProgram (program); return program; }, create_vbo = function (data) { let gl = this.gl, 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); return vbo; }; //_______ this.Shader = Shader; } {//実行スクリプト let doc = document, canvas = doc.querySelector ('canvas'), shader = new Shader ( canvas, doc.querySelector ('script[type="x-shader/x-vertex"]'), doc.querySelector ('script[type="x-shader/x-fragment"]'), ), // モデル(頂点)データ vertex_position = [ 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ], vertex_color = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ], // 各種行列の生成と初期化 matrix = Matrix4.perspective (90, canvas.width / canvas.height, 0.1, 100) .multiply (Matrix4.lookAt ([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0])) .multiply (new Matrix4); shader.setVBO (vertex_position, 'position'); shader.setVBO (vertex_color, 'color'); //_____ let start = + new Date, deg = Math.PI / 180, {sin, cos} = Math; const loop = function (timeStamp) { let count = (timeStamp - start) / 30, rad = (count % 360) * deg; shader.clear (); // model-1 円の軌道を描き移動 let x = cos (rad), y = sin (rad), m1 = (new Matrix4).translate ([x, y + 1, 0]), m1_Matrix = matrix.copy ().multiply (m1); // model-2 Y軸を中心に回転 let m2 = (new Matrix4).translate ([1,-1,0]).rotate (rad, [0,1,0]), m2_Matrix = matrix.copy ().multiply (m2); // model-3 拡大縮小 let s = sin (rad) + 1, m3 = (new Matrix4).translate ([-1,-1,0]).scale ([s,s,0]), m3_Matrix = matrix.copy ().multiply (m3); shader.draw (m1_Matrix.M); shader.draw (m2_Matrix.M); shader.draw (m3_Matrix.M); requestAnimationFrame (loop); } loop (); } </script>
インデックスバッファによる描画
https://wgld.org/d/webgl/w018.html
まだまだ余裕。
<!DOCTYPE html> <meta charset="utf-8"> <title>WebGL</title> <style> </style> <body> <canvas width="1024" height="512"</canvas> <!-- ※頂点シェーダ --> <script id="vshader" type="x-shader/x-vertex"> attribute vec3 position; attribute vec4 color; uniform mat4 mvpMatrix; varying vec4 vColor; void main (void){ vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0); } </script> <!-- ※フラグメントシェーダ --> <script id="fshader" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void){ gl_FragColor = vColor; } </script> <script> { class Matrix4 { constructor (ary = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) { let F = Math.fround, M = new Float32Array (16); for (let i = 0; i < 16; i++) M[i] = F (ary[i] || 0); this.M = M; } multiply (mat1) { let [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M, [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M; this.M = new Float32Array ([ A * a + B * e + C * i + D * m, A * b + B * f + C * j + D * n, A * c + B * g + C * k + D * o, A * d + B * h + C * l + D * p, E * a + F * e + G * i + H * m, E * b + F * f + G * j + H * n, E * c + F * g + G * k + H * o, E * d + F * h + G * l + H * p, I * a + J * e + K * i + L * m, I * b + J * f + K * j + L * n, I * c + J * g + K * k + L * o, I * d + J * h + K * l + L * p, M * a + N * e + O * i + P * m, M * b + N * f + O * j + P * n, M * c + N * g + O * k + P * o, M * d + N * h + O * l + P * p ]); return this; } scale ([x,y,z]) { let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M; this.M = new Float32Array ([ a * x, b * x, c * x, d * x, e * y, f * y, g * y, h * y, i * z, j * z, k * z, l * z, m, n, o, p ]); return this; } translate ([x, y, z]) { let [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M; this.M = new Float32Array ([ a, b, c, d, e, f, g, h, i, j, k, l, a * x + e * y + i * z + m, b * x + f * y + j * z + n, c * x + g * y + k * z + o, d * x + h * y + l * z + p ]); return this; } rotate (angle, [a, b, c]) {//a,b,cは多分ベクトルだろう。後で四元数で回せるから let sq = Math.sqrt(a * a + b * b + c * c); if (! sq) throw new Error ('ベクトルが値が0です'); if (sq != 1)//一応これが高速化? sq = 1 / sq; a *= sq; b *= sq; c *= sq; //単位ベクトルにしている let d = Math.sin (angle), e = Math.cos (angle), f = 1 - e, [g,h,i,j,k,l,m,n,o,p,q,r, B,C,D,E] = this.M, s = a * a * f + e, t = b * a * f + c * d, u = c * a * f - b * d, v = a * b * f - c * d, w = b * b * f + e, x = c * b * f + a * d, y = a * c * f + b * d, z = b * c * f - a * d, A = c * c * f + e; this.M = new Float32Array ([ g * s + k * t + o * u, h * s + l * t + p * u, i * s + m * t + q * u, j * s + n * t + r * u, g * v + k * w + o * x, h * v + l * w + p * x, i * v + m * w + q * x, j * v + n * w + r * x, g * y + k * z + o * A, h * y + l * z + p * A, i * y + m * z + q * A, j * y + n * z + r * A, B, C, D,E ]); return this; } inverse (mat, dest) { let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M, q = a * f - b * e, r = a * g - c * e, s = a * h - d * e, t = b * g - c * f, u = b * h - d * f, v = c * h - d * g, w = i * n - j * m, x = i * o - k * m, y = i * p - l * m, z = j * o - k * n, A = j * p - l * n, B = k * p - l * o, ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); this.M = new Float32Array ([ ( f * B - g * A + h * z) * ivd, (-b * B + c * A - d * z) * ivd, ( n * v - o * u + p * t) * ivd, (-j * v + k * u - l * t) * ivd, (-e * B + g * y - h * x) * ivd, ( a * B - c * y + d * x) * ivd, (-m * v + o * s - p * r) * ivd, ( i * v - k * s + l * r) * ivd, ( e * A - f * y + h * w) * ivd, (-a * A + b * y - d * w) * ivd, ( m * u - n * s + p * q) * ivd, (-i * u + j * s - l * q) * ivd, (-e * z + f * x - g * w) * ivd, ( a * z - b * x + c * w) * ivd, (-m * t + n * r - o * q) * ivd, ( i * t - j * r + k * q) * ivd ]); return this; }; copy () { return new Matrix4 (this.M); } transpose () { return new Matrix4 (this.M); } static lookAt (eye, center, up) { let [eyeX, eyeY, eyeZ] = eye, [upX, upY, upZ] = up, [centerX, centerY, centerZ] = center; if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) return (new this).identity (); let sqrt = Math.sqrt, z0 = eyeX - centerX, z1 = eyeY - centerY, z2 = eyeZ - centerZ, l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2); z0 *= l; z1 *= l; z2 *= l; let x0 = upY * z2 - upZ * z1, x1 = upZ * z0 - upX * z2, x2 = upX * z1 - upY * z0; l = sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (! l) { x0 = 0; x1 = 0; x2 = 0; } else { l = 1 / l; x0 *= l; x1 *= l; x2 *= l; } let y0 = z1 * x2 - z2 * x1, y1 = z2 * x0 - z0 * x2, y2 = z0 * x1 - z1 * x0; l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (! l) { y0 = 0; y1 = 0; y2 = 0; } else { l = 1 / l; y0 *= l; y1 *= l; y2 *= l; } return new this ([ x0, y0, z0, 0, x1, y1, z1, 0, x2, y2, z2, 0, -(x0 * eyeX + x1 * eyeY + x2 * eyeZ), -(y0 * eyeX + y1 * eyeY + y2 * eyeZ), -(z0 * eyeX + z1 * eyeY + z2 * eyeZ), 1 ]); } static perspective (fovy, aspect, near, far) { let t = near * Math.tan (fovy * Math.PI / 360), r = t * aspect, a = r * 2, b = t * 2, c = far - near; return new this ([ near * 2 / a, 0, 0, 0, 0, near * 2 / b, 0, 0, 0, 0, -(far + near) / c, -1, 0, 0, -(far * near * 2) / c, 0 ]); } } this.Matrix4 = Matrix4; } { class Shader { constructor (canvas, vertex, fragment) { this.canvas = canvas; this.gl = canvas.getContext('webgl'); this.v_shader = create_shader.call (this, vertex); this.f_shader = create_shader.call (this, fragment); this.prg = create_program.call (this); this.vbo = {}; this.clear (); } clear (r = 0, g = 0, b = 0, a = 1) { let gl = this.gl; gl.clearColor (r, g, b, a); gl.clearDepth (1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); return this; } setVBO (data = [], type = 'position') { let gl = this.gl, attLocation = gl.getAttribLocation (this.prg, type), attStride = {position: 3, color: 4}[type], vbo = create_vbo.call (this, data); gl.bindBuffer (gl.ARRAY_BUFFER, vbo); gl.enableVertexAttribArray (attLocation); gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0); this.vbo[type] = vbo; return this; } setIBO (data = []) { let gl = this.gl, ibo = create_ibo.call (this, data); gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, ibo); this.ibo = ibo; return this; } draw (mvpMatrix, vertex_index, flash = true) { let {gl, prg} = this, uniLocation = gl.getUniformLocation (prg, 'mvpMatrix'); gl.uniformMatrix4fv (uniLocation, false, mvpMatrix); // gl.drawArrays (gl.TRIANGLES, 0, 3); gl.drawElements (gl.TRIANGLES, vertex_index.length, gl.UNSIGNED_SHORT, 0); if (flash) gl.flush (); return this; } } const SHADER_TYPE = { 'x-shader/x-vertex': 'VERTEX_SHADER', 'x-shader/x-fragment': 'FRAGMENT_SHADER'}, create_shader = function (target = null) { if (target) { let gl = this.gl, {text, type} = target, shader = gl.createShader (gl[SHADER_TYPE[type]]); gl.shaderSource (shader, text); gl.compileShader (shader); if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS)) throw new Error (['シェーダーのコンパイルエラーがありました', gl.getShaderInfoLog (shader)].join ('\n')) return shader; } throw new Error ('要素がありません'); }, // プログラムオブジェクトを生成しシェーダをリンクする関数 create_program = function () { let gl = this.gl, program = gl.createProgram(); gl.attachShader (program, this.v_shader); gl.attachShader (program, this.f_shader); gl.linkProgram (program); if (! gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error (['プログラムのオブジェクトの生成に失敗しました', gl.getProgramInfoLog (program)].join ('\n')); gl.useProgram (program); return program; }, create_vbo = function (data) { let gl = this.gl, 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); return vbo; }, create_ibo = function (data) { let gl = this.gl, 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); return ibo; }; //_______ this.Shader = Shader; } {//実行スクリプト let doc = document, canvas = doc.querySelector ('canvas'), shader = new Shader ( canvas, doc.querySelector ('script[type="x-shader/x-vertex"]'), doc.querySelector ('script[type="x-shader/x-fragment"]'), ), // モデル(頂点)データ vertex_position = [ 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0 ], vertex_color = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ], vertex_index = [ 0, 1, 2, 1, 2, 3 ], // 各種行列の生成と初期化 matrix = Matrix4.perspective (45, canvas.width / canvas.height, 0.1, 100) .multiply (Matrix4.lookAt ([0, 0, 5.0], [0, 0, 0], [0, 1, 0])) .multiply (new Matrix4); shader.setVBO (vertex_position, 'position'); shader.setVBO (vertex_color, 'color'); shader.setIBO (vertex_index); //_____ let start = + new Date, deg = Math.PI / 180, {sin, cos} = Math; const loop = function (timeStamp) { let count = (timeStamp - start) / 30, rad = (count % 360) * deg; shader.clear (); let m = (new Matrix4).rotate (rad, [0,1,0]), mMatrix = matrix.copy ().multiply (m); shader.draw (mMatrix.M, vertex_index); requestAnimationFrame (loop); } loop (); } </script>