├── data ├── models │ ├── table-indices.bin │ ├── smoke100-indices.bin │ ├── sky-indices.bin │ ├── sky-strides.bin │ ├── table-strides.bin │ ├── buddha-indices.bin │ ├── buddha-strides.bin │ ├── shafts-indices.bin │ ├── shafts-strides.bin │ ├── smoke100-strides.bin │ ├── particles_20-strides.bin │ └── particles_20-indices.bin └── textures │ ├── dust.png │ ├── shafts.png │ ├── smoke.png │ ├── buddha_lm.png │ ├── sky │ └── sky1.png │ ├── buddha-normals.png │ ├── sphere_gold3.png │ └── table │ ├── marble.pkm │ ├── marble.png │ └── table_lm.png ├── .gitignore ├── favicon.ico ├── icon-192.png ├── sass-watch.bat ├── styles ├── scss │ ├── _common.scss │ └── site.scss └── css │ ├── site.css.map │ └── site.css ├── js ├── app.js ├── app │ ├── framework │ │ ├── BinaryDataLoader.js │ │ ├── UncompressedTextureLoader.js │ │ ├── TextureUtils.js │ │ ├── CompressedTextureLoader.js │ │ ├── FullModel.js │ │ ├── FrameBuffer.js │ │ ├── BaseShader.js │ │ ├── utils │ │ │ └── FullscreenUtils.js │ │ └── BaseRenderer.js │ ├── VignetteData.js │ ├── DiffuseShader.js │ ├── PointSpriteScaledColoredShader.js │ ├── main.js │ ├── LMTableShader.js │ ├── LightShaftShader.js │ ├── SoftDiffuseColoredShader.js │ ├── SphericalMapLMShader.js │ └── BuddhaRenderer.js └── lib │ └── require.js ├── .editorconfig ├── manifest.json ├── LICENSE.txt ├── README.md └── index.html /data/models/table-indices.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.sass-cache 2 | /node_modules 3 | /temp 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/favicon.ico -------------------------------------------------------------------------------- /icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/icon-192.png -------------------------------------------------------------------------------- /sass-watch.bat: -------------------------------------------------------------------------------- 1 | sass --watch styles\scss\site.scss:styles\css\site.css --style compressed 2 | -------------------------------------------------------------------------------- /data/textures/dust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/dust.png -------------------------------------------------------------------------------- /data/models/smoke100-indices.bin: -------------------------------------------------------------------------------- 1 |   2 | 3 | -------------------------------------------------------------------------------- /data/textures/shafts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/shafts.png -------------------------------------------------------------------------------- /data/textures/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/smoke.png -------------------------------------------------------------------------------- /styles/scss/_common.scss: -------------------------------------------------------------------------------- 1 | $screen-sm-min: 768px; 2 | $screen-md-min: 992px; 3 | $screen-lg-min: 1200px; 4 | -------------------------------------------------------------------------------- /data/models/sky-indices.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/sky-indices.bin -------------------------------------------------------------------------------- /data/models/sky-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/sky-strides.bin -------------------------------------------------------------------------------- /data/textures/buddha_lm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/buddha_lm.png -------------------------------------------------------------------------------- /data/textures/sky/sky1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/sky/sky1.png -------------------------------------------------------------------------------- /data/models/table-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/table-strides.bin -------------------------------------------------------------------------------- /data/models/buddha-indices.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/buddha-indices.bin -------------------------------------------------------------------------------- /data/models/buddha-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/buddha-strides.bin -------------------------------------------------------------------------------- /data/models/shafts-indices.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/shafts-indices.bin -------------------------------------------------------------------------------- /data/models/shafts-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/shafts-strides.bin -------------------------------------------------------------------------------- /data/models/smoke100-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/smoke100-strides.bin -------------------------------------------------------------------------------- /data/textures/buddha-normals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/buddha-normals.png -------------------------------------------------------------------------------- /data/textures/sphere_gold3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/sphere_gold3.png -------------------------------------------------------------------------------- /data/textures/table/marble.pkm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/table/marble.pkm -------------------------------------------------------------------------------- /data/textures/table/marble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/table/marble.png -------------------------------------------------------------------------------- /data/textures/table/table_lm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/textures/table/table_lm.png -------------------------------------------------------------------------------- /data/models/particles_20-strides.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keaukraine/webgl-buddha/HEAD/data/models/particles_20-strides.bin -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | 'baseUrl': 'js/app', 3 | 'paths': { 4 | 'app': '../app' 5 | } 6 | }); 7 | 8 | requirejs(['app/main']); 9 | -------------------------------------------------------------------------------- /data/models/particles_20-indices.bin: -------------------------------------------------------------------------------- 1 |    2 |  3 |  4 |       5 |   6 |   7 |       -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.html] 2 | indent_style = space 3 | indent_size = 4 4 | 5 | [*.css] 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.scss] 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [*.js] 14 | indent_style = space 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "3D Buddha WebGL Demo", 3 | "name": "Buddha Live Wallpaper Web Demo", 4 | "icons": [ 5 | { 6 | "src": "icon-192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "/index.html", 12 | "display": "fullscreen", 13 | "orientation": "portrait", 14 | "background_color": "#111111", 15 | "theme_color": "#111111" 16 | } 17 | -------------------------------------------------------------------------------- /js/app/framework/BinaryDataLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function() { 4 | 5 | function BinaryDataLoader() {} 6 | 7 | /** 8 | * Static function to load binary data 9 | * @param {string} url - URL for content 10 | * @param {Function} - callback to receive binary data 11 | */ 12 | BinaryDataLoader.load = function(url, callback) { 13 | var root = this, 14 | xhr = new XMLHttpRequest(); 15 | 16 | xhr.open('GET', url, true); 17 | xhr.responseType = 'arraybuffer'; 18 | xhr.onload = function() { 19 | callback && callback(this.response); 20 | }; 21 | xhr.send(null); 22 | } 23 | 24 | return BinaryDataLoader; 25 | }); 26 | -------------------------------------------------------------------------------- /js/app/VignetteData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function() { 4 | 5 | /** 6 | * Helper class to render billboard taking whole screen. 7 | * @class 8 | */ 9 | function VignetteData() { 10 | this.quadTriangles = [ 11 | // X, Y, Z, U, V 12 | -1.0, -1.0, -5.0, 0.0, 0.0, // 0. left-bottom 13 | 1.0, -1.0, -5.0, 1.0, 0.0, // 1. right-bottom 14 | -1.0, 1.0, -5.0, 0.0, 1.0, // 2. left-top 15 | 1.0, 1.0, -5.0, 1.0, 1.0 // 3. right-top 16 | ]; 17 | } 18 | 19 | VignetteData.prototype = { 20 | buffer: null, 21 | 22 | initGL: function(gl) { 23 | this.buffer = gl.createBuffer(); 24 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); 25 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.quadTriangles), gl.STATIC_DRAW); 26 | } 27 | } 28 | 29 | return VignetteData; 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Oleksandr Popov, Dmytro Popov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 4 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 5 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 6 | is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 11 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 12 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 13 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /js/app/DiffuseShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function(BaseShader) { 4 | 5 | /** 6 | * Simple diffuse shader. 7 | * @class 8 | */ 9 | class DiffuseShader extends BaseShader { 10 | fillCode() { 11 | this.vertexShaderCode = 'uniform mat4 view_proj_matrix;\n' + 12 | 'attribute vec4 rm_Vertex;\n' + 13 | 'attribute vec2 rm_TexCoord0;\n' + 14 | 'varying vec2 vTextureCoord;\n' + 15 | '\n' + 16 | 'void main() {\n' + 17 | ' gl_Position = view_proj_matrix * rm_Vertex;\n' + 18 | ' vTextureCoord = rm_TexCoord0;\n' + 19 | '}'; 20 | 21 | this.fragmentShaderCode = 'precision mediump float;\n' + 22 | 'varying vec2 vTextureCoord;\n' + 23 | 'uniform sampler2D sTexture;\n' + 24 | '\n' + 25 | 'void main() {\n' + 26 | ' gl_FragColor = texture2D(sTexture, vTextureCoord);\n' + 27 | '}'; 28 | } 29 | 30 | fillUniformsAttributes() { 31 | this.view_proj_matrix = this.getUniform('view_proj_matrix'); 32 | this.rm_Vertex = this.getAttrib('rm_Vertex'); 33 | this.rm_TexCoord0 = this.getAttrib('rm_TexCoord0'); 34 | this.sTexture = this.getUniform('sTexture'); 35 | } 36 | } 37 | 38 | return DiffuseShader; 39 | }); 40 | -------------------------------------------------------------------------------- /js/app/framework/UncompressedTextureLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function() { 4 | 5 | /** 6 | * Constructor. No need to create an instance of this class because it has only static methods 7 | */ 8 | function UncompressedTextureLoader() {} 9 | 10 | /** 11 | * Loads texture from any image format supported by browser 12 | * @param {string} url - URL to texture 13 | * @param {Function} callbak - callback called after texture is loaded to GPU 14 | * @return {number} - WebGL texture 15 | */ 16 | UncompressedTextureLoader.load = function(url, callback) { 17 | var texture = gl.createTexture(); 18 | 19 | texture.image = new Image(); 20 | texture.image.src = url; 21 | texture.image.onload = function() { 22 | gl.bindTexture(gl.TEXTURE_2D, texture); 23 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); 24 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 25 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 26 | gl.bindTexture(gl.TEXTURE_2D, null); 27 | 28 | if (texture.image && texture.image.src) { 29 | console.log('Loaded texture ' + url + ' [' + texture.image.width + 'x' + texture.image.height + ']'); 30 | } 31 | 32 | callback && callback(); 33 | }; 34 | 35 | return texture; 36 | } 37 | 38 | return UncompressedTextureLoader; 39 | }); 40 | -------------------------------------------------------------------------------- /styles/css/site.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAEA,IAAK,CACD,gBAAgB,CAAE,IAAI,CAG1B,aAAc,CACV,KAAK,CAAE,IAAI,CAGf,SAAU,CACN,OAAO,CAAE,KAAK,CACd,KAAK,CAAE,KAAK,CAEZ,MAAM,CAAE,KAAK,CACb,MAAM,CAAE,IAAI,CACZ,OAAO,CAAE,CAAC,CACV,MAAM,CAAE,MAAM,CAGlB,YAAa,CACT,OAAO,CAAE,YAAY,CAGzB,MAAO,CACH,OAAO,CAAE,CAAC,CACV,QAAQ,CAAE,QAAQ,CAClB,MAAM,CAAE,GAAG,CACX,KAAK,CAAE,IAAI,CACX,UAAU,CAAE,MAAM,CAClB,UAAU,CAAE,mDAAmD,CAE/D,UAAI,CACA,KAAK,CAAE,KAAK,CAGhB,YAAM,CACF,KAAK,CAAE,IAAI,CACX,WAAW,CAAE,mBAAmB,CAIxC,SAAU,CACN,gBAAgB,CAAE,IAAI,CACtB,QAAQ,CAAE,QAAQ,CAClB,GAAG,CAAE,CAAC,CACN,MAAM,CAAE,CAAC,CACT,IAAI,CAAE,CAAC,CACP,KAAK,CAAE,CAAC,CACR,OAAO,CAAE,IAAI,CACb,WAAW,CAAE,IAAI,CACjB,OAAO,CAAE,GAAG,CAEZ,UAAU,CAAE,kDAAkD,CAE9D,yBAAkC,CAbtC,SAAU,CAcF,WAAW,CAAE,IAAI,CACjB,YAAY,CAAE,IAAI,EAGtB,aAAM,CACF,OAAO,CAAE,CAAC,CACV,WAAW,CAAE,IAAI,CAEjB,yBAAkC,CAJtC,aAAM,CAKE,WAAW,CAAE,CAAC,CACd,YAAY,CAAE,IAAI,EAI1B,eAAM,CACF,KAAK,CAAE,IAAI,CACX,WAAW,CAAE,mBAAwB,CAI7C,aAAc,CACV,OAAO,CAAE,IAAI,CACb,QAAQ,CAAE,QAAQ,CAElB,UAAU,CAAE,kDAAkD,CAE9D,6BAAgB,CACZ,KAAK,CAAE,IAAI,CACX,WAAW,CAAE,mBAAwB,CAGzC,wBAAa,CACT,KAAK,CAAE,IAAI,CACX,GAAG,CAAE,IAAI,CAET,iCAAkC,CAJtC,wBAAa,CAKL,OAAO,CAAE,eAAe,EAG5B,uCAAe,CACX,OAAO,CAAE,YAAY,CAGzB,sCAAc,CACV,OAAO,CAAE,IAAI,CAIb,2CAAe,CACX,OAAO,CAAE,IAAI,CAGjB,0CAAc,CACV,OAAO,CAAE,YAAY,CAKjC,8BAAmB,CACf,IAAI,CAAE,IAAI,CACV,GAAG,CAAE,IAAI,CAET,kDAAoB,CAChB,OAAO,CAAE,YAAY,CAGzB,kDAAoB,CAChB,OAAO,CAAE,IAAI,CAGjB,kCAAM,CACF,IAAI,CAAE,IAAI,CACV,GAAG,CAAE,IAAI,CAIT,uDAAoB,CAChB,OAAO,CAAE,IAAI,CAGjB,uDAAoB,CAChB,OAAO,CAAE,YAAY,CAOjC,qBAAQ,CACJ,SAAS,CAAE,IAAI,CAEnB,qBAAQ,CACJ,SAAS,CAAE,IAAI,CAEnB,qBAAQ,CACJ,SAAS,CAAE,IAAI,CAEnB,qBAAQ,CACJ,SAAS,CAAE,IAAI", 4 | "sources": ["../scss/site.scss"], 5 | "names": [], 6 | "file": "site.css" 7 | } -------------------------------------------------------------------------------- /js/app/PointSpriteScaledColoredShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function (BaseShader) { 4 | /** 5 | * Shader to render colored point sprites. 6 | */ 7 | class PointSpriteScaledColoredShader extends BaseShader { 8 | fillCode() { 9 | this.vertexShaderCode = "uniform mat4 uMvp;\n" + 10 | "uniform float uThickness;\n" + 11 | "\n" + 12 | "attribute vec3 aPosition;\n" + 13 | "\n" + 14 | "void main() {\n" + 15 | " vec4 position = uMvp * vec4(aPosition.xyz, 1.0);\n" + 16 | " vec3 ndc = position.xyz / position.w; // perspective divide.\n" + 17 | " float zDist = 1.0 - ndc.z; // 1 is close (right up in your face,)\n" + 18 | " gl_PointSize = uThickness * zDist;\n" + 19 | " gl_Position = position; \n" + 20 | "}"; 21 | this.fragmentShaderCode = "precision mediump float;\n" + 22 | "uniform sampler2D tex0;\n" + 23 | "uniform vec4 color;\n" + 24 | "\n" + 25 | "void main() \n" + 26 | "{\n" + 27 | " gl_FragColor = texture2D(tex0, gl_PointCoord) * color;\n" + 28 | "}"; 29 | } 30 | 31 | fillUniformsAttributes() { 32 | this.uMvp = this.getUniform("uMvp"); 33 | this.uThickness = this.getUniform("uThickness"); 34 | this.aPosition = this.getAttrib("aPosition"); 35 | this.tex0 = this.getUniform("tex0"); 36 | this.color = this.getUniform("color"); 37 | } 38 | } 39 | 40 | return PointSpriteScaledColoredShader; 41 | }); 42 | -------------------------------------------------------------------------------- /styles/css/site.css: -------------------------------------------------------------------------------- 1 | body{background-color:#000}#row-progress{color:#fff}.canvasGL{display:block;width:100vw;height:100vh;border:none;padding:0;margin:0 auto}.transparent{opacity:0 !important}.promo{opacity:1;position:absolute;bottom:5vh;width:100%;text-align:center;transition:opacity 1000ms cubic-bezier(0.645, 0.045, 0.355, 1)}.promo img{width:220px}.promo .text{color:#fff;text-shadow:#030303 0px 0px 5px}.controls{background-color:#000;position:absolute;top:0;bottom:0;left:0;right:0;padding:20px;padding-top:60px;opacity:1.0;transition:opacity 800ms cubic-bezier(0.645, 0.045, 0.355, 1)}@media (min-width: 768px){.controls{padding-top:20px;padding-left:50px}}.fs .controls{padding:0;padding-top:60px}@media (min-width: 768px){.fs .controls{padding-top:0;padding-left:30px}}.controls label{color:#fff;text-shadow:#030303 0px 0px 8px}.control-icon{opacity:0.85;position:absolute;transition:opacity 800ms cubic-bezier(0.645, 0.045, 0.355, 1)}.control-icon .material-icons{color:#fff;text-shadow:#030303 0px 0px 8px}.control-icon.control-fs{right:30px;top:30px}@media (display-mode: fullscreen){.control-icon.control-fs{display:none !important}}.control-icon.control-fs .icon-enter-fs{display:inline-block}.control-icon.control-fs .icon-exit-fs{display:none}.fs .control-icon.control-fs .icon-enter-fs{display:none}.fs .control-icon.control-fs .icon-exit-fs{display:inline-block}.control-icon.control-settings{left:40px;top:40px}.control-icon.control-settings .icon-show-settings{display:inline-block}.control-icon.control-settings .icon-hide-settings{display:none}.fs .control-icon.control-settings{left:20px;top:20px}.control-icon.control-settings.open .icon-show-settings{display:none}.control-icon.control-settings.open .icon-hide-settings{display:inline-block}.material-icons.md-18{font-size:18px}.material-icons.md-24{font-size:24px}.material-icons.md-36{font-size:36px}.material-icons.md-48{font-size:48px} 2 | /*# sourceMappingURL=site.css.map */ 3 | -------------------------------------------------------------------------------- /js/app/framework/TextureUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(() => { 4 | 5 | function TextureUtils() { } 6 | 7 | TextureUtils.createNPOTTexture = (texWidth, texHeight, bUseAlpha) => { 8 | const textureID = gl.createTexture(); 9 | gl.bindTexture(gl.TEXTURE_2D, textureID); 10 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 11 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 12 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 13 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 14 | 15 | let glFormat = null, glInternalFormat = null; 16 | 17 | if (bUseAlpha) { 18 | glFormat = gl.RGBA; 19 | glInternalFormat = gl.RGBA; 20 | } else { 21 | glFormat = gl.RGB; 22 | glInternalFormat = gl.RGB; 23 | } 24 | 25 | gl.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, texWidth, texHeight, 0, glFormat, gl.UNSIGNED_BYTE, null); 26 | 27 | return textureID; 28 | }; 29 | 30 | TextureUtils.createDepthTexture = (texWidth, texHeight) => { 31 | const textureID = gl.createTexture(); 32 | gl.bindTexture(gl.TEXTURE_2D, textureID); 33 | 34 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 35 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 36 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 37 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 38 | 39 | const glFormat = gl.DEPTH_COMPONENT; 40 | const glInternalFormat = gl.DEPTH_COMPONENT16; 41 | const type = gl.UNSIGNED_SHORT; 42 | 43 | // In WebGL, we cannot pass array to depth texture. 44 | gl.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, texWidth, texHeight, 0, glFormat, type, null); 45 | 46 | return textureID; 47 | }; 48 | 49 | return TextureUtils; 50 | }); 51 | -------------------------------------------------------------------------------- /js/app/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'BuddhaRenderer', 5 | 'framework/utils/FullscreenUtils' 6 | ], 7 | function ( 8 | BuddhaRenderer, 9 | FullScreenUtils) { 10 | 11 | var renderer; 12 | 13 | /** 14 | * Initialize renderer with current scene configuration 15 | */ 16 | function initRenderer() { 17 | window.gl = null; 18 | 19 | if (renderer) { 20 | renderer.resetLoaded(); 21 | oldYaw = renderer.angleYaw; 22 | } 23 | 24 | renderer = new BuddhaRenderer(); 25 | 26 | renderer.init('canvasGL'); 27 | renderer.angleYaw = 30; 28 | } 29 | 30 | function loadedHandler() { 31 | initRenderer(); 32 | 33 | if (FullScreenUtils.isFullScreenSupported()) { 34 | const toggleFullscreenElement = document.getElementById('toggleFullscreen'); 35 | 36 | toggleFullscreenElement.addEventListener('click', () => { 37 | if (document.body.classList.contains('fs')) { 38 | FullScreenUtils.exitFullScreen(); 39 | } else { 40 | FullScreenUtils.enterFullScreen(); 41 | } 42 | FullScreenUtils.addFullScreenListener(function () { 43 | if (FullScreenUtils.isFullScreen()) { 44 | document.body.classList.add('fs'); 45 | } else { 46 | document.body.classList.remove('fs'); 47 | } 48 | }); 49 | }); 50 | } else { 51 | toggleFullscreenElement.classList.add('hidden'); 52 | } 53 | } 54 | 55 | if (document.readyState === 'loading') { 56 | document.addEventListener('DOMContentLoaded', loadedHandler); 57 | } else { 58 | loadedHandler(); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /js/app/LMTableShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function(BaseShader) { 4 | 5 | /** 6 | * Shader for table. Uses diffuse and lightmap textures. 7 | * @class 8 | */ 9 | class LMTableShader extends BaseShader { 10 | fillCode() { 11 | this.vertexShaderCode = 'uniform mat4 view_proj_matrix;\n' + 12 | 'attribute vec4 rm_Vertex;\n' + 13 | 'attribute vec2 rm_TexCoord0;\n' + 14 | 'attribute vec2 rm_TexCoord1;\n' + 15 | 'varying vec2 vTextureCoord;\n' + 16 | 'varying vec2 vTextureCoordLM;\n' + 17 | 'uniform float diffuseScale;\n' + 18 | '\n' + 19 | 'void main() {\n' + 20 | ' gl_Position = view_proj_matrix * rm_Vertex;\n' + 21 | ' vTextureCoord = rm_TexCoord0 * diffuseScale;\n' + 22 | ' vTextureCoordLM = rm_TexCoord1;\n' + 23 | '}'; 24 | 25 | this.fragmentShaderCode = 'precision mediump float;\n' + 26 | 'varying vec2 vTextureCoord;\n' + 27 | 'varying vec2 vTextureCoordLM;\n' + 28 | 'uniform sampler2D sTexture;\n' + 29 | 'uniform sampler2D sLM;\n' + 30 | '\n' + 31 | 'void main() {\n' + 32 | ' vec4 lmColor = texture2D(sLM, vTextureCoordLM);\n' + 33 | ' gl_FragColor = texture2D(sTexture, vTextureCoord) * lmColor.r * lmColor.g;\n' + 34 | ' gl_FragColor.a = lmColor.g;\n' + 35 | '}'; 36 | } 37 | 38 | fillUniformsAttributes() { 39 | this.view_proj_matrix = this.getUniform('view_proj_matrix'); 40 | this.rm_Vertex = this.getAttrib('rm_Vertex'); 41 | this.rm_TexCoord0 = this.getAttrib('rm_TexCoord0'); 42 | this.rm_TexCoord1 = this.getAttrib('rm_TexCoord1'); 43 | this.sTexture = this.getUniform('sTexture'); 44 | this.sLM = this.getUniform('sLM'); 45 | this.diffuseScale = this.getUniform('diffuseScale'); 46 | } 47 | } 48 | 49 | return LMTableShader; 50 | }); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D Buddha WebGL Demo 2 | 3 | This demo is a WebGL port of Android live wallpaper *[3D Buddha Live Wallpaper](https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperbuddha)*. 4 | 5 | Live demo page is available here: https://keaukraine.github.io/webgl-buddha/index.html 6 | 7 | Article explaining implementation of soft particles used in this demo - https://keaukraine.medium.com/implementing-soft-particles-in-webgl-and-opengl-es-b968d61133b0 8 | 9 | Works in latest Chrome, Firefox, Safari and Edge. 10 | 11 | # Used Libraries 12 | 13 | * Twitter Bootstrap used under the MIT License https://github.com/twbs/bootstrap/blob/master/LICENSE 14 | * Material Icons by Google used under Apache License https://design.google.com/icons/ 15 | * RequireJS, Released under the MIT license https://github.com/requirejs/requirejs/blob/master/LICENSE 16 | * gl-matrix, a high performance matrix and vector operations by Brandon Jones and Colin MacKenzie IV 17 | * WebGL initialization code is based on http://learningwebgl.com/ tutorials 18 | * Google Play and the Google Play logo are trademarks of Google LLC. 19 | 20 | # License 21 | 22 | **The MIT License** 23 | 24 | Copyright (c) 2018 Oleksandr Popov, Dmytro Popov 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /js/app/framework/CompressedTextureLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./BinaryDataLoader'], function(BinaryDataLoader) { 4 | 5 | /** 6 | * Constructor. No need to create an instance of this class because it has only static methods 7 | */ 8 | function CompressedTextureLoader() {} 9 | 10 | /** 11 | * Loads ETC1 texture from PKM format 12 | * @param {string} url - URL to texture in PKM format 13 | * @param {Function} callbak - callback called after texture is loaded to GPU 14 | * @return {number} - WebGL texture 15 | */ 16 | CompressedTextureLoader.loadETC1 = function(url, callback) { 17 | var root = this, 18 | texture = gl.createTexture(); 19 | 20 | var PKM_HEADER_SIZE = 16; // size of PKM header 21 | var PKM_HEADER_WIDTH_OFFSET = 8; // offset to texture width 22 | var PKM_HEADER_HEIGHT_OFFSET = 10; // offset to texture height 23 | 24 | BinaryDataLoader.load(url, function(data) { 25 | var bufWidth, bufHeight, bufData, 26 | width, height; 27 | 28 | var ETC1_RGB8_OES = 36196; 29 | 30 | if (data.byteLength > 0) { 31 | // Endianness depends on machine architecture, can't read Int16 32 | // In PKM, width and height are big-endian, and x86 is little-endian and ARM is bi-endian 33 | bufWidth = new Uint8Array(data, PKM_HEADER_WIDTH_OFFSET, 2); 34 | width = bufWidth[0]*256 + bufWidth[1]; 35 | bufHeight = new Uint8Array(data, PKM_HEADER_HEIGHT_OFFSET, 2); 36 | height = bufHeight[0]*256 + bufHeight[1]; 37 | bufData = new Uint8Array(data, PKM_HEADER_SIZE, data.byteLength - PKM_HEADER_SIZE); 38 | 39 | gl.bindTexture(gl.TEXTURE_2D, texture); 40 | gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ETC1_RGB8_OES, width, height, 0, bufData); 41 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 42 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 43 | gl.bindTexture(gl.TEXTURE_2D, null); 44 | 45 | console.log('Loaded texture ' + url + ' [' + width + 'x' + height + ']'); 46 | 47 | callback && callback(); 48 | } 49 | }); 50 | 51 | return texture; 52 | } 53 | 54 | return CompressedTextureLoader; 55 | }); 56 | -------------------------------------------------------------------------------- /js/app/framework/FullModel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./BinaryDataLoader'], function(BinaryDataLoader) { 4 | 5 | /** 6 | * Class to represent mesh data 7 | * @class 8 | */ 9 | function FullModel() { 10 | this.bufferIndices = null; 11 | this.bufferStrides = null; 12 | this.numIndices = 0; 13 | } 14 | 15 | FullModel.prototype = { 16 | 17 | /** 18 | * Loads binary data for mesh 19 | * @param {string} url - URL for mesh files without trailing "-*.bin" 20 | * @param {Function} callback when data is loaded 21 | */ 22 | load: function(url, callback) { 23 | var root = this; 24 | 25 | function loadBuffer(buffer, target, arrayBuffer) { 26 | var byteArray = new Uint8Array(arrayBuffer, 0, arrayBuffer.byteLength); 27 | gl.bindBuffer(target, buffer); 28 | gl.bufferData(target, byteArray, gl.STATIC_DRAW); 29 | } 30 | 31 | BinaryDataLoader.load(url + '-indices.bin', 32 | function(data) { 33 | root.bufferIndices = gl.createBuffer(); 34 | console.log('Loaded ' + url + '-indices.bin: ' + data.byteLength + ' bytes'); 35 | loadBuffer(root.bufferIndices, gl.ELEMENT_ARRAY_BUFFER, data); 36 | root.numIndices = data.byteLength / 2 / 3; 37 | root.bufferIndices && root.bufferStrides && callback(); 38 | } 39 | ); 40 | BinaryDataLoader.load(url + '-strides.bin', 41 | function(data) { 42 | root.bufferStrides = gl.createBuffer(); 43 | console.log('Loaded ' + url + '-strides.bin: ' + data.byteLength + ' bytes'); 44 | loadBuffer(root.bufferStrides, gl.ARRAY_BUFFER, data); 45 | root.bufferIndices && root.bufferStrides && callback(); 46 | } 47 | ); 48 | }, 49 | 50 | /** 51 | * Binds buffers for glDrawElements() call 52 | */ 53 | bindBuffers: function() { 54 | gl.bindBuffer(gl.ARRAY_BUFFER, this.bufferStrides); 55 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.bufferIndices); 56 | }, 57 | 58 | /** 59 | * Returns number of inices in model 60 | * @return {number} - number of indices 61 | */ 62 | getNumIndices: function() { 63 | return this.numIndices; 64 | } 65 | } 66 | 67 | return FullModel; 68 | }); 69 | -------------------------------------------------------------------------------- /js/app/LightShaftShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function(BaseShader) { 4 | 5 | /** 6 | * Sahder to render coins. Uses spherical map for reflection and diffuse, normal and light maps. 7 | */ 8 | class LightShaftShader extends BaseShader { 9 | fillCode() { 10 | this.vertexShaderCode = 'precision highp float;\r\n' + 11 | 'uniform mat4 view_proj_matrix;\r\n' + 12 | 'uniform mat4 view_matrix;\r\n' + 13 | '\r\n' + 14 | 'attribute vec4 rm_Vertex;\r\n' + 15 | 'attribute vec2 rm_TexCoord0;\r\n' + 16 | 'attribute vec3 rm_Normal;\r\n' + 17 | '\r\n' + 18 | 'varying vec3 vNormal;\r\n' + 19 | 'varying vec2 vTexCoord0;\r\n' + 20 | '\r\n' + 21 | 'void main( void )\r\n' + 22 | '{\r\n' + 23 | ' gl_Position = view_proj_matrix * rm_Vertex;\r\n' + 24 | '\r\n' + 25 | ' vTexCoord0 = vec2(rm_TexCoord0);\r\n' + 26 | '\r\n' + 27 | ' vNormal = (view_matrix * vec4(rm_Normal, 0.0)).xyz;\r\n' + 28 | '}'; 29 | 30 | this.fragmentShaderCode = 'precision highp float;\r\n' + 31 | 'uniform sampler2D diffuseMap;\r\n' + 32 | 'varying vec2 vTexCoord0;\r\n' + 33 | 'varying vec3 vNormal;\r\n' + 34 | '\r\n' + 35 | 'void main()\r\n' + 36 | '{\r\n' + 37 | ' vec3 normColor = texture2D(diffuseMap,vTexCoord0).rgb;\r\n' + 38 | ' \r\n' + 39 | ' float rimAmount = clamp(pow(vNormal.b - 0.65, 1.0), 0.0, 1.0);' + 40 | ' ' + 41 | ' ' + 42 | ' gl_FragColor = vec4(normColor.r,normColor.g,normColor.b, normColor.b*rimAmount);\r\n' + 43 | '}'; 44 | } 45 | 46 | fillUniformsAttributes() { 47 | this.view_proj_matrix = this.getUniform('view_proj_matrix'); 48 | this.view_matrix = this.getUniform('view_matrix'); 49 | this.rm_Vertex = this.getAttrib('rm_Vertex'); 50 | this.rm_TexCoord0 = this.getAttrib('rm_TexCoord0'); 51 | this.rm_Normal = this.getAttrib('rm_Normal'); 52 | this.diffuseMap = this.getUniform('diffuseMap'); 53 | this.sphereMap = this.getUniform('sphereMap'); 54 | this.aoMap = this.getUniform('aoMap'); 55 | } 56 | } 57 | 58 | return LightShaftShader; 59 | }); 60 | -------------------------------------------------------------------------------- /js/app/framework/FrameBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(() => { 4 | 5 | class FrameBuffer { 6 | 7 | /** 8 | * Constructor 9 | */ 10 | constructor() { 11 | this.textureHandle = null; 12 | this.depthTextureHandle = null; 13 | this.framebufferHandle = null; 14 | this.depthbufferHandle = null; 15 | this.width = 0; 16 | this.height = 0; 17 | } 18 | 19 | /** 20 | * Creates OpenGL objects 21 | */ 22 | createGLData() { 23 | if (this.textureHandle !== null && this.width > 0 && this.height > 0) { 24 | this.framebufferHandle = gl.createFramebuffer(); // alternative to GLES20.glGenFramebuffers() 25 | 26 | if (this.textureHandle !== null) { 27 | gl.bindTexture(gl.TEXTURE_2D, this.textureHandle); 28 | 29 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebufferHandle); 30 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textureHandle, 0); 31 | this.checkGlError("FB"); 32 | } 33 | 34 | if (this.depthTextureHandle === null) { 35 | this.depthbufferHandle = gl.createRenderBuffer(); 36 | 37 | 38 | gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthbufferHandle); 39 | this.checkGlError("FB - glBindRenderbuffer"); 40 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); 41 | this.checkGlError("FB - glRenderbufferStorage"); 42 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthbufferHandle); 43 | this.checkGlError("FB - glFramebufferRenderbuffer"); 44 | } else { 45 | gl.bindTexture(gl.TEXTURE_2D, this.depthTextureHandle); 46 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebufferHandle); 47 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.depthTextureHandle, 0); 48 | this.checkGlError("FB depth"); 49 | } 50 | 51 | const result = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 52 | if (result != gl.FRAMEBUFFER_COMPLETE) { 53 | console.error(`Error creating framebufer: ${result}`); 54 | } 55 | 56 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 57 | // gl.bindTexture(gl.TEXTURE_2D, 0); 58 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 59 | } 60 | } 61 | 62 | checkGlError(op) { 63 | let error; 64 | 65 | while ((error = gl.getError()) !== gl.NO_ERROR) { 66 | console.error(`${op}: glError ${error}`); 67 | } 68 | } 69 | }; 70 | 71 | return FrameBuffer; 72 | }); 73 | -------------------------------------------------------------------------------- /js/app/SoftDiffuseColoredShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function (BaseShader) { 4 | 5 | /** 6 | * Shader for table. Uses diffuse and lightmap textures. 7 | * @class 8 | */ 9 | class SoftDiffuseColoredShader extends BaseShader { 10 | fillCode() { 11 | this.vertexShaderCode = "uniform mat4 view_proj_matrix;\n" + 12 | "attribute vec4 rm_Vertex;\n" + 13 | "attribute vec2 rm_TexCoord0;\n" + 14 | "varying vec2 vTextureCoord;\n" + 15 | "\n" + 16 | "void main() {\n" + 17 | " gl_Position = view_proj_matrix * rm_Vertex;\n" + 18 | " vTextureCoord = rm_TexCoord0;\n" + 19 | "}"; 20 | 21 | 22 | this.fragmentShaderCode = "precision highp float;\n" + 23 | "uniform vec2 uCameraRange;\n" + 24 | "uniform vec2 uInvViewportSize;\n" + 25 | "uniform float uTransitionSize;\n" + 26 | "float calc_depth(in float z)\n" + 27 | "{\n" + 28 | " return (2.0 * uCameraRange.x) / (uCameraRange.y + uCameraRange.x - z*(uCameraRange.y - uCameraRange.x));\n" + 29 | "}\n" + 30 | "uniform sampler2D sDepth;\n" + 31 | "varying vec2 vTextureCoord;\n" + 32 | "uniform sampler2D sTexture;\n" + 33 | "uniform vec4 color;\n" + 34 | "\n" + 35 | "void main() {\n" + 36 | " vec4 diffuse = texture2D(sTexture, vTextureCoord) * color;\n" + // particle base diffuse color 37 | // " diffuse += vec4(0.0, 0.0, 1.0, 1.0);\n"+ // uncomment to visualize particle shape 38 | " vec2 coords = gl_FragCoord.xy * uInvViewportSize;\n" + // calculate depth texture coordinates 39 | " float geometryZ = calc_depth(texture2D(sDepth, coords).r);\n" + // lineriarize particle depth 40 | " float sceneZ = calc_depth(gl_FragCoord.z);\n" + // lineriarize scene depth 41 | " float a = clamp(geometryZ - sceneZ, 0.0, 1.0);\n" + // linear clamped diff between scene and particle depth 42 | " float b = smoothstep(0.0, uTransitionSize, a);\n" + // apply smoothstep to make soft transition 43 | " gl_FragColor = diffuse * b;\n" + // final color with soft edge 44 | // " gl_FragColor = vec4(a, a, a, 1.0);\n" + // uncomment to visualize raw Z difference 45 | // " gl_FragColor = vec4(b, b, b, 1.0);\n" + // uncomment to visualize blending coefficient 46 | "}"; 47 | } 48 | 49 | fillUniformsAttributes() { 50 | this.view_proj_matrix = this.getUniform("view_proj_matrix"); 51 | this.rm_Vertex = this.getAttrib("rm_Vertex"); 52 | this.rm_TexCoord0 = this.getAttrib("rm_TexCoord0"); 53 | this.sTexture = this.getUniform("sTexture"); 54 | this.cameraRange = this.getUniform("uCameraRange"); 55 | this.sDepth = this.getUniform("sDepth"); 56 | this.invViewportSize = this.getUniform("uInvViewportSize"); 57 | this.transitionSize = this.getUniform("uTransitionSize"); 58 | this.color = this.getUniform("color"); 59 | } 60 | } 61 | 62 | return SoftDiffuseColoredShader; 63 | }); 64 | -------------------------------------------------------------------------------- /js/app/framework/BaseShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function() { 4 | 5 | class BaseShader { 6 | 7 | /** 8 | * Constructor. Compiles shader. 9 | */ 10 | constructor() { 11 | this.vertexShaderCode = ''; 12 | this.fragmentShaderCode = ''; 13 | this.program = null; 14 | 15 | this.fillCode(); 16 | this.initShader(); 17 | } 18 | 19 | /** 20 | * Used to fill shader code. Put actual shader code to this.vertexShaderCode and this.fragmentShaderCode 21 | */ 22 | fillCode() {} 23 | 24 | getShader(gl, type, code) { 25 | var shader = gl.createShader(type); 26 | 27 | gl.shaderSource(shader, code); 28 | gl.compileShader(shader); 29 | 30 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 31 | console.warn(gl.getShaderInfoLog(shader)); 32 | return null; 33 | } 34 | 35 | return shader; 36 | } 37 | 38 | /** 39 | * Retrieve and save uniforms and attributes for actual shader here 40 | */ 41 | fillUniformsAttributes() {} 42 | 43 | /** 44 | * Get shader unform location 45 | * @param {string} uniform - uniform name 46 | * @return {number} - uniform location 47 | */ 48 | getUniform(uniform) { 49 | return gl.getUniformLocation(this.program, uniform); 50 | } 51 | 52 | /** 53 | * Get shader attribute location 54 | * @param {string} attrib - uniform name 55 | * @return {number} - attribute location 56 | */ 57 | getAttrib(attrib) { 58 | return gl.getAttribLocation(this.program, attrib); 59 | } 60 | 61 | /** 62 | * Initializes shader. No need to call this manually, this is called in constructor. 63 | */ 64 | initShader() { 65 | var shaderProgram, 66 | fragmentShader = this.getShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderCode), 67 | vertexShader = this.getShader(gl, gl.VERTEX_SHADER, this.vertexShaderCode); 68 | 69 | shaderProgram = gl.createProgram(); 70 | gl.attachShader(shaderProgram, vertexShader); 71 | gl.attachShader(shaderProgram, fragmentShader); 72 | gl.linkProgram(shaderProgram); 73 | 74 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 75 | console.warn(this.constructor.name + ': Could not initialise shader'); 76 | } else { 77 | console.log(this.constructor.name + ': Initialised shader'); 78 | } 79 | 80 | gl.useProgram(shaderProgram); 81 | this.program = shaderProgram; 82 | 83 | this.fillUniformsAttributes(); 84 | } 85 | 86 | /** 87 | * Activates shader. 88 | */ 89 | use() { 90 | if (this.program) { 91 | gl.useProgram(this.program); 92 | } 93 | } 94 | 95 | /** 96 | * Deletes shader. 97 | */ 98 | deleteProgram() { 99 | gl.deleteProgram(this.program); 100 | } 101 | }; 102 | 103 | return BaseShader; 104 | }); 105 | -------------------------------------------------------------------------------- /styles/scss/site.scss: -------------------------------------------------------------------------------- 1 | @import "common"; 2 | 3 | body { 4 | background-color: #000; 5 | } 6 | 7 | #row-progress { 8 | color: #fff; 9 | } 10 | 11 | .canvasGL { 12 | display: block; 13 | width: 100vw; 14 | // max-width: 240vh; 15 | height: 100vh; 16 | border: none; 17 | padding: 0; 18 | margin: 0 auto; 19 | } 20 | 21 | .transparent { 22 | opacity: 0 !important; 23 | } 24 | 25 | .promo { 26 | opacity: 1; 27 | position: absolute; 28 | bottom: 5vh; 29 | width: 100%; 30 | text-align: center; 31 | transition: opacity 1000ms cubic-bezier(0.645, 0.045, 0.355, 1); 32 | 33 | img { 34 | width: 220px; 35 | } 36 | 37 | .text { 38 | color: #fff; 39 | text-shadow: #030303 0px 0px 5px; 40 | } 41 | } 42 | 43 | .controls { 44 | background-color: #000; 45 | position: absolute; 46 | top: 0; 47 | bottom: 0; 48 | left: 0; 49 | right: 0; 50 | padding: 20px; 51 | padding-top: 60px; 52 | opacity: 1.0; 53 | 54 | transition: opacity 800ms cubic-bezier(0.645, 0.045, 0.355, 1); 55 | 56 | @media(min-width: $screen-sm-min) { 57 | padding-top: 20px; 58 | padding-left: 50px; 59 | } 60 | 61 | .fs & { 62 | padding: 0; 63 | padding-top: 60px; 64 | 65 | @media(min-width: $screen-sm-min) { 66 | padding-top: 0; 67 | padding-left: 30px; 68 | } 69 | } 70 | 71 | label { 72 | color: #fff; 73 | text-shadow: rgb(3, 3, 3) 0px 0px 8px; 74 | } 75 | } 76 | 77 | .control-icon { 78 | opacity: 0.85; 79 | position: absolute; 80 | 81 | transition: opacity 800ms cubic-bezier(0.645, 0.045, 0.355, 1); 82 | 83 | .material-icons { 84 | color: #fff; 85 | text-shadow: rgb(3, 3, 3) 0px 0px 8px; 86 | } 87 | 88 | &.control-fs { 89 | right: 30px; 90 | top: 30px; 91 | 92 | @media (display-mode: fullscreen) { 93 | display: none !important; 94 | } 95 | 96 | .icon-enter-fs { 97 | display: inline-block; 98 | } 99 | 100 | .icon-exit-fs { 101 | display: none; 102 | } 103 | 104 | .fs & { 105 | .icon-enter-fs { 106 | display: none; 107 | } 108 | 109 | .icon-exit-fs { 110 | display: inline-block; 111 | } 112 | } 113 | } 114 | 115 | &.control-settings { 116 | left: 40px; 117 | top: 40px; 118 | 119 | .icon-show-settings { 120 | display: inline-block; 121 | } 122 | 123 | .icon-hide-settings { 124 | display: none; 125 | } 126 | 127 | .fs & { 128 | left: 20px; 129 | top: 20px; 130 | } 131 | 132 | &.open { 133 | .icon-show-settings { 134 | display: none; 135 | } 136 | 137 | .icon-hide-settings { 138 | display: inline-block; 139 | } 140 | } 141 | } 142 | } 143 | 144 | .material-icons { 145 | &.md-18 { 146 | font-size: 18px; 147 | } 148 | &.md-24 { 149 | font-size: 24px; 150 | } 151 | &.md-36 { 152 | font-size: 36px; 153 | } 154 | &.md-48 { 155 | font-size: 48px; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 3D Buddha WebGL demo 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 34 | 35 | 36 | 37 | 41 |
42 |
43 |
44 |
45 |

Loading...

46 |
47 |
49 | 0% 50 |
51 |
52 |
53 |
54 |
55 |
56 | 59 |
60 |
61 |
62 |
63 | 64 | fullscreen 65 | fullscreen_exit 66 | 67 |
68 |

3D Buddha Live Wallpaper

69 | Get it on Google Play 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /js/app/framework/utils/FullscreenUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview Cross-browser full-screen utilities 3 | * @author Oleksandr Popov 4 | */ 5 | 6 | 'use strict'; 7 | 8 | define(function () { 9 | 10 | function FullScreenUtils() { } 11 | 12 | /** 13 | * Enters fullscreen 14 | */ 15 | FullScreenUtils.enterFullScreen = function () { 16 | if (document.documentElement.requestFullscreen) { 17 | if (!document.fullscreenElement) { 18 | document.documentElement.requestFullscreen({ navigationUI: "hide" }); 19 | } 20 | } 21 | if (document.documentElement.webkitRequestFullScreen) { 22 | if (!document.webkitFullscreenElement) { 23 | document.documentElement.webkitRequestFullscreen(); 24 | } 25 | } 26 | if (document.documentElement.mozRequestFullScreen) { 27 | if (!document.mozFullscreenElement) { 28 | document.documentElement.mozRequestFullScreen(); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Exits fullscreen 35 | */ 36 | FullScreenUtils.exitFullScreen = function () { 37 | if (document.documentElement.requestFullscreen) { 38 | if (document.exitFullscreen) { 39 | document.exitFullscreen(); 40 | } 41 | } 42 | if (document.documentElement.webkitRequestFullscreen) { 43 | if (document.webkitExitFullscreen) { 44 | document.webkitExitFullscreen(); 45 | } 46 | } 47 | if (document.documentElement.mozRequestFullScreen) { 48 | if (document.mozCancelFullScreen) { 49 | document.mozCancelFullScreen(); 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * Adds cross-browser fullscreenchange event 56 | * @param {function} exitHandler - function to be called on fullscreenchange event 57 | */ 58 | FullScreenUtils.addFullScreenListener = function (exitHandler) { 59 | if (document.documentElement.requestFullscreen) { 60 | document.addEventListener('fullscreenchange', exitHandler, false); 61 | } 62 | if (document.documentElement.webkitRequestFullScreen) { 63 | document.addEventListener('webkitfullscreenchange', exitHandler, false); 64 | } 65 | if (document.documentElement.mozRequestFullScreen) { 66 | document.addEventListener('mozfullscreenchange', exitHandler, false); 67 | } 68 | } 69 | 70 | /** 71 | * Checks fullscreen state 72 | * @return {Boolean} - true if fullscreen is active, false if not 73 | */ 74 | FullScreenUtils.isFullScreen = function () { 75 | if (document.documentElement.requestFullscreen) { 76 | return !!document.fullscreenElement; 77 | } 78 | if (document.documentElement.webkitRequestFullscreen) { 79 | return !!document.webkitFullscreenElement; 80 | } 81 | if (document.documentElement.mozRequestFullScreen) { 82 | return !!document.mozFullScreenElement; 83 | } 84 | } 85 | 86 | /** 87 | * Checks for full-screen API availability 88 | * @return {Boolean} - true if fullscrees is supported, false if not 89 | */ 90 | FullScreenUtils.isFullScreenSupported = function () { 91 | return !!document.documentElement.requestFullscreen || !!document.documentElement.webkitRequestFullScreen || !!document.documentElement.mozRequestFullScreen; 92 | } 93 | 94 | return FullScreenUtils; 95 | }); 96 | -------------------------------------------------------------------------------- /js/app/SphericalMapLMShader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['framework/BaseShader'], function(BaseShader) { 4 | 5 | /** 6 | * Sahder to render coins. Uses spherical map for reflection and diffuse, normal and light maps. 7 | */ 8 | class SphericalMapLMShader extends BaseShader { 9 | fillCode() { 10 | this.vertexShaderCode = 'precision highp float;\r\n' + 11 | 'uniform mat4 view_proj_matrix;\r\n' + 12 | 'uniform mat4 view_matrix;\r\n' + 13 | '\r\n' + 14 | 'attribute vec4 rm_Vertex;\r\n' + 15 | 'attribute vec2 rm_TexCoord0;\r\n' + 16 | 'attribute vec2 rm_TexCoord1;\r\n' + 17 | 'attribute vec3 rm_Normal;\r\n' + 18 | '\r\n' + 19 | 'varying vec3 vNormal;\r\n' + 20 | 'varying vec2 vTexCoord0;\r\n' + 21 | 'varying vec2 vTexCoord1;\r\n' + 22 | '\r\n' + 23 | 'void main( void )\r\n' + 24 | '{\r\n' + 25 | ' gl_Position = view_proj_matrix * rm_Vertex;\r\n' + 26 | '\r\n' + 27 | ' vTexCoord0 = vec2(rm_TexCoord0);\r\n' + 28 | ' vTexCoord1 = vec2(rm_TexCoord1);\r\n' + 29 | '\r\n' + 30 | ' vNormal = (view_matrix * vec4(rm_Normal, 0.0)).xyz;\r\n' + 31 | '}'; 32 | 33 | this.fragmentShaderCode = 'precision highp float;\r\n' + 34 | 'uniform sampler2D normalMap;\r\n' + 35 | 'uniform sampler2D sphereMap;\r\n' + 36 | 'uniform sampler2D aoMap;\r\n' + 37 | 'varying vec2 vTexCoord0;\r\n' + 38 | 'varying vec2 vTexCoord1;\r\n' + 39 | 'varying vec3 vNormal;\r\n' + 40 | 'const vec4 SMALL_VALUE = vec4(0.01, 0.01, 0.01, 0.01);\r\n' + 41 | 'const vec4 VEC4_ONE = vec4(1.0, 1.0, 1.0, 1.0);\r\n' + 42 | '\r\n' + 43 | 'void main()\r\n' + 44 | '{\r\n' + 45 | ' vec3 normColor = texture2D(normalMap,vTexCoord0).rgb;\r\n' + 46 | ' vec4 aoColor = texture2D(aoMap,vTexCoord1);\r\n' + 47 | ' vec3 norm = normColor * 2.0 - 1.0;\r\n' + 48 | ' \r\n' + 49 | ' vec3 vNormal2 = normalize(\r\n' + 50 | ' vNormal\r\n' + 51 | ' + norm\r\n' + 52 | ' ) * 0.5\r\n' + 53 | ' ;\r\n' + 54 | '\r\n' + 55 | ' vec4 sphereColor = texture2D(sphereMap,\r\n' + 56 | ' vec2(vNormal2.x, vNormal2.y));\r\n' + 57 | //' sphereColor = pow(sphereColor, VEC4_ONE / (aoColor + SMALL_VALUE));\r\n' + 58 | ' sphereColor = pow(sphereColor, VEC4_ONE / (vec4(aoColor.g, aoColor.g, aoColor.g, 1.0) + SMALL_VALUE));\r\n' + 59 | ' sphereColor *= vec4(aoColor.b, aoColor.b, aoColor.b, 1.0);\r\n' + 60 | ' \r\n' + 61 | ' gl_FragColor = sphereColor;// * normColor.b;\r\n' + 62 | '}'; 63 | } 64 | 65 | fillUniformsAttributes() { 66 | this.view_proj_matrix = this.getUniform('view_proj_matrix'); 67 | this.view_matrix = this.getUniform('view_matrix'); 68 | this.rm_Vertex = this.getAttrib('rm_Vertex'); 69 | this.rm_TexCoord0 = this.getAttrib('rm_TexCoord0'); 70 | this.rm_TexCoord1 = this.getAttrib('rm_TexCoord1'); 71 | this.rm_Normal = this.getAttrib('rm_Normal'); 72 | this.normalMap = this.getUniform('normalMap'); 73 | this.sphereMap = this.getUniform('sphereMap'); 74 | this.aoMap = this.getUniform('aoMap'); 75 | } 76 | } 77 | 78 | return SphericalMapLMShader; 79 | }); 80 | -------------------------------------------------------------------------------- /js/app/framework/BaseRenderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | './utils/MatrixUtils' 5 | ], 6 | function(MatrixUtils) { 7 | 8 | /** 9 | * Constructor 10 | */ 11 | function BaseRenderer() { 12 | this.mMMatrix = MatrixUtils.mat4.create(); 13 | this.mVMatrix = MatrixUtils.mat4.create(); 14 | this.mMVPMatrix = MatrixUtils.mat4.create(); 15 | this.mProjMatrix = MatrixUtils.mat4.create(); 16 | 17 | this.matOrtho = MatrixUtils.mat4.create(); 18 | MatrixUtils.mat4.ortho(this.matOrtho, -1, 1, -1, 1, 2.0, 250); 19 | 20 | this.boundTick = this.tick.bind(this); 21 | } 22 | 23 | /** 24 | * Logs last GL error to console 25 | */ 26 | BaseRenderer.prototype.logGLError = function() { 27 | var err = gl.getError(); 28 | if (err !== gl.NO_ERROR) { 29 | console.warn('WebGL error #' + err); 30 | } 31 | } 32 | 33 | /** 34 | * Binds 2D texture. 35 | * @param {number} textureUnit - texture unit to a 36 | * @param {number} textureID - texture to be used 37 | * @param {number} uniformID - shader's uniform ID 38 | */ 39 | BaseRenderer.prototype.setTexture2D = function(textureUnit, textureID, uniformID) { 40 | gl.activeTexture(gl.TEXTURE0 + textureUnit); 41 | gl.bindTexture(gl.TEXTURE_2D, textureID); 42 | gl.uniform1i(uniformID, textureUnit); 43 | } 44 | 45 | /** 46 | * Binds cubemap texture. 47 | * @param {number} textureUnit - texture unit to a 48 | * @param {number} textureID - texture to be used 49 | * @param {number} uniformID - shader's uniform ID 50 | */ 51 | BaseRenderer.prototype.setTextureCubemap = function(textureUnit, textureID, uniformID) { 52 | gl.ActiveTexture(gl.TEXTURE0 + textureUnit); 53 | gl.BindTexture(gl.TEXTURE_CUBE_MAP, textureID); 54 | gl.Uniform1i(uniformID, textureUnit); 55 | } 56 | 57 | /** 58 | * Calculates FOV for matrix. 59 | * @param {Mat4} matrix - output matrix 60 | * @param {number} fovY - vertical FOV in degrees 61 | * @param {number} aspect - aspect ratio of viewport 62 | * @param {number} zNear - near clipping plane distance 63 | * @param {number} zFar - far clipping plane distance 64 | */ 65 | BaseRenderer.prototype.setFOV = function(matrix, fovY, aspect, zNear, zFar) { 66 | var fW, fH; 67 | 68 | fH = Math.tan(fovY / 360.0 * 3.1415926) * zNear; 69 | fW = fH * aspect; 70 | MatrixUtils.mat4.frustum(matrix, -fW, fW, -fH, fH, zNear, zFar); 71 | } 72 | 73 | /** 74 | * Calculates MVP matrix. Saved in this.mMVPMatrix 75 | */ 76 | BaseRenderer.prototype.calculateMVPMatrix = function(tx, ty, tz, rx, ry, rz, sx, sy, sz) { 77 | MatrixUtils.mat4.identity(this.mMMatrix); 78 | MatrixUtils.mat4.rotate(this.mMMatrix, this.mMMatrix, 0, [1, 0, 0]); 79 | MatrixUtils.mat4.translate(this.mMMatrix, this.mMMatrix, [tx, ty, tz]); 80 | MatrixUtils.mat4.scale(this.mMMatrix, this.mMMatrix, [sx, sy, sz]); 81 | MatrixUtils.mat4.rotateX(this.mMMatrix, this.mMMatrix, rx); 82 | MatrixUtils.mat4.rotateY(this.mMMatrix, this.mMMatrix, ry); 83 | MatrixUtils.mat4.rotateZ(this.mMMatrix, this.mMMatrix, rz); 84 | MatrixUtils.mat4.multiply(this.mMVPMatrix, this.mVMatrix, this.mMMatrix); 85 | MatrixUtils.mat4.multiply(this.mMVPMatrix, this.mProjMatrix, this.mMVPMatrix); 86 | } 87 | 88 | /** 89 | * Called before WebGL initialization 90 | */ 91 | BaseRenderer.prototype.onBeforeInit = function() {} 92 | 93 | /** 94 | * Called right after WebGL initialization 95 | */ 96 | BaseRenderer.prototype.onAfterInit = function() {} 97 | 98 | /** 99 | * Called on WebGL initialization error 100 | */ 101 | BaseRenderer.prototype.onInitError = function() {} 102 | 103 | /** 104 | * Shaders initialization code goes here 105 | */ 106 | BaseRenderer.prototype.initShaders = function() {} 107 | 108 | /** 109 | * Load WebGL data here 110 | */ 111 | BaseRenderer.prototype.loadData = function() {} 112 | 113 | /** 114 | * Perform each frame's draw calls here 115 | */ 116 | BaseRenderer.prototype.drawScene = function() { 117 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 118 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 119 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 120 | } 121 | 122 | /** 123 | * Update timers and aminate stuff here 124 | */ 125 | BaseRenderer.prototype.animate = function() {} 126 | 127 | /** 128 | * Called on each frame 129 | */ 130 | BaseRenderer.prototype.tick = function() { 131 | requestAnimationFrame(this.boundTick); 132 | this.resizeCanvas(); 133 | this.drawScene(); 134 | this.animate(); 135 | } 136 | 137 | /** 138 | * Initializes WebGL context 139 | * @param {HTMLElement} canvas - canvas to initialize WebGL 140 | */ 141 | BaseRenderer.prototype.initGL = function(canvas) { 142 | var gl = null; 143 | 144 | try { 145 | gl = canvas.getContext('webgl2', { alpha: false }) || canvas.getContext('experimental-webgl', { alpha: false }); 146 | gl.viewportWidth = canvas.width; 147 | gl.viewportHeight = canvas.height; 148 | this.isETC1Supported = !!gl.getExtension('WEBGL_compressed_texture_etc1'); 149 | } catch (e) { } 150 | if (!gl) { 151 | console.warn('Could not initialise WebGL'); 152 | } 153 | 154 | return gl; 155 | }; 156 | 157 | /** 158 | * Initializes WebGL 2 context 159 | * @param {HTMLElement} canvas - canvas to initialize WebGL 160 | */ 161 | BaseRenderer.prototype.initGL2 = function (canvas) { 162 | var gl = null; 163 | 164 | try { 165 | gl = canvas.getContext('webgl2', { alpha: false }); 166 | } catch (e) { } 167 | this.isWebGL2 = !!gl; 168 | 169 | if (!this.isWebGL2) { 170 | console.warn('Could not initialise WebGL 2, falling back to WebGL 1'); 171 | return this.initGL(canvas); 172 | } else { 173 | return gl; 174 | } 175 | }; 176 | 177 | /** 178 | * Initializes WebGL and calls all callbacks. Assigns current WebGL context to global window.gl 179 | * @param {String} canvasID - ID of canvas element to initialize WebGL 180 | */ 181 | BaseRenderer.prototype.init = function(canvasID, requestWebGL2) { 182 | this.onBeforeInit(); 183 | 184 | this.canvas = document.getElementById(canvasID); 185 | window.gl = !!requestWebGL2 ? this.initGL2(this.canvas) : this.initGL(this.canvas); 186 | 187 | if (window.gl) { 188 | this.resizeCanvas(); 189 | this.onAfterInit(); 190 | this.initShaders(); 191 | this.loadData(); 192 | this.boundTick(); 193 | } else { 194 | this.onInitError(); 195 | } 196 | } 197 | 198 | /** 199 | * Adjusts viewport according to resizing of canvas 200 | */ 201 | BaseRenderer.prototype.resizeCanvas = function() { 202 | var cssToRealPixels = window.devicePixelRatio || 1, 203 | displayWidth = Math.floor(this.canvas.clientWidth * cssToRealPixels), 204 | displayHeight = Math.floor(this.canvas.clientHeight * cssToRealPixels); 205 | 206 | if (this.canvas.width != displayWidth || this.canvas.height != displayHeight) { 207 | this.canvas.width = displayWidth; 208 | this.canvas.height = displayHeight; 209 | } 210 | } 211 | 212 | BaseRenderer.prototype.checkGlError = function(op) { 213 | let error; 214 | 215 | while ((error = gl.getError()) !== gl.NO_ERROR) { 216 | console.error(`${op}: glError ${error}`); 217 | } 218 | } 219 | 220 | return BaseRenderer; 221 | }); 222 | -------------------------------------------------------------------------------- /js/lib/require.js: -------------------------------------------------------------------------------- 1 | /** vim: et:ts=4:sw=4:sts=4 2 | * @license RequireJS 2.3.1 Copyright jQuery Foundation and other contributors. 3 | * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE 4 | */ 5 | var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h,m,g=i&&i.split("/"),v=y.map,x=v&&v["*"];if(e&&(e=e.split("/"),d=e.length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[d])&&(e[d]=e[d].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&g&&(m=g.slice(0,g.length-1),e=m.concat(e)),t(e),e=e.join("/")),r&&v&&(g||x)){a=e.split("/");e:for(s=a.length;s>0;s-=1){if(c=a.slice(0,s).join("/"),g)for(u=g.length;u>0;u-=1)if(o=getOwn(v,g.slice(0,u).join("/")),o&&(o=getOwn(o,c))){p=o,f=s;break e}!l&&x&&getOwn(x,c)&&(l=getOwn(x,c),h=s)}!p&&l&&(p=l,f=h),p&&(a.splice(0,f,p),e=a.join("/"))}return n=getOwn(y.pkgs,e),n?n:e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(A+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):e.indexOf("!")===-1?i(e,p,n):e:(h=i(e,p,n),c=o(h),d=c[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(T+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e),n.error&&"error"===t?i(n.error):n.on(t,i)):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(e=O.shift(),null===e[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},A=1,T=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;return this.shim?void q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()})):e.prefix?this.callPlugin():this.load()}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&(t=this.module,t?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),d=getOwn(S,o.id),void(d&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):(n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})}),n.error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(i.indexOf("?")===-1?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(y.paths[i]=e.location),y.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),f=s(a(null,t)),f.skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return n!==-1&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){var t=getOwn(S,e.id);t&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(i=O.shift(),null===i[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,n=e.split("/"),o=n.length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js"),s=("/"===s.charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.1",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if("undefined"==typeof define){if("undefined"!=typeof requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}"undefined"==typeof require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),n=getOwn(contexts,a),n||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick="undefined"!=typeof setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],baseElement=document.getElementsByTagName("base")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return r=req.createNode(n,t,i),r.setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||mainScript.indexOf("!")!==-1||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript(),r&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")])),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,setTimeout); -------------------------------------------------------------------------------- /js/app/BuddhaRenderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'framework/BaseRenderer', 5 | 'DiffuseShader', 6 | 'LightShaftShader', 7 | 'SphericalMapLMShader', 8 | 'LMTableShader', 9 | 'PointSpriteScaledColoredShader', 10 | 'framework/utils/MatrixUtils', 11 | 'framework/FullModel', 12 | 'framework/UncompressedTextureLoader', 13 | 'framework/CompressedTextureLoader', 14 | 'framework/FrameBuffer', 15 | 'framework/TextureUtils', 16 | 'SoftDiffuseColoredShader' 17 | ], 18 | function ( 19 | BaseRenderer, 20 | DiffuseShader, 21 | LightShaftShader, 22 | SphericalMapLMShader, 23 | LMTableShader, 24 | PointSpriteScaledColoredShader, 25 | MatrixUtils, 26 | FullModel, 27 | UncompressedTextureLoader, 28 | CompressedTextureLoader, 29 | FrameBuffer, 30 | TextureUtils, 31 | SoftDiffuseColoredShader 32 | ) { 33 | class BuddhaRenderer extends BaseRenderer { 34 | constructor() { 35 | super(); 36 | 37 | this.loadedItemsCount = 0; // counter of loaded OpenGL buffers+textures 38 | this.loaded = false; // won't draw until this is true 39 | 40 | this.angleYaw = 0; // camera rotation angle 41 | this.lastTime = 0; // used for animating camera 42 | 43 | this.ITEMS_TO_LOAD = 15; // total number of OpenGL buffers+textures to load 44 | this.FLOAT_SIZE_BYTES = 4; // float size, used to calculate stride sizes 45 | this.TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * this.FLOAT_SIZE_BYTES; 46 | this.TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 47 | this.TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 48 | this.FOV_LANDSCAPE = 25.0; // FOV for landscape 49 | this.FOV_PORTRAIT = 40.0; // FOV for portrait 50 | this.YAW_COEFF_NORMAL = 80.0; // camera rotation speed 51 | 52 | this.SCENE_SIZE = { x: 350, y: 350, z: 200 }; 53 | this.DUST_SPEED = 350000 * 60; 54 | this.DUST_FLICKER_SPEED = 10000; 55 | this.DUST_COUNT = 8; 56 | this.DUST_OFFSET_Z = 200; 57 | this.DUST_COLOR = { r: 20 / 256, g: 18 / 256, b: 15 / 256, a: 1 }; 58 | this.DUST_SPRITE_SIZE = 0.015; 59 | this.DUST_SCALE = 0.75; 60 | 61 | this.timerDustRotation = 0; 62 | this.timerDustFlicker = 0; 63 | this.timerSmokeRotation = 0; 64 | this.dustSpriteSize = 0; 65 | 66 | this.Z_NEAR = 100; 67 | this.Z_FAR = 2000; 68 | this.SMOKE_SOFTNESS = 0.08; 69 | this.m_smokeCoordinates = [ 70 | [-219.188324, -77.765877, -32.000130], 71 | [-164.917282, -155.531754, -3.268164], 72 | [-74.093452, -109.379417, -100.979820], 73 | [-54.972435, -218.313690, -125.006828], 74 | [68.940094, -109.379417, -57.751869], 75 | [54.972427, -218.313690, -46.009144], 76 | [209.912292, -77.765877, -144.106689], 77 | [164.917282, -155.531754, 37.452091], 78 | [-237.278671, -0.000009, 63.925690], 79 | [224.910599, -0.000009, 69.076576], 80 | [-219.188324, 77.765869, -143.545853], 81 | [-74.093452, 107.153793, -18.438652], 82 | [68.940109, 107.153816, 9.984768], 83 | [209.912292, 77.765869, -87.094765], 84 | [-164.917297, 155.531754, -42.647652], 85 | [-54.972443, 214.752686, 36.233562], 86 | [54.972427, 214.752686, 29.778637], 87 | [164.917282, 155.531754, -194.976959], 88 | ]; 89 | this.SMOKE_COLOR = { 90 | r: 75 / 255, 91 | g: 76 / 255, 92 | b: 92 / 255 93 | }; 94 | this.SMOKE_SPEED = 90333; 95 | 96 | this.mOrthoMatrix = new Array(16); 97 | MatrixUtils.mat4.ortho(this.mOrthoMatrix, -1, 1, -1, 1, 2.0, 250); 98 | } 99 | 100 | initVignette() { 101 | this.mQuadTriangles = new Float32Array([ 102 | // X, Y, Z, U, V 103 | -1.0, -1.0, -5.0, 0.0, 0.0, // 0. left-bottom 104 | 1.0, -1.0, -5.0, 1.0, 0.0, // 1. right-bottom 105 | -1.0, 1.0, -5.0, 0.0, 1.0, // 2. left-top 106 | 1.0, 1.0, -5.0, 1.0, 1.0, // 3. right-top 107 | ]); 108 | this.mTriangleVerticesVignette = gl.createBuffer(); 109 | gl.bindBuffer(gl.ARRAY_BUFFER, this.mTriangleVerticesVignette); 110 | gl.bufferData(gl.ARRAY_BUFFER, this.mQuadTriangles, gl.STATIC_DRAW); 111 | } 112 | 113 | resizeCanvas() { 114 | super.resizeCanvas(); 115 | 116 | this.dustSpriteSize = Math.min(this.canvas.height, this.canvas.width) * this.DUST_SPRITE_SIZE; 117 | } 118 | 119 | /** 120 | * Resets loaded state for renderer 121 | */ 122 | resetLoaded() { 123 | this.loaded = false; 124 | this.loadedItemsCount = 0; 125 | } 126 | 127 | onBeforeInit() { 128 | super.onBeforeInit(); 129 | 130 | document.getElementById('canvasGL').classList.remove('hidden'); 131 | } 132 | 133 | onInitError() { 134 | super.onInitError(); 135 | 136 | $(canvas).hide(); 137 | document.getElementById('alertError').classList.remove('hidden'); 138 | } 139 | 140 | initShaders() { 141 | this.shaderSphericalMapLM = new SphericalMapLMShader(); 142 | this.shaderLMTable = new LMTableShader(); 143 | this.shaderDiffuse = new DiffuseShader(); 144 | this.shaderShaft = new LightShaftShader(); 145 | this.shaderPointSpriteScaledColored = new PointSpriteScaledColoredShader(); 146 | this.shaderSoftDiffuseColored = new SoftDiffuseColoredShader(); 147 | } 148 | 149 | /** 150 | * Callback for all loading function. Updates loading progress and allows rendering after loading all stuff 151 | */ 152 | updateLoadedObjectsCount() { 153 | var percent, 154 | progressElement = document.getElementById('progressLoading'); 155 | 156 | this.loadedItemsCount++; // increase loaded objects counter 157 | 158 | percent = Math.floor(this.loadedItemsCount * 100 / this.ITEMS_TO_LOAD) + '%'; 159 | progressElement.innerHTML = percent; 160 | progressElement.style.width = percent; 161 | 162 | if (this.loadedItemsCount >= this.ITEMS_TO_LOAD) { 163 | this.loaded = true; // allow rendering 164 | console.log('Loaded all assets'); 165 | document.getElementById('divControls').classList.add('transparent'); 166 | setTimeout(() => document.getElementById('divControls').classList.add('hidden'), 1000); 167 | setTimeout(() => document.querySelector('.control-icon').classList.remove('transparent'), 1200); 168 | setTimeout(() => document.querySelector('.promo').classList.remove('transparent'), 1800); 169 | } 170 | } 171 | 172 | /** 173 | * loads all WebGL buffers and textures. Uses updateLoadedObjectsCount() callback to indicate that data is loaded to GPU 174 | */ 175 | loadData() { 176 | this.initVignette(); 177 | 178 | var boundUpdateCallback = this.updateLoadedObjectsCount.bind(this); 179 | 180 | this.textureBuddhaNormalMap = UncompressedTextureLoader.load('data/textures/buddha-normals.png', boundUpdateCallback); 181 | this.textureSphericalMap = UncompressedTextureLoader.load('data/textures/sphere_gold3.png', boundUpdateCallback); 182 | this.textureBuddhaLightMap = UncompressedTextureLoader.load('data/textures/buddha_lm.png', boundUpdateCallback); 183 | this.textureTable = this.loadETC1WithFallback('data/textures/table/marble'); 184 | this.textureTableLM = UncompressedTextureLoader.load('data/textures/table/table_lm.png', boundUpdateCallback); 185 | this.textureSky = UncompressedTextureLoader.load('data/textures/sky/sky1.png', boundUpdateCallback); 186 | this.textureShaft = UncompressedTextureLoader.load('data/textures/shafts.png', boundUpdateCallback); 187 | this.textureSmoke = UncompressedTextureLoader.load('data/textures/smoke.png', boundUpdateCallback); 188 | 189 | this.modelTable = new FullModel(); 190 | this.modelTable.load('data/models/table', boundUpdateCallback); 191 | this.modelBuddha = new FullModel(); 192 | this.modelBuddha.load('data/models/buddha', boundUpdateCallback); 193 | 194 | this.fmSky = new FullModel(); 195 | this.fmSky.load('data/models/sky', boundUpdateCallback); 196 | 197 | this.fmShaft = new FullModel(); 198 | this.fmShaft.load('data/models/shafts', boundUpdateCallback); 199 | 200 | this.fmDustPatch = new FullModel(); 201 | this.fmDustPatch.load('data/models/particles_20', boundUpdateCallback); 202 | this.textureDust = UncompressedTextureLoader.load('data/textures/dust.png', boundUpdateCallback); 203 | 204 | this.fmQuad = new FullModel(); 205 | this.fmQuad.load('data/models/smoke100', boundUpdateCallback); 206 | 207 | this.fillParticles(); 208 | 209 | this.initOffscreen(); 210 | } 211 | 212 | fillParticles() { 213 | this.dustCoordinates = []; 214 | 215 | for (let i = 0; i < this.DUST_COUNT; i++) { 216 | this.dustCoordinates[i] = { 217 | x: (0.5 - Math.random()) * this.SCENE_SIZE.x, 218 | y: (0.5 - Math.random()) * this.SCENE_SIZE.y, 219 | z: (0.5 - Math.random()) * this.SCENE_SIZE.z + this.DUST_OFFSET_Z 220 | }; 221 | } 222 | } 223 | 224 | initOffscreen() { 225 | this.textureOffscreenColorID = TextureUtils.createNPOTTexture(this.canvas.width, this.canvas.height, false); 226 | this.checkGlError("color"); 227 | this.textureOffscreenDepthID = TextureUtils.createDepthTexture(this.canvas.width, this.canvas.height); 228 | this.checkGlError("depth"); 229 | this.fboOffscreen = new FrameBuffer(); 230 | this.fboOffscreen.textureHandle = this.textureOffscreenColorID; 231 | this.fboOffscreen.depthTextureHandle = this.textureOffscreenDepthID; 232 | this.fboOffscreen.width = this.canvas.width; 233 | this.fboOffscreen.height = this.canvas.height; 234 | this.fboOffscreen.createGLData(); 235 | this.checkGlError("offscreen FBO"); 236 | } 237 | 238 | /** 239 | * Loads either ETC1 from PKM or falls back to loading PNG 240 | * @param {string} url - URL to texture without extension 241 | */ 242 | loadETC1WithFallback(url) { 243 | var boundUpdateCallback = this.updateLoadedObjectsCount.bind(this); 244 | 245 | if (this.isETC1Supported) { 246 | return CompressedTextureLoader.loadETC1(url + '.pkm', boundUpdateCallback); 247 | } else { 248 | return UncompressedTextureLoader.load(url + '.png', boundUpdateCallback); 249 | } 250 | } 251 | 252 | /** 253 | * Calculates camera matrix 254 | * @param {number} a - position in [0...1] range 255 | */ 256 | positionCamera(a) { 257 | var x, y, z, 258 | sina, cosa; 259 | 260 | x = 0; 261 | y = 0; 262 | z = (Math.sin(a * 6.2831852) * 200.0) + 250.0; 263 | sina = Math.sin(this.angleYaw / 360.0 * 6.2831852); 264 | cosa = Math.cos(this.angleYaw / 360.0 * 6.2831852); 265 | x = sina * 460.0 * 1.5; 266 | y = cosa * 460.0 * 1.5; 267 | const lookAtZ = 75 + 20 * sina; 268 | 269 | MatrixUtils.mat4.identity(this.mVMatrix); 270 | MatrixUtils.mat4.lookAt(this.mVMatrix, [x, y, z], [0, 0, lookAtZ], [0, 0, 1]); 271 | } 272 | 273 | /** 274 | * Calculates projection matrix 275 | */ 276 | setCameraFOV(multiplier) { 277 | var ratio; 278 | 279 | if (gl.canvas.height > 0) { 280 | ratio = gl.canvas.width / gl.canvas.height; 281 | } else { 282 | ratio = 1.0; 283 | } 284 | 285 | if (gl.canvas.width >= gl.canvas.height) { 286 | this.setFOV(this.mProjMatrix, this.FOV_LANDSCAPE * multiplier, ratio, this.Z_NEAR, this.Z_FAR); 287 | } else { 288 | this.setFOV(this.mProjMatrix, this.FOV_PORTRAIT * multiplier, ratio, this.Z_NEAR, this.Z_FAR); 289 | } 290 | } 291 | 292 | /** 293 | * Issues actual draw calls 294 | */ 295 | drawScene() { 296 | if (!this.loaded) { 297 | return; 298 | } 299 | 300 | this.positionCamera(0.0); 301 | this.setCameraFOV(1.0); 302 | 303 | gl.clearColor(0.0, 1.0, 0.0, 1.0); 304 | 305 | gl.colorMask(false, false, false, false); 306 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboOffscreen.framebufferHandle); 307 | gl.viewport(0, 0, this.fboOffscreen.width, this.fboOffscreen.height); 308 | gl.depthMask(true); 309 | gl.enable(gl.DEPTH_TEST); 310 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 311 | this.drawDepthObjects(); 312 | 313 | gl.enable(gl.DEPTH_TEST); 314 | gl.enable(gl.CULL_FACE); 315 | gl.cullFace(gl.BACK); 316 | 317 | gl.colorMask(true, true, true, true); 318 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); // This differs from OpenGL ES 319 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 320 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 321 | 322 | this.drawSceneObjects(); 323 | // this.drawTestDepth(); 324 | } 325 | 326 | drawSceneObjects() { 327 | this.drawBuddha(); 328 | this.drawSky(); 329 | this.drawTable(1.0); 330 | this.drawShaft(); 331 | this.drawDust(); 332 | this.drawSmoke(); 333 | } 334 | 335 | drawDepthObjects() { 336 | gl.depthMask(true); 337 | gl.disable(gl.BLEND); 338 | this.drawBuddha(); 339 | this.drawTable(2.0); 340 | } 341 | 342 | drawTestDepth() { 343 | gl.enable(gl.CULL_FACE); 344 | gl.cullFace(gl.BACK); 345 | gl.disable(gl.BLEND); 346 | 347 | this.shaderDiffuse.use(); 348 | 349 | this.setTexture2D(0, this.textureOffscreenDepthID, this.shaderDiffuse.sTexture); 350 | this.drawVignette(this.shaderDiffuse); 351 | } 352 | 353 | drawSmoke() { 354 | gl.enable(gl.BLEND); 355 | gl.blendFunc(gl.ONE, gl.ONE); 356 | gl.depthMask(false); 357 | 358 | const cosa = Math.cos(this.timerSmokeRotation * Math.PI * 2); 359 | const sina = Math.sin(this.timerSmokeRotation * Math.PI * 2); 360 | 361 | this.shaderSoftDiffuseColored.use(); 362 | this.initDepthReadShader(this.shaderSoftDiffuseColored); 363 | this.setTexture2D(0, this.textureSmoke, this.shaderSoftDiffuseColored.sTexture); 364 | gl.uniform4f( 365 | this.shaderSoftDiffuseColored.color, 366 | this.SMOKE_COLOR.r, this.SMOKE_COLOR.g, this.SMOKE_COLOR.b, 1 367 | ); 368 | 369 | for (let i = 0; i < this.m_smokeCoordinates.length; i++) { 370 | const x = this.m_smokeCoordinates[i][0] * 1.5; 371 | const y = this.m_smokeCoordinates[i][1] * 1.5; 372 | const z = this.m_smokeCoordinates[i][2] * 0.2 + 20; 373 | const rotation = i * 35 + this.timerDustRotation * 3 * (i % 2 === 0 ? 360 : -360); 374 | 375 | this.drawDiffuseVBOFacingCamera( 376 | this.shaderSoftDiffuseColored, 377 | this.fmQuad, 378 | (x * cosa - y * sina), 379 | (x * sina + y * cosa), 380 | z, 381 | 2.3, 2.3, 2.3, 382 | rotation 383 | ); 384 | } 385 | 386 | gl.disable(gl.BLEND); 387 | gl.depthMask(true); 388 | } 389 | 390 | initDepthReadShader(shader) { 391 | gl.uniform2f(shader.cameraRange, this.Z_NEAR, this.Z_FAR); // near and far clipping planes 392 | gl.uniform2f(shader.invViewportSize, 1.0 / this.canvas.width, 1.0 / this.canvas.height); // inverted screen size 393 | gl.uniform1f(shader.transitionSize, this.SMOKE_SOFTNESS); 394 | this.setTexture2D(2, this.textureOffscreenDepthID, shader.sDepth); 395 | } 396 | 397 | drawDiffuseVBOFacingCamera(shader, model, tx, ty, tz, sx, sy, sz, rotation) { 398 | model.bindBuffers(); 399 | 400 | gl.enableVertexAttribArray(shader.rm_Vertex); 401 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 402 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.FLOAT, false, 4 * (3 + 2), 0); 403 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.FLOAT, false, 4 * (3 + 2), 4 * 3); 404 | 405 | this.calculateMVPMatrixForSprite(tx, ty, tz, sx, sy, sz, rotation); 406 | 407 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mMVPMatrix); 408 | gl.drawElements(gl.TRIANGLES, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 409 | this.checkGlError("glDrawElements"); 410 | } 411 | 412 | calculateMVPMatrixForSprite(tx, ty, tz, sx, sy, sz, rotation) { 413 | MatrixUtils.mat4.identity(this.mMMatrix); 414 | MatrixUtils.mat4.translate(this.mMMatrix, this.mMMatrix, [tx, ty, tz]); 415 | MatrixUtils.mat4.scale(this.mMMatrix, this.mMMatrix, [sx, sy, sz]); 416 | MatrixUtils.mat4.multiply(this.mMVPMatrix, this.mVMatrix, this.mMMatrix); 417 | this.resetMatrixRotations(this.mMVPMatrix); 418 | MatrixUtils.mat4.rotateZ(this.mMVPMatrix, this.mMVPMatrix, rotation); 419 | MatrixUtils.mat4.multiply(this.mMVPMatrix, this.mProjMatrix, this.mMVPMatrix); 420 | } 421 | 422 | resetMatrixRotations(matrix) { 423 | const d = Math.sqrt(matrix[0] * matrix[0] + matrix[1] * matrix[1] + matrix[2] * matrix[2]); 424 | matrix[0] = d; 425 | matrix[4] = 0; 426 | matrix[8] = 0; 427 | 428 | matrix[1] = 0; 429 | matrix[5] = d; 430 | matrix[9] = 0; 431 | 432 | matrix[2] = 0; 433 | matrix[6] = 0; 434 | matrix[10] = d; 435 | 436 | matrix[3] = 0; 437 | matrix[7] = 0; 438 | matrix[11] = 0; 439 | 440 | matrix[15] = 1; 441 | } 442 | 443 | drawDust() { 444 | gl.enable(gl.BLEND); 445 | gl.blendFunc(gl.ONE, gl.ONE); 446 | gl.depthMask(false); 447 | 448 | const cosa = 0.5 + Math.cos(this.timerDustFlicker * Math.PI * 2) * 0.5; 449 | const sina = 0.5 + Math.sin(this.timerDustFlicker * Math.PI * 2) * 0.5; 450 | 451 | this.shaderPointSpriteScaledColored.use(); 452 | this.setTexture2D(0, this.textureDust, this.shaderPointSpriteScaledColored.tex0); 453 | gl.uniform1f(this.shaderPointSpriteScaledColored.uThickness, this.dustSpriteSize); 454 | gl.uniform4f(this.shaderPointSpriteScaledColored.color, this.DUST_COLOR.r * cosa, this.DUST_COLOR.g * cosa, this.DUST_COLOR.b * cosa, this.DUST_COLOR.a); 455 | 456 | const a = this.timerDustRotation * 360; 457 | const b = -this.timerDustRotation * 360; 458 | 459 | for (let i = 0; i < this.dustCoordinates.length; i++) { 460 | if (i < this.dustCoordinates.length / 2) { 461 | this.drawPointSpritesVBOTranslatedRotatedScaled(this.shaderPointSpriteScaledColored, this.fmDustPatch, 462 | this.dustCoordinates[i].x, this.dustCoordinates[i].y, this.dustCoordinates[i].z, 463 | a, b, a, 464 | this.DUST_SCALE, this.DUST_SCALE, this.DUST_SCALE); 465 | } else { 466 | this.drawPointSpritesVBOTranslatedRotatedScaled(this.shaderPointSpriteScaledColored, this.fmDustPatch, 467 | this.dustCoordinates[i].x, this.dustCoordinates[i].y, this.dustCoordinates[i].z, 468 | b, a, b, 469 | this.DUST_SCALE, this.DUST_SCALE, this.DUST_SCALE); 470 | } 471 | if (i == this.dustCoordinates.length / 2) { 472 | gl.uniform4f(this.shaderPointSpriteScaledColored.color, this.DUST_COLOR.r * sina, this.DUST_COLOR.g * sina, this.DUST_COLOR.b * sina, this.DUST_COLOR.a); 473 | } 474 | } 475 | 476 | gl.disable(gl.BLEND); 477 | gl.depthMask(true); 478 | } 479 | 480 | drawSky() { 481 | this.shaderDiffuse.use(); 482 | this.setTexture2D(0, this.textureSky, this.shaderDiffuse.sTexture); 483 | this.drawDiffuseVBOTranslatedRotatedScaled(this.shaderDiffuse, this.fmSky, 0, 0, 0, 0, 0, 0, 7, 7, 3.5); 484 | } 485 | 486 | drawTable(scale) { 487 | // gl.depthMask(false); 488 | gl.enable(gl.BLEND); 489 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 490 | 491 | this.shaderLMTable.use(); 492 | 493 | this.setTexture2D(0, this.textureTable, this.shaderLMTable.sTexture); 494 | this.setTexture2D(1, this.textureTableLM, this.shaderLMTable.sLM); 495 | gl.uniform1f(this.shaderLMTable.diffuseScale, 5.0); 496 | this.drawLMVBOTranslatedRotatedScaled(this.shaderLMTable, this.modelTable, 0, 0, 0, 0, 0, 0, scale, scale, 1); 497 | 498 | // gl.depthMask(true); 499 | gl.disable(gl.BLEND); 500 | } 501 | 502 | drawBuddha() { 503 | this.shaderSphericalMapLM.use(); 504 | 505 | this.setTexture2D(0, this.textureBuddhaNormalMap, this.shaderSphericalMapLM.normalMap); 506 | this.setTexture2D(1, this.textureSphericalMap, this.shaderSphericalMapLM.sphereMap); 507 | this.setTexture2D(2, this.textureBuddhaLightMap, this.shaderSphericalMapLM.aoMap); 508 | this.drawSphericalMapLmVBOTranslatedRotatedScaled(this.shaderSphericalMapLM, this.modelBuddha, 0, 0, 0, 0, 0, 0, 1, 1, 1); 509 | } 510 | 511 | drawShaft() { 512 | gl.depthMask(false); 513 | gl.enable(gl.BLEND); 514 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 515 | 516 | this.shaderShaft.use(); 517 | 518 | this.setTexture2D(0, this.textureShaft, this.shaderShaft.diffuseMap); 519 | this.drawShaftVBOTranslatedRotatedScaled(this.shaderShaft, this.fmShaft, 0, 0, 0, 0, 0, 0, 1, 1, 1); 520 | 521 | gl.depthMask(true); 522 | gl.disable(gl.BLEND); 523 | } 524 | 525 | drawVignette(shader) { 526 | gl.bindBuffer(gl.ARRAY_BUFFER, this.mTriangleVerticesVignette); 527 | 528 | gl.enableVertexAttribArray(shader.rm_Vertex); 529 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.FLOAT, false, this.TRIANGLE_VERTICES_DATA_STRIDE_BYTES, 0); 530 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 531 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.FLOAT, false, this.TRIANGLE_VERTICES_DATA_STRIDE_BYTES, 4 * 3); 532 | 533 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mOrthoMatrix); 534 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 535 | } 536 | 537 | drawPointSpritesVBOTranslatedRotatedScaled(shader, model, tx, ty, tz, rx, ry, rz, sx, sy, sz) { 538 | model.bindBuffers(); 539 | 540 | gl.enableVertexAttribArray(shader.aPosition); 541 | gl.vertexAttribPointer(shader.aPosition, 3, gl.FLOAT, false, 4 * (3 + 2), 0); 542 | 543 | this.calculateMVPMatrix(tx, ty, tz, rx, ry, rz, sx, sy, sz); 544 | 545 | gl.uniformMatrix4fv(shader.uMvp, false, this.mMVPMatrix); 546 | gl.drawElements(gl.POINTS, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 547 | } 548 | 549 | drawShaftVBOTranslatedRotatedScaled(shader, model, tx, ty, tz, rx, ry, rz, sx, sy, sz) { 550 | model.bindBuffers(); 551 | 552 | gl.enableVertexAttribArray(shader.rm_Vertex); 553 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 554 | gl.enableVertexAttribArray(shader.rm_Normal); 555 | 556 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.FLOAT, false, 4 * (3 + 2 + 2 + 3), 0); 557 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.FLOAT, false, 4 * (3 + 2 + 2 + 3), 4 * (3)); 558 | gl.vertexAttribPointer(shader.rm_Normal, 3, gl.FLOAT, false, 4 * (3 + 2 + 2 + 3), 4 * (3 + 2 + 2)); 559 | 560 | this.calculateMVPMatrix(tx, ty, tz, rx, ry, rz, sx, sy, sz); 561 | 562 | gl.uniformMatrix4fv(shader.view_matrix, false, this.mVMatrix); 563 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mMVPMatrix); 564 | gl.drawElements(gl.TRIANGLES, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 565 | } 566 | 567 | drawSphericalMapLmVBOTranslatedRotatedScaled(shader, model, tx, ty, tz, rx, ry, rz, sx, sy, sz) { 568 | model.bindBuffers(); 569 | 570 | gl.enableVertexAttribArray(shader.rm_Vertex); 571 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 572 | gl.enableVertexAttribArray(shader.rm_TexCoord1); 573 | gl.enableVertexAttribArray(shader.rm_Normal); 574 | 575 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.HALF_FLOAT, false, 16, 0); 576 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.UNSIGNED_BYTE, true, 16, 2 * 3); 577 | gl.vertexAttribPointer(shader.rm_TexCoord1, 2, gl.UNSIGNED_BYTE, true, 16, 2 * 3 + 1 * 2); 578 | gl.vertexAttribPointer(shader.rm_Normal, 3, gl.BYTE, true, 16, 2 * 3 + 1 * 2 + 1 * 2); 579 | // gl.vertexAttribPointer(shader.rm_Normal, 4, gl.INT_2_10_10_10_REV, true, 16, 12); 580 | 581 | this.calculateMVPMatrix(tx, ty, tz, rx, ry, rz, sx, sy, sz); 582 | 583 | gl.uniformMatrix4fv(shader.view_matrix, false, this.mVMatrix); 584 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mMVPMatrix); 585 | gl.drawElements(gl.TRIANGLES, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 586 | } 587 | 588 | drawDiffuseVBOTranslatedRotatedScaled(shader, model, tx, ty, tz, rx, ry, rz, sx, sy, sz) { 589 | model.bindBuffers(); 590 | 591 | gl.enableVertexAttribArray(shader.rm_Vertex); 592 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 593 | 594 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.HALF_FLOAT, false, 8, 0); 595 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.UNSIGNED_BYTE, true, 8, 2 * 3); 596 | 597 | this.calculateMVPMatrix(tx, ty, tz, rx, ry, rz, sx, sy, sz); 598 | 599 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mMVPMatrix); 600 | gl.drawElements(gl.TRIANGLES, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 601 | } 602 | 603 | drawLMVBOTranslatedRotatedScaled(shader, model, tx, ty, tz, rx, ry, rz, sx, sy, sz) { 604 | model.bindBuffers(); 605 | 606 | gl.enableVertexAttribArray(shader.rm_Vertex); 607 | gl.enableVertexAttribArray(shader.rm_TexCoord0); 608 | gl.enableVertexAttribArray(shader.rm_TexCoord1); 609 | 610 | gl.vertexAttribPointer(shader.rm_Vertex, 3, gl.FLOAT, false, 4 * (3 + 2 + 2), 0); 611 | gl.vertexAttribPointer(shader.rm_TexCoord0, 2, gl.FLOAT, false, 4 * (3 + 2 + 2), 4 * (3)); 612 | gl.vertexAttribPointer(shader.rm_TexCoord1, 2, gl.FLOAT, false, 4 * (3 + 2 + 2), 4 * (3 + 2)); 613 | 614 | this.calculateMVPMatrix(tx, ty, tz, rx, ry, rz, sx, sy, sz); 615 | 616 | gl.uniformMatrix4fv(shader.view_proj_matrix, false, this.mMVPMatrix); 617 | gl.drawElements(gl.TRIANGLES, model.getNumIndices() * 3, gl.UNSIGNED_SHORT, 0); 618 | } 619 | 620 | /** 621 | * Updates camera rotation 622 | */ 623 | animate() { 624 | var timeNow = new Date().getTime(), 625 | elapsed; 626 | 627 | if (this.lastTime != 0) { 628 | elapsed = timeNow - this.lastTime; 629 | 630 | this.angleYaw += elapsed / this.YAW_COEFF_NORMAL; 631 | this.angleYaw %= 360.0; 632 | 633 | this.timerDustRotation = (timeNow % this.DUST_SPEED) / this.DUST_SPEED; 634 | this.timerDustFlicker = (timeNow % this.DUST_FLICKER_SPEED) / this.DUST_FLICKER_SPEED; 635 | this.timerSmokeRotation = (timeNow % this.SMOKE_SPEED) / this.SMOKE_SPEED; 636 | } 637 | 638 | this.lastTime = timeNow; 639 | } 640 | } 641 | 642 | return BuddhaRenderer; 643 | }); 644 | --------------------------------------------------------------------------------