平行光源によるライティング
<!DOCTYPE html>
<meta charset="utf-8">
<title>WebGL</title>
<style>
body { background: black; }
</style>
<body>
<canvas width="512" height="512"</canvas>
<!-- ※頂点シェーダ -->
<script id="vshader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 mvpMatrix;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
varying vec4 vColor;
void main (void) {
vec3 invLight = normalize (invMatrix * vec4 (lightDirection, 0.0)).xyz;
float diffuse = clamp (dot (normal, invLight), 0.1, 1.0);
vColor = color * vec4 (vec3 (diffuse), 1.0);
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]) {
let
sq = Math.sqrt(a * a + b * b + c * c) || Number.MIN_VALUE;
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 () {
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);
}
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;
}
{
const
ATT_STRIDE = {position: 3, normal: 3, color: 4};
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.uniLocation = { mvp: null, inv: null, light: null };
let gl = this.gl;
gl.enable (gl.DEPTH_TEST);
gl.depthFunc (gl.LEQUAL);
gl.enable (gl.CULL_FACE);
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, prg } = this,
attLocation = gl.getAttribLocation (prg, type),
attStride = ATT_STRIDE[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;
}
setLocation () {
let {gl, prg} = this;
this.uniLocation = {
mvp: gl.getUniformLocation (prg, 'mvpMatrix'),
inv: gl.getUniformLocation (prg, 'invMatrix'),
light: gl.getUniformLocation (prg, 'lightDirection')
};
return this;
}
draw (mvpMatrix, invMatrix, lightDirection, vertex_index, flush = true) {
let
{gl, prg, uniLocation} = this,
{mvp, inv, light} = uniLocation;
gl.uniformMatrix4fv (mvp, false, mvpMatrix);
gl.uniformMatrix4fv (inv, false, invMatrix);
gl.uniform3fv (light, lightDirection);
gl.drawElements (gl.TRIANGLES, vertex_index.length, gl.UNSIGNED_SHORT, 0);
if (flush)
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 (),
buf = gl.ARRAY_BUFFER;
gl.bindBuffer (buf, vbo);
gl.bufferData (buf, new Float32Array (data), gl.STATIC_DRAW);
gl.bindBuffer (buf, null);
return vbo;
},
create_ibo =
function (data) {
let
gl = this.gl,
ibo = gl.createBuffer (),
buf = gl.ELEMENT_ARRAY_BUFFER;
gl.bindBuffer (buf, ibo);
gl.bufferData (buf, new Int16Array (data), gl.STATIC_DRAW);
gl.bindBuffer (buf, null);
return ibo;
};
this.Shader = Shader;
}
function torus(row, column, irad, orad){
var pos = new Array(), nor = new Array(),
col = new Array(), idx = new Array();
for(var i = 0; i <= row; i++){
var r = Math.PI * 2 / row * i;
var rr = Math.cos(r);
var ry = Math.sin(r);
for(var ii = 0; ii <= column; ii++){
var tr = Math.PI * 2 / column * ii;
var tx = (rr * irad + orad) * Math.cos(tr);
var ty = ry * irad;
var tz = (rr * irad + orad) * Math.sin(tr);
var rx = rr * Math.cos(tr);
var rz = rr * Math.sin(tr);
pos.push(tx, ty, tz);
nor.push(rx, ry, rz);
var tc = hsva(360 / column * ii, 1, 1, 1);
col.push(tc[0], tc[1], tc[2], tc[3]);
}
}
for(i = 0; i < row; i++){
for(ii = 0; ii < column; ii++){
r = (column + 1) * i + ii;
idx.push(r, r + column + 1, r + 1);
idx.push(r + column + 1, r + column + 2, r + 1);
}
}
return [pos, nor, col, idx];
}
function hsva(h, s, v, a){
if(s > 1 || v > 1 || a > 1){return;}
var th = h % 360;
var i = Math.floor(th / 60);
var f = th / 60 - i;
var m = v * (1 - s);
var n = v * (1 - s * f);
var k = v * (1 - s * (1 - f));
var color = new Array();
if(!s > 0 && !s < 0){
color.push(v, v, v, a);
} else {
var r = new Array(v, n, m, m, k, v);
var g = new Array(k, v, v, n, m, m);
var b = new Array(m, m, k, v, v, n);
color.push(r[i], g[i], b[i], a);
}
return color;
}
{
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"]'),
),
matrix = Matrix4.perspective (45, canvas.width / canvas.height, 0.1, 100)
.multiply (Matrix4.lookAt ([0, 0, 10.0], [0, 0, 0], [0, 1, 0]))
.multiply (new Matrix4),
lightDirection = [-0.5, 0.5, 0.5],
[vertex_position, vector_normal, vertex_color, vertex_index]
= torus (72, 72, 1.0, 2.0);
shader.setLocation ();
shader.setVBO (vertex_position, 'position');
shader.setVBO (vector_normal, 'normal');
shader.setVBO (vertex_color, 'color');
shader.setIBO (vertex_index);
let cnt = 0, pi = Math.PI, deg = pi / 180;
const loop = function (timeStamp) {
shader.clear ();
let
rad = (cnt++ %360) * deg,
m = (new Matrix4).rotate (rad, [0, 1, 1]),
mMatrix = matrix.copy ().multiply (m),
invMatrix = m.copy ().inverse ();
shader.draw (mMatrix.M, invMatrix.M, lightDirection, vertex_index);
requestAnimationFrame (loop);
}
loop ();
}
</script>