<!doctype html>
<head>
<title>sphere5</title>
<script src="minMatrix.js"></script>
</head>
<body>
<canvas id="canvas" width="640" height="480"></canvas>
<div id="msg"></div>
<p>
move light: cursor key
</p>
<script id="vs" type="text/x-vertex">
// 頂点シェイダー
attribute vec3 position;
attribute vec3 normal;
attribute vec2 texCoord;
uniform mat4 mvpMatrix;
varying vec3 vNormal;
varying vec2 vTexCoord;
void main(void) {
vNormal = normal;
vTexCoord = texCoord;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</script>
<script id="fs" type="text/x-fragment">
// 断片シェイダー
precision mediump float;
uniform mat4 invMatrix;
uniform vec3 direction;
uniform sampler2D texture;
varying vec3 vNormal;
varying vec2 vTexCoord;
void main(void) {
vec3 invLight = normalize(invMatrix * vec4(direction, 0.0)).xyz;
float diffuse = clamp(dot(vNormal, invLight), 0.0, 1.0);
diffuse = clamp(pow(diffuse, 0.3), 0.2, 1.0);
gl_FragColor = texture2D(texture, vTexCoord) * vec4(vec3(diffuse), 1.0);
// gl_FragColor = vec4(vTexCoord, 0.0, 1.0) * vec4(vec3(diffuse), 1.0);
}
</script>
<script>
function Keyboard() {
var _keys = [];
this.isKeyDown = function(keyCode) {
return _keys[keyCode];
};
document.addEventListener("keydown", function(event) {
_keys[event.keyCode] = true;
});
document.addEventListener("keyup", function(event) {
_keys[event.keyCode] = false;
});
}
var keyboard = new Keyboard();
var gl; // webgl context
var mat = new matIV();
var pMatrix = mat.create(); // perspective
var texture = null;
var indexCount;
var loc = {};
var lat = 0; // 緯度
var lon = 0; // 経度
var angle = 0;
var fps;
var fpsCount;
var secSave;
onload = function() {
gl = canvas.getContext("experimental-webgl");
var vShader = createShader("vs");
var fShader = createShader("fs");
var program = createProgram(vShader, fShader);
gl.useProgram(program);
gl.activeTexture(gl.TEXTURE0);
createTexture("earthmap1k.jpg");
var model = sphere(32);
var vbo = createVbo(model.positions);
setAttribute(program, "position", vbo, 3);
var vbo = createVbo(model.normals);
setAttribute(program, "normal", vbo, 3);
var vbo = createVbo(model.texCoords);
setAttribute(program, "texCoord", vbo, 2);
var ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(model.indices), gl.STATIC_DRAW);
indexCount = model.indices.length;
loc.mvpMatrix = gl.getUniformLocation(program, "mvpMatrix");
loc.invMatrix = gl.getUniformLocation(program, "invMatrix");
loc.direction = gl.getUniformLocation(program, "direction");
loc.texture = gl.getUniformLocation(program, "texture");
gl.enable(gl.CULL_FACE);
gl.frontFace(gl.CW);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
mat.perspective(45, canvas.width / canvas.height, 0.1, 10, pMatrix);
draw();
};
function draw() {
if (keyboard.isKeyDown(37)) lon = (lon + 2) % 360; // Left
if (keyboard.isKeyDown(38)) lat = Math.min(lat + 1, 23); // Up
if (keyboard.isKeyDown(39)) lon = (lon + 358) % 360; // Right
if (keyboard.isKeyDown(40)) lat = Math.max(lat - 1, -23); // Down
var sec = (new Date()).getSeconds();
if (secSave != sec) {
fps = fpsCount;
fpsCount = 0;
secSave = sec;
}
fpsCount++;
angle = (angle + 1) % 360;
msg.innerHTML = "lat:" + lat + " lon:" + lon + " fps:" + fps + " angle:" + angle;
// clear
// gl.clearColor(0x64 / 0xff, 0x95 / 0xff, 0xed / 0xff, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// camera
var vMatrix = mat.create();
var vpMatrix = mat.create();
var mvpMatrix = mat.create();
mat.lookAt([0, 0, 3], [0, 0, 0], [0, 1, 0], vMatrix);
mat.multiply(pMatrix, vMatrix, vpMatrix);
// light
var rad = lat * Math.PI / 180;
var r = Math.cos(rad);
var y = Math.sin(rad);
var rad = lon * Math.PI / 180;
var x = Math.cos(rad) * r;
var z = Math.sin(rad) * r;
var direction = [x, y, z];
gl.uniform3fv(loc.direction, direction);
// texture
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(loc.texture, 0);
// model
var rad = angle * Math.PI / 180;
var mMatrix = mat.identity(mat.create());
mat.rotate(mMatrix, rad, [0, 1, 0], mMatrix);
var invMatrix = mat.create();
mat.inverse(mMatrix, invMatrix);
mat.multiply(vpMatrix, mMatrix, mvpMatrix);
gl.uniformMatrix4fv(loc.mvpMatrix, false, mvpMatrix);
gl.uniformMatrix4fv(loc.invMatrix, false, invMatrix);
gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0);
gl.flush();
setTimeout(draw, 1000 / 30);
}
function createShader(id) {
var element = document.getElementById(id);
if (! element) return;
const types = {
"text/x-vertex": gl.VERTEX_SHADER,
"text/x-fragment": gl.FRAGMENT_SHADER,
};
var shader = gl.createShader(types[element.type]);
gl.shaderSource(shader, element.text);
gl.compileShader(shader);
if (! gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return;
}
return shader;
}
function createProgram(vs, fs) {
var program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (! gl.getProgramParameter(program, gl.LINK_STATUS)) {
alert(gl.getProgramInfoLog(program));
return;
}
return program;
}
function createVbo(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);
return vbo;
}
function setAttribute(program, name, vbo, stride) {
var location = gl.getAttribLocation(program, name);
gl.enableVertexAttribArray(location);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.vertexAttribPointer(location, stride, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
function createTexture(src) {
var img1 = new Image();
img1.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 512;
var ctx = canvas.getContext("2d");
ctx.drawImage(img1, 0, 0, canvas.width, canvas.height);
// 赤道
ctx.strokeStyle = "#ff0000";
ctx.beginPath();
ctx.moveTo(0, 256);
ctx.lineTo(1024, 256);
ctx.closePath();
ctx.stroke();
var img2 = new Image();
img2.onload = function() {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img2);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
texture = tex;
};
img2.src = canvas.toDataURL("image/png");
};
img1.src = src;
}
function sphere(stacks) {
// 頂点
var positions = [];
var texCoords = [];
positions.push(0, 1, 0); // x, y, z
texCoords.push(0.5, 0);
for (var stack = 1; stack < stacks; stack++) {
var v = stack / stacks;
var lat = Math.PI * v;
var r = Math.sin(lat);
var y = Math.cos(lat);
var slices = 4 * Math.min(stack, stacks - stack); // 放射頂点の数
for (var slice = 0; slice <= slices; slice++) {
var u = slice / slices;
var lon = 2 * Math.PI * u;
var z = Math.sin(lon) * r;
var x = Math.cos(lon) * r;
positions.push(x, y, z);
texCoords.push(1.0 - u, v);
}
}
positions.push(0, -1, 0);
texCoords.push(0.5, 1);
// 法線ベクトル
var normals = [];
for (var i = 0; i < positions.length; i++) {
normals.push(positions[i]); // 必要なら正規化
}
// 索引
var indices = [];
var prevHead = 0; // 前の先頭頂点番号
var prevVert = 1; // 前の頂点数
for (var stack = 0; stack < stacks / 2; stack++) {
var currHead = prevHead + prevVert; // 現在の先頭頂点番号
var currVert = 4 * (stack + 1) + 1; // 現在の頂点数
for (var quad = 0; quad < 4; quad++) { // 4象限
var prevQuad = quad * stack; // 前の象限オフセット
var currQuad = quad * (stack + 1); // 現在の象限オフセット
for (var i = 0; ; i++) {
var prev = prevHead + prevQuad + i;
var curr = currHead + currQuad + i;
indices.push(prev, curr, curr + 1);
if (stack <= i) break;
indices.push(prev, curr + 1, prev + 1);
}
}
prevHead = currHead;
prevVert = currVert;
}
for (var stack = stacks / 2 - 1; 0 <= stack; stack--) {
var currHead = prevHead + prevVert;
var currVert = 4 * stack + 1;
for (var quad = 0; quad < 4; quad++) {
var prevQuad = quad * (stack + 1);
var currQuad = quad * stack;
for (var i = 0; ; i++) {
var prev = prevHead + prevQuad + i;
var curr = currHead + currQuad + i;
indices.push(curr, prev + 1, prev);
if (stack <= i) break;
indices.push(curr, curr + 1, prev + 1);
}
}
prevHead = currHead;
prevVert = currVert;
}
return {"positions":positions, "normals":normals, "texCoords":texCoords, "indices":indices};
}
</script>
</body>