<canvas id="screen" width="500px" height="500px"></canvas>
var gl = null; var canvas = $('#screen').get(0); $.each(['webgl', 'experimental-webgl'], function() { try { gl = canvas.getContext(this); } catch(e) {} return !gl; }); if(!gl) { alert('WebGL がサポートされていません。'); }
canvas
タグを利用するgetContext()
に 'webgl'
を渡すと、 WebGL 用のコンテキストが取得できる'experimental-webgl'
も試行したほうが無難諸事情により、ここからは端折っていきます…
// 頂点データを生成 var positions = []; for(var i = 0 ; i <= 8 ; ++i) { var y = Math.cos(Math.PI * v), r = Math.sin(Math.PI * v); for(var j = 0 ; j <= 16 ; ++j) { var u = j / 16.0; positions = positions.concat( Math.cos(2 * Math.PI * u) * r, y, Math.sin(2 * Math.PI * u) * r); } } // VBOを作成し、データを転送 vbuffers = $.map([positions, positions], function() { var vbuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this), gl.STATIC_DRAW); return vbuffer; }); gl.bindBuffer(gl.ARRAY_BUFFER, null);
// インデックスデータを生成 var indices = []; for(var j = 0 ; j < 8 ; ++j) { var base = j * 17; for(var i = 0 ; i < 16 ; ++i) { indices = indices.concat( base + i, base + i + 1, base + i + 17, base + i + 17, base + i + 1, base + i + 1 + 17); } } // IBOを作成し、データを転送 ibuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(indices), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // インデックスの数を保存しておく numIndices = indices.length;
<script id="vshader" type="x-shader/x-vertex"> #ifdef GL_ES precision highp float; #endif uniform mat4 mvpMatrix; uniform mat4 normalMatrix; uniform vec4 lightVec; attribute vec3 position; attribute vec3 normal; varying vec4 color; void main() { vec3 n = (normalMatrix * vec4(normal, 0.0)).xyz; float light = clamp(dot(n, lightVec.xyz), 0.0, 1.0) * 0.8 + 0.2; color = vec4(light, light, light, 1.0); gl_Position = mvpMatrix * vec4(position, 1.0); } </script>
<script id="fshader" type="x-shader/x-fragment"> #ifdef GL_ES precision highp float; #endif varying vec4 color; void main() { gl_FragColor = color; } </script>
// 頂点シェーダーを作成 var vshader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vshader, $('#vshader').text()); gl.compileShader(vshader); if(!gl.getShaderParameter(vshader, gl.COMPILE_STATUS)) alert(gl.getShaderInfoLog(vshader)); // フラグメントシェーダーを作成 var fshader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fshader, $('#fshader').text()); gl.compileShader(fshader); if(!gl.getShaderParameter(fshader, gl.COMPILE_STATUS)) alert(gl.getShaderInfoLog(fshader)); // プログラムオブジェクトを作成 program = gl.createProgram(); gl.attachShader(program, vshader); gl.attachShader(program, fshader);
// シェーダー内の変数を頂点属性に結びつける $.each(["position", "normal"], function(i, name) { gl.bindAttribLocation(program, i, name); }); // 頂点シェーダーとフラグメントシェーダーをリンクする gl.linkProgram(program); if(!gl.getProgramParameter(program, gl.LINK_STATUS)) alert(gl.getProgramInfoLog(program)); // シェーダーパラメータのインデックスを取得・保存 uniformVars = $.map(["mvpMatrix", "normalMatrix", "lightVec"], function(name) { return gl.getUniformLocation(program, name); });
// 画面をクリア gl.clearColor(0, 0, 0, 1); gl.clearDepth(1000); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // デプステストを有効にし、シェーダーを指定 gl.enable(gl.DEPTH_TEST); gl.useProgram(program); // シェーダーに渡すパラメータを計算し、設定 var lightVec = [0.57735, 0.57735, 0.57735, 0.0]; var modelMatrix = new CanvasMatrix4(); modelMatrix.rotate(count, 0, 1, 0); var mvpMatrix = new CanvasMatrix4(modelMatrix); mvpMatrix.translate(0, 0, -6); mvpMatrix.perspective(30, 500.0 / 500.0, 0.1, 1000); var normalMatrix = new CanvasMatrix4(modelMatrix); normalMatrix.invert(); normalMatrix.transpose();
$.each([mvpMatrix, normalMatrix, lightVec], function(i, value) { if(value instanceof CanvasMatrix4) gl.uniformMatrix4fv(uniformVars[i], false, value.getAsWebGLFloatArray()); else gl.uniform4fv(uniformVars[i], new Float32Array(value)); }); // VBOを頂点属性に割り当てる $.each([3, 3], function(i, stride) { gl.enableVertexAttribArray(i); gl.bindBuffer(gl.ARRAY_BUFFER, vbuffers[i]); gl.vertexAttribPointer(i, stride, gl.FLOAT, false, 0, 0); }); // IBOを指定 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuffer); // 描画 gl.drawElements(gl.TRIANGLES, numIndices, gl.UNSIGNED_SHORT, 0); // ページに反映させる gl.flush();
そこで Three.js ですよ!!
var SCREEN_WIDTH = 500; var SCREEN_HEIGHT = 500; // シーンの初期化 var scene = new THREE.Scene(); var renderer = new THREE.WebGLRenderer(); renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); renderer.setClearColorHex(0x000000, 1); document.body.appendChild( renderer.domElement); // カメラの作成 var camera = new THREE.PerspectiveCamera( 30, SCREEN_WIDTH / SCREEN_HEIGHT, 0.1, 1000 ); camera.position.set(0, 0, 6); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera); // ライトの作成 var light = new THREE.DirectionalLight(0xcccccc); light.position.set(0.577, 0.577, 0.577); scene.add(light);
var ambient = new THREE.AmbientLight(0x333333); scene.add(ambient); // モデルの作成 var geometry = new THREE.SphereGeometry(1, 16, 8); var material = new THREE.MeshLambertMaterial( { color: 0xffffff , ambient: 0xffffff }); var mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // レンダリング function render() { requestAnimationFrame(render); renderer.render(scene, camera); }; render();
基本的に、.jsファイルをひとつ読みこむだけです。
<script src="Three.js"></script>
Tree.js
は ↓ でダウンロードできます。
http://mrdoob.github.com/three.js/build/Three.js
自分でビルドするのも簡単です。
git clone https://github.com/mrdoob/three.js.git three cd three/utils python build.py --common --minified
--minified
を外せば非圧縮バージョンになります。
デバッグに便利。
var SCREEN_WIDTH = 500; var SCREEN_HEIGHT = 500; var scene = new THREE.Scene(); var renderer = new THREE.WebGLRenderer(); renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); renderer.setClearColorHex(0x000000, 1); document.body.appendChild(renderer.domElement);
THREE.Scene
のインスタンスを作成THREE.WebGLRenderer
のインスタンスを作成var camera = new THREE.PerspectiveCamera( 30, SCREEN_WIDTH / SCREEN_HEIGHT, 0.1, 1000 ); camera.position.set(0, 0, 6); camera.lookAt(new THREE.Vector3(0, 0, 0)); scene.add(camera);
THREE.PerspectiveCamera
のインスタンスを作成position
にカメラ位置を設定lookAt()
で注視点の座標を指定var light = new THREE.DirectionalLight(0xcccccc); light.position.set(0.577, 0.577, 0.577); scene.add(light); var ambient = new THREE.AmbientLight(0x333333); scene.add(ambient);
THREE.DirectionalLight
のインスタンスを作成position
に光の照射方向を設定AmbientLight
はすべての面の均一な明るさ(環境光)var geometry = new THREE.SphereGeometry(1, 16, 8); var material = new THREE.MeshLambertMaterial( { color: 0xffffff , ambient: 0xffffff }); var mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
THREE.Mesh
インスタンスを作成
function render() { requestAnimationFrame(render); renderer.render(scene, camera); }; render();
WebGLRenderer
の render()
を呼ぶだけでrequestAnimationFrame()
はクロスブラウザ対応版が
var material = new THREE.MeshLambertMaterial( { color: 0xffffff , ambient: 0xffffff, map: THREE.ImageUtils.loadTexture('lib/earth.jpg') });
マテリアルの初期化時にファイルを指定するだけで、
テクスチャが貼れる
var baseTime = +new Date; function render() { requestAnimationFrame(render); var time = (+new Date - baseTime) / 1000; mesh.rotation.y = 0.3 * time; renderer.render(scene, camera); }; render();
描画するごとにオブジェクトの position
や rotation
を
少しずつ変化させることでアニメーションする
// モデルの作成 // ... var meshes = []; var rotation = new THREE.Vector3(); for(var x = 0 ; x < 8 ; x++) { for(var y = 0 ; y < 8 ; y++) { for(var z = 0 ; z < 8 ; z++) { var mesh = new THREE.Mesh(geometry, material); mesh.position.set(x*4-14.5, y*4-14.5, -z*8-55); mesh.rotation = rotation; scene.add(mesh); meshes.push(mesh); } } } // レンダリング var baseTime = +new Date; function render() { requestAnimationFrame(render); var time = (+new Date - baseTime) / 1000; rotation.y = 0.3 * time; renderer.render(scene, camera); }; render();
scene.fog = new THREE.Fog(0xffffff, 60, 130); material.fog = true;
THREE.Fog
のインスタンスを作成し、 scene.fog
にfog
属性を true にする
var renderer = new THREE.WebGLRenderer(); renderer.shadowMapEnabled = true; // ShadowMap を有効化 renderer.shadowMapBias = 0.00385; // アーティファクトが出ないように調整 // ... // ShadowMap が使えるのは SpotLight のみ var light = new THREE.SpotLight(0xffffff, 1, 0, true); light.position.set(500, 1000, 500); scene.add(light); // ... var torus1 = new THREE.Mesh(torusGeometry, torusMaterial); torus1.position.set(-200, 0, 0); torus1.castShadow = true; // true なら他の物体に影を落とす torus1.receiveShadow = true; // true なら他の物体の影が落ちる scene.add(torus1);
// モデルを読み込む var loader = new THREE.ColladaLoader(); loader.options.convertUpAxis = true; loader.load('lib/monster.dae', init); function init(collada) { // モデル以外の初期化処理… // モデルの作成 console.log(collada); var mesh = collada.scene; // レンダリング }