HEXA BLOG
ヘキサブログ
プログラム
JavaScriptで3D
突然ですが、ベランダで野菜を育て始めました。
少しずつではありますが、元気に育っていく野菜を見るのが楽しみなgood sunこと山口です。
さて少し前からJavaScriptを話題にしているのですが、
今回は前回のドラッグ&ドロップを引き継ぎつつ、今までと違ったJavaScriptのサンプルを用意しました。
お待たせ致しました、ついにWebGL到来です!!
ついにブラウザだけの力で3D表示出来る時代の波に少しだけ乗っかってみました。
その為今回のサンプルが動作するブラウザは限られてしまいます。
推奨ブラウザはGoogle Chromeとなります。
サンプルはこちらから
今回のサンプルは実行されると四角い箱が回っています。
ここにブラウザで使用可能な画像ファイル(.png等)をドラッグ&ドロップすることで、
画像をテクスチャとして貼ることが出来ます。
※簡単に試してみましたが、どうもjpegは白い画像になってしまうようです。。。pngでお試し下さい。
ドラッグ&ドロップについては
最後に掲載するソースコードのdropFileの部分を参照して下さい。
dropイベントを受け取って、
中身が画像であればテクスチャを作成しています。
実際に一連のソースコードがあると、
どのような流れで動いているのか分かりやすいのでは無いでしょうか。
今回サンプルを作っていて、昔Java3Dでブラウザに3D表示をしていた時の事を思い出したりしました。
いまではJavaScriptで3D表示が出来ておまけにシェーダが使えてしまうのですから、
技術の進歩は凄まじいものです。
更にJavaScript版COLLADAローダというのもあるようです。
頑張れば3Dモデルデータもブラウザにドラッグ&ドロップで表示出来るようになってしまいます!
これは益々目が離せない感じです。
WebGL入門も果たしブラウザ熱が上がってきたところで今回は終了とさせて頂きたいと思います。
次回もWebGLについて書ければ良いなぁと考えておりますが、予定は未定です。
最後に注意点及び、今回のサンプルのソースコードを掲載します。
まず注意点ですが、
このサンプル、Google Chromeではサーバにソースコードがある場合は問題なく動作しますが、
ソースをローカルにダウンロードして動作させようとした場合に通常はドラッグ&ドロップが働きません。
MacOSXの場合は以下のようなシェルスクリプト経由で起動させることでファイルのドラッグ&ドロップを受け付けるようになります。
#!/bin/sh /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files
そして以下が今回のサンプルのソースコードになります。
今回のサンプルは行列処理にNewBSDライセンスにて配布されているglMatrixを利用しています。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>javascriptサンプル</title> <script type="text/javascript" src="http://glmatrix.googlecode.com/files/glMatrix-0.9.5.min.js"></script> </head> <body> <canvas id="canvas" ondragover="scene.dragFile(event);" ondrop="scene.dropFile(event);"></canvas> <script id="vshader" type="x-shader/x-vertex"> #ifdef GL_ES precision highp float; #endif uniform mat4 mMatrix; uniform mat4 vpMatrix; attribute vec3 position; attribute vec2 uv; varying vec2 texCoord; void main() { gl_Position = vpMatrix * mMatrix * vec4(position, 1.0); texCoord = uv; } </script> <script id="fshader" type="x-shader/x-fragment"> #ifdef GL_ES precision highp float; #endif uniform sampler2D texture; varying vec2 texCoord; void main() { gl_FragColor = texture2D(texture, texCoord); } </script> <script type="text/javascript"> var scene; var frame; window.onload = function() { frame = new Frame(); if(frame.enabled){ scene = new Scene(frame.context3d); frame.drawFunc = function(context3d){scene.draw(context3d);} frame.run(new Date()); } } </script> </body> <script type="text/javascript"> Screen = { width:640, height:480 }; //! カラー定義 function Color(r,g,b,a){ this.r = r; this.g = g; this.b = b; this.a = a; } //! シェーダ function Shader(context3d, vshaderSource, fshaderSource, attrNames, uniformNames){ this.vshader = null; this.fshader = null; this.program = null; this.attributes = []; this.uniforms = []; // 頂点シェーダー this.vshader = context3d.createShader(context3d.VERTEX_SHADER); context3d.shaderSource(this.vshader, vshaderSource); context3d.compileShader(this.vshader); if(!context3d.getShaderParameter(this.vshader, context3d.COMPILE_STATUS)){ alert(context3d.getShaderInfoLog(this.vshader)); } // フラグメントシェーダー this.fshader = context3d.createShader(context3d.FRAGMENT_SHADER); context3d.shaderSource(this.fshader, fshaderSource); context3d.compileShader(this.fshader); if(!context3d.getShaderParameter(this.fshader, context3d.COMPILE_STATUS)){ alert(context3d.getShaderInfoLog(this.fshader)); } this.program = context3d.createProgram(); context3d.attachShader(this.program, this.vshader); context3d.attachShader(this.program, this.fshader); context3d.linkProgram(this.program); if(!context3d.getProgramParameter(this.program, context3d.LINK_STATUS)){ alert(context3d.getProgramInfoLog(this.program)); } for(var i = 0; i < attrNames.length; i++){ var attr = context3d.getAttribLocation(this.program, attrNames[i]); this.attributes = this.attributes.concat(attr); } for(var i = 0; i < uniformNames.length; i++){ this.uniforms = this.uniforms.concat(context3d.getUniformLocation(this.program, uniformNames[i])); } } //! ボックスデータを作る function Box(context3d){ this.vbuffer = []; this.ibuffer = null; this.indexCount = 0; this.initVertexBuffer(context3d); this.initIndexBuffer(context3d); } Box.prototype.initVertexBuffer = function(context3d){ var position = [ [-1,-1,-1], [ 1,-1,-1], [-1,-1, 1], [ 1,-1, 1], [-1, 1,-1], [ 1, 1,-1], [-1, 1, 1], [ 1, 1, 1], ]; var uv = [ [0,1], [1,1], [0,0], [1,0] ]; // 頂点配列を組み立てる var index = [[0,1,2,3],[0,1,4,5],[2,3,6,7],[0,2,4,6],[1,3,5,7],[4,5,6,7]]; var vertexPosition = []; for(var i = 0; i < 6; i++){ for(var j = 0; j < 4; j++){ vertexPosition = vertexPosition.concat(position[index[i][j]]); } } var vertexTexCoord = []; for(var i = 0; i < 6; i++){ for(var j = 0; j < 4; j++){ vertexTexCoord = vertexTexCoord.concat(uv[j]); } } // Buffer作成 var vbuffer = context3d.createBuffer(); context3d.bindBuffer(context3d.ARRAY_BUFFER, vbuffer); context3d.bufferData(context3d.ARRAY_BUFFER, new Float32Array(vertexPosition), context3d.STATIC_DRAW); vbuffer.itemSize = 3; this.vbuffer = this.vbuffer.concat(vbuffer); vbuffer = context3d.createBuffer(); context3d.bindBuffer(context3d.ARRAY_BUFFER, vbuffer); context3d.bufferData(context3d.ARRAY_BUFFER, new Float32Array(vertexTexCoord), context3d.STATIC_DRAW); vbuffer.itemSize = 2; this.vbuffer = this.vbuffer.concat(vbuffer); context3d.bindBuffer(context3d.ARRAY_BUFFER, null); } Box.prototype.initIndexBuffer = function(context3d){ var index = []; var base = 0; for(var i = 0 ; i < 6 ; i++) { index = index.concat(base + 0); index = index.concat(base + 1); index = index.concat(base + 2); index = index.concat(base + 2); index = index.concat(base + 1); index = index.concat(base + 3); base += 4; } // バッファを作成 this.ibuffer = context3d.createBuffer(context3d); context3d.bindBuffer(context3d.ELEMENT_ARRAY_BUFFER, this.ibuffer); context3d.bufferData(context3d.ELEMENT_ARRAY_BUFFER, new Int16Array(index), context3d.STATIC_DRAW); context3d.bindBuffer(context3d.ELEMENT_ARRAY_BUFFER, null); this.indexCount = index.length; } Box.prototype.draw = function(context3d, shader){ context3d.enableVertexAttribArray(shader.attributes[0]); context3d.bindBuffer(context3d.ARRAY_BUFFER, this.vbuffer[0]); context3d.vertexAttribPointer(shader.attributes[0], this.vbuffer[0].itemSize, context3d.FLOAT, false, 0, 0); context3d.enableVertexAttribArray(shader.attributes[1]); context3d.bindBuffer(context3d.ARRAY_BUFFER, this.vbuffer[1]); context3d.vertexAttribPointer(shader.attributes[1], this.vbuffer[1].itemSize, context3d.FLOAT, false, 0, 0); context3d.bindBuffer(context3d.ELEMENT_ARRAY_BUFFER, this.ibuffer); context3d.drawElements(context3d.TRIANGLES, this.indexCount, context3d.UNSIGNED_SHORT, 0); } //! 描画シーン function Scene(context3d){ this.updateTexture = false; this.image = null; this.texture = this.createEmptyTexture(context3d); this.shader = new Shader( context3d, window.document.getElementById("vshader").text, window.document.getElementById("fshader").text, ["position", "uv"], ["mMatrix", "vpMatrix"]); this.box = new Box(context3d); this.viewMatrix = mat4.create(); mat4.identity(this.viewMatrix); mat4.translate(this.viewMatrix, [0, 0, -8]); this.projectionMatrix = mat4.create(); mat4.identity(this.projectionMatrix); mat4.perspective(30, Screen.width / Screen.height, 0.1, 1000, this.projectionMatrix); this.count = 0; } Scene.prototype.draw = function(context3d){ this.count++; if(this.updateTexture){ this.updateTexture = false; this.texture = this.createTexture(context3d, this.image); } context3d.disable(context3d.CULL_FACE); context3d.useProgram(this.shader.program); var matrix = mat4.create(); mat4.identity(matrix); mat4.rotate(matrix, 10 * this.count * Math.PI / 180, [0, 1, 0]); var camMatrix = mat4.create(this.projectionMatrix); mat4.multiply(camMatrix, this.viewMatrix); context3d.uniformMatrix4fv(this.shader.uniforms[0], false, matrix); context3d.uniformMatrix4fv(this.shader.uniforms[1], false, camMatrix); context3d.bindTexture(context3d.TEXTURE_2D, this.texture); context3d.texParameteri(context3d.TEXTURE_2D, context3d.TEXTURE_MAG_FILTER, context3d.LINEAR); context3d.texParameteri(context3d.TEXTURE_2D, context3d.TEXTURE_MIN_FILTER, context3d.LINEAR_MIPMAP_LINEAR); context3d.texParameteri(context3d.TEXTURE_2D, context3d.TEXTURE_WRAP_S, context3d.CLAMP_TO_EDGE); context3d.texParameteri(context3d.TEXTURE_2D, context3d.TEXTURE_WRAP_T, context3d.CLAMP_TO_EDGE); this.box.draw(context3d, this.shader); } Scene.prototype.dragFile = function(event){ if (event.dataTransfer.types[0] == "Files"){ event.preventDefault(); } } Scene.prototype.dropFile = function(event){ var file = event.dataTransfer.files[0]; if(file.type.match(/image\/\w+/)){ var reader = new FileReader(); reader.filename = event.dataTransfer.files[0].fileName; var _this = this; // エンコード完了後のコールバック reader.onloadend = function() { _this.setupImage(reader); } // Base64URI変換 reader.readAsDataURL(file); } } Scene.prototype.setupImage = function(reader){ if(reader.error){ return; } this.image = new Image(); this.image.src = reader.result; this.image = this.checkSize(this.image); this.updateTexture = true; } Scene.prototype.createEmptyTexture = function(context3d){ var canvas = document.createElement('canvas'); canvas.height = canvas.width = 8; var context = canvas.getContext('2d'); context.fillStyle = "#ffffff"; context.fillRect(0, 0, canvas.width, canvas.height); return this.createTexture(context3d, canvas); } Scene.prototype.createTexture = function(context3d, image){ var texture = context3d.createTexture(); context3d.bindTexture(context3d.TEXTURE_2D, texture); context3d.texImage2D(context3d.TEXTURE_2D, 0, context3d.RGBA, context3d.RGBA, context3d.UNSIGNED_BYTE, image); context3d.generateMipmap(context3d.TEXTURE_2D); context3d.bindTexture(context3d.TEXTURE_2D, null); return texture; } Scene.prototype.checkSize = function(image) { var w = image.naturalWidth; var h = image.naturalHeight; var size = Math.pow(2, Math.log(Math.min(w, h)) / Math.LN2 | 0); if (w !== h || w !== size) { var canvas = document.createElement('canvas'); canvas.height = canvas.width = size; canvas.getContext('2d').drawImage(image, 0, 0, w, h, 0, 0, size, size); image = canvas; } return image; } //! フレーム管理クラス function Frame(){ this.enabled = false; // コンテキストの準備 this.canvas = window.document.getElementById("canvas"); var contextName = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; for(var i = 0; i < contextName.length; i++){ try{ this.context3d = this.canvas.getContext(contextName[i]); }catch(e){ } if(this.context3d){ break; } } if(!this.context3d){ return; } this.canvas.width = Screen.width; this.canvas.height = Screen.height; this.context3d.viewport(0, 0, Screen.width, Screen.height); this.context3d.enable(this.context3d.TEXTURE_2D); this.clearColor = new Color(0.1,0.1,0.1,1); this.clearDepth = 1000; this.enabled = true; this.drawFunc = null; } Frame.prototype.clear = function(){ var cc = this.clearColor; this.context3d.clearColor(cc.r, cc.g, cc.b, cc.a); this.context3d.clearDepth(this.clearDepth); this.context3d.clear(this.context3d.COLOR_BUFFER_BIT | this.context3d.DEPTH_BUFFER_BIT); } Frame.prototype.run = function(lastTime){ // クリア this.clear(); this.context3d.enable(this.context3d.DEPTH_TEST); this.drawFunc(this.context3d); this.context3d.flush(); //! 更新を行う var nowTime = new Date(); // 20FPS var nextTime = 50 - (nowTime.getTime() - lastTime.getTime()); if(nextTime <= 0){ nextTime = 1; } var _this = this; setTimeout(function(){_this.run(nowTime);}, nextTime); } </script> </html>
CATEGORY
- about ヘキサ (166)
- 部活動 (6)
- CG (18)
- プロジェクトマネジメント (1)
- 研修 (5)
- 美学 (1)
- いいモノづくり道 (230)
- 採用 -お役立ち情報も- (149)
- プログラム (188)
- デザイン (99)
- ゲーム (274)
- 日記 (1,104)
- 書籍紹介 (113)
- その他 (875)
- 就活アドバイス (20)
- ラーメン (3)
- ライフハック (25)
- イベント紹介 (10)
- 料理 (23)
- TIPS (7)
- 怖い話 (3)
- サウンド (5)
- 子育て (1)
- 筋トレ (1)
- 商品紹介 (21)
- アプリ紹介 (31)
- ソフトウェア紹介 (33)
- ガジェット紹介 (12)
- サイト紹介 (10)
- 研究・開発 (34)
- 回路図 (4)
- アナログゲーム (40)
- 交流会 (21)
- 報告会 (3)
- インフラ (25)
- グリとブラン (6)
- カメラ (9)
- クラフト (27)
- 部活 (14)
- 画伯 (15)
- カレー (6)
- 音楽(洋楽) (6)
- 映画・舞台鑑賞 (43)
- 飼育 (5)
- いぬ (8)
- ねこ (19)
ARCHIVE
- 2024年
- 2023年
- 2022年
- 2021年
- 2020年
- 2019年
- 2018年
- 2017年
- 2016年
- 2015年
- 2014年
- 2013年
- 2012年
- 2011年
- 2010年
- 2009年
- 2008年
- 2007年