From 35575d796ec401c451d4743281bd03e50f9d5a68 Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 23 Jun 2017 15:12:16 -0400 Subject: [PATCH 1/8] Move all the mesh stuff out of scene. --- index.html | 3 +- jquery-3.1.1.js => libs/jquery-3.1.1.js | 0 mesh.js | 457 ++++++++++++++++++++++++ scene.js | 453 ----------------------- 4 files changed, 459 insertions(+), 454 deletions(-) rename jquery-3.1.1.js => libs/jquery-3.1.1.js (100%) create mode 100644 mesh.js diff --git a/index.html b/index.html index f979c5a7..770f9228 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,8 @@ - + + diff --git a/jquery-3.1.1.js b/libs/jquery-3.1.1.js similarity index 100% rename from jquery-3.1.1.js rename to libs/jquery-3.1.1.js diff --git a/mesh.js b/mesh.js new file mode 100644 index 00000000..9955f461 --- /dev/null +++ b/mesh.js @@ -0,0 +1,457 @@ +var assets = {}; + +function defined(value) { + return value !== undefined && value !== null; +} + +class Mesh { + constructor(gl, scene, globalState, modelPath, gltf, meshIdx) { + this.modelPath = modelPath; + this.scene = scene; + + this.defines = { + 'USE_MATHS': 1, + 'USE_IBL': 1, + }; + + this.glState = { + uniforms: {}, + uniformLocations: {}, + attributes: {}, + vertSource: globalState.vertSource, + fragSource: globalState.fragSource, + sRGBifAvailable : globalState.sRGBifAvailable + }; + + var primitives = gltf.meshes[meshIdx].primitives; + // todo: multiple primitives doesn't work. + for (let i = 0; i < primitives.length; i++) { + var primitive = primitives[Object.keys(primitives)[i]]; + + for (let attribute in primitive.attributes) { + switch (attribute) { + case "NORMAL": + this.defines.HAS_NORMALS = 1; + break; + case "TANGENT": + this.defines.HAS_TANGENTS = 1; + break; + case "TEXCOORD_0": + this.defines.HAS_UV = 1; + break; + } + } + + // Material + var materialName = primitive.material; + if (defined(materialName)) { + this.material = gltf.materials[materialName]; + } + var imageInfos = this.initTextures(gl, gltf); + + this.initProgram(gl, globalState); + + this.accessorsLoading = 0; + // Attributes + for (let attribute in primitive.attributes) { + getAccessorData(this, gl, gltf, modelPath, primitive.attributes[attribute], attribute); + } + + // Indices + getAccessorData(this, gl, gltf, modelPath, primitive.indices, 'INDEX'); + + loadImages(imageInfos, gl, this); + } + } + + + initProgram(gl, globalState) { + var definesToString = function(defines) { + var outStr = ''; + for (var def in defines) { + outStr += '#define ' + def + ' ' + defines[def] + '\n'; + } + return outStr; + }; + + var shaderDefines = definesToString(this.defines);//"#define USE_SAVED_TANGENTS 1\n#define USE_MATHS 1\n#define USE_IBL 1\n"; + if (globalState.hasLODExtension) { + shaderDefines += '#define USE_TEX_LOD 1\n'; + } + + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, shaderDefines + this.glState.vertSource); + gl.compileShader(vertexShader); + var compiled = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS); + if (!compiled) { + error.innerHTML += 'Failed to compile the vertex shader
'; + let compilationLog = gl.getShaderInfoLog(vertexShader); + error.innerHTML += 'Shader compiler log: ' + compilationLog + '
'; + } + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, shaderDefines + this.glState.fragSource); + gl.compileShader(fragmentShader); + compiled = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS); + if (!compiled) { + error.innerHTML += 'Failed to compile the fragment shader
'; + let compilationLog = gl.getShaderInfoLog(fragmentShader); + error.innerHTML += 'Shader compiler log: ' + compilationLog + '
'; + } + + var program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + this.program = program; + } + + drawMesh(gl, transform, view, projection, globalState) { + // Update model matrix + var modelMatrix = mat4.create(); + mat4.multiply(modelMatrix, modelMatrix, transform); + + if (this.material && this.material.doubleSided) { + gl.disable(gl.CULL_FACE); + } else { + gl.enable(gl.CULL_FACE); + } + + // Update mvp matrix + var mvMatrix = mat4.create(); + var mvpMatrix = mat4.create(); + mat4.multiply(mvMatrix, view, modelMatrix); + mat4.multiply(mvpMatrix, projection, mvMatrix); + // these should actually be local to the mesh (not in global) + globalState.uniforms['u_mvpMatrix'].vals = [false, mvpMatrix]; + + // Update normal matrix + globalState.uniforms['u_modelMatrix'].vals = [false, modelMatrix]; + + applyState(gl, this.program, globalState, this.glState); + + // Draw + if (defined(this.indicesAccessor)) { + gl.drawElements(gl.TRIANGLES, this.indicesAccessor.count, gl.UNSIGNED_SHORT, this.indicesAccessor.byteOffset); + } + + disableState(gl, globalState, this.glState); + } + + initArrayBuffer(gl, data, num, type, attribute, stride, offset) { + var buffer = gl.createBuffer(); + if (!buffer) { + var error = document.GetElementById('error'); + error.innerHTML += 'Failed to create the buffer object
'; + return -1; + } + + gl.useProgram(this.program); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); + + var a_attribute = gl.getAttribLocation(this.program, attribute); + + this.glState.attributes[attribute] = { + 'cmds': [ + { 'funcName': 'bindBuffer', 'vals': [gl.ARRAY_BUFFER, buffer] }, + { 'funcName': 'vertexAttribPointer', 'vals': [a_attribute, num, type, false, stride, offset] }, + { 'funcName': 'enableVertexAttribArray', 'vals': [a_attribute] } + ], + 'a_attribute': a_attribute + }; + return true; + } + + initBuffers(gl, gltf) { + var error = document.getElementById('error'); + var indexBuffer = gl.createBuffer(); + if (!indexBuffer) { + error.innerHTML += 'Failed to create the buffer object
'; + return -1; + } + + if (!this.initArrayBuffer(gl, this.vertices, 3, gl.FLOAT, 'a_Position', this.verticesAccessor.byteStride, this.verticesAccessor.byteOffset)) { + error.innerHTML += 'Failed to initialize position buffer
'; + } + + if (this.normalsAccessor) { + if (!this.initArrayBuffer(gl, this.normals, 3, gl.FLOAT, 'a_Normal', this.normalsAccessor.byteStride, this.normalsAccessor.byteOffset)) { + error.innerHTML += 'Failed to initialize normal buffer
'; + } + } + + if (this.tangentsAccessor) { + if (!this.initArrayBuffer(gl, this.tangents, 4, gl.FLOAT, 'a_Tangent', this.tangentsAccessor.byteStride, this.tangentsAccessor.byteOffset)) { + error.innerHTML += 'Failed to initialize tangent buffer
'; + } + } + + if (this.texcoordsAccessor) { + if (!this.initArrayBuffer(gl, this.texcoords, 2, gl.FLOAT, 'a_UV', this.texcoordsAccessor.byteStride, this.texcoordsAccessor.byteOffset)) { + error.innerHTML += 'Failed to initialize texture buffer
'; + } + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); + + this.loadedBuffers = true; + if (this.pendingTextures === 0) { + this.scene.drawScene(gl); + } + } + + initTextures(gl, gltf) { + var imageInfos = {}; + var pbrMat = this.material ? this.material.pbrMetallicRoughness : null; + var samplerIndex = 3; // skip the first three because of the cubemaps + + // Base Color + var baseColorFactor = pbrMat && defined(pbrMat.baseColorFactor) ? pbrMat.baseColorFactor : [1.0, 1.0, 1.0, 1.0]; + this.glState.uniforms['u_BaseColorFactor'] = { + funcName: 'uniform4f', + vals: baseColorFactor + }; + if (pbrMat && pbrMat.baseColorTexture && gltf.textures.length > pbrMat.baseColorTexture.index) { + var baseColorTexInfo = gltf.textures[pbrMat.baseColorTexture.index]; + var baseColorSrc = this.modelPath + gltf.images[baseColorTexInfo.source].uri; + imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri + this.glState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + samplerIndex++; + this.defines.HAS_BASECOLORMAP = 1; + } + else if (this.glState.uniforms['u_BaseColorSampler']) { + delete this.glState.uniforms['u_BaseColorSampler']; + } + + // Metallic-Roughness + var metallic = (pbrMat && defined(pbrMat.metallicFactor)) ? pbrMat.metallicFactor : 1.0; + var roughness = (pbrMat && defined(pbrMat.roughnessFactor)) ? pbrMat.roughnessFactor : 1.0; + this.glState.uniforms['u_MetallicRoughnessValues'] = { + funcName: 'uniform2f', + vals: [metallic, roughness] + }; + if (pbrMat && pbrMat.metallicRoughnessTexture && gltf.textures.length > pbrMat.metallicRoughnessTexture.index) { + var mrTexInfo = gltf.textures[pbrMat.metallicRoughnessTexture.index]; + var mrSrc = this.modelPath + gltf.images[mrTexInfo.source].uri; + // gltf.samplers[mrTexInfo.sampler].magFilter etc + imageInfos['metalRoughness'] = { 'uri': mrSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; + this.glState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + samplerIndex++; + this.defines.HAS_METALROUGHNESSMAP = 1; + } + else { + if (this.glState.uniforms['u_MetallicRoughnessSampler']) { + delete this.glState.uniforms['u_MetallicRoughnessSampler']; + } + } + + // Normals + if (this.material && this.material.normalTexture && gltf.textures.length > this.material.normalTexture.index) { + var normalScale = defined(this.material.normalTexture.scale) ? this.material.normalTexture.scale : 1.0; + var normalsTexInfo = gltf.textures[this.material.normalTexture.index]; + var normalsSrc = this.modelPath + gltf.images[normalsTexInfo.source].uri; + imageInfos['normal'] = { 'uri': normalsSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; + this.glState.uniforms['u_NormalSampler'] = { + funcName: 'uniform1i', + vals: [samplerIndex] + }; + this.glState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; + samplerIndex++; + this.defines.HAS_NORMALMAP = 1; + } + else if (this.glState.uniforms['u_NormalSampler']) { + delete this.glState.uniforms['u_NormalSampler']; + } + + // brdfLUT + var brdfLUT = 'textures/brdfLUT.png'; + imageInfos['brdfLUT'] = { 'uri': brdfLUT, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA, 'clamp': true }; + this.glState.uniforms['u_brdfLUT'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + samplerIndex++; + + // Emissive + if (this.material && this.material.emissiveTexture) { + var emissiveTexInfo = gltf.textures[this.material.emissiveTexture.index]; + var emissiveSrc = this.modelPath + gltf.images[emissiveTexInfo.source].uri; + imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri + this.glState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + samplerIndex++; + this.defines.HAS_EMISSIVEMAP = 1; + var emissiveFactor = defined(this.material.emissiveFactor) ? this.material.emissiveFactor : [0.0, 0.0, 0.0]; + this.glState.uniforms['u_EmissiveFactor'] = { + funcName: 'uniform3f', + vals: emissiveFactor + }; + } + else if (this.glState.uniforms['u_EmissiveSampler']) { + delete this.glState.uniforms['u_EmissiveSampler']; + } + + // AO + if (this.material && this.material.occlusionTexture) { + var occlusionStrength = defined(this.material.occlusionTexture.strength) ? this.material.occlusionTexture.strength : 1.0; + var occlusionTexInfo = gltf.textures[this.material.occlusionTexture.index]; + var occlusionSrc = this.modelPath + gltf.images[occlusionTexInfo.source].uri; + imageInfos['occlusion'] = { 'uri': occlusionSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; + this.glState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + this.glState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; + samplerIndex++; + this.defines.HAS_OCCLUSIONMAP = 1; + } + else if (this.glState.uniforms['u_OcclusionSampler']) { + delete this.glState.uniforms['u_OcclusionSampler']; + } + + return imageInfos; + } +} + +// +// **** NOTE: The following functions are global **** +// + +function getAccessorData(mesh, gl, gltf, model, accessorName, attribute) { + mesh.accessorsLoading++; + var accessor = gltf.accessors[accessorName]; + var bufferView = gltf.bufferViews[accessor.bufferView]; + var buffer = gltf.buffers[bufferView.buffer]; + var bin = buffer.uri; + + var reader = new FileReader(); + + reader.onload = function(e) { + var arrayBuffer = reader.result; + var start = defined(bufferView.byteOffset) ? bufferView.byteOffset : 0; + var end = start + bufferView.byteLength; + var slicedBuffer = arrayBuffer.slice(start, end); + var data; + if (accessor.componentType === 5126) { + data = new Float32Array(slicedBuffer); + } + else if (accessor.componentType === 5123) { + data = new Uint16Array(slicedBuffer); + } + switch (attribute) { + case "POSITION": mesh.vertices = data; + mesh.verticesAccessor = accessor; + break; + case "NORMAL": mesh.normals = data; + mesh.normalsAccessor = accessor; + break; + case "TANGENT": mesh.tangents = data; + mesh.tangentsAccessor = accessor; + break; + case "TEXCOORD_0": mesh.texcoords = data; + mesh.texcoordsAccessor = accessor; + break; + case "INDEX": mesh.indices = data; + mesh.indicesAccessor = accessor; + break; + } + + mesh.accessorsLoading--; + if (mesh.accessorsLoading === 0) { + mesh.initBuffers(gl, gltf); + } + }; + + var assetUrl = model + bin; + var promise; + if (assets.hasOwnProperty(assetUrl)) { + // We already requested this, and a promise already exists. + promise = assets[assetUrl]; + } else { + // We didn't request this yet, create a promise for it. + var deferred = $.Deferred(); + assets[assetUrl] = deferred; + promise = deferred.promise(); + var oReq = new XMLHttpRequest(); + oReq.open("GET", model + bin, true); + oReq.responseType = "blob"; + oReq.onload = function(e) { + deferred.resolve(oReq.response); + }; + oReq.send(); + } + + // This will fire when the promise is resolved, or immediately if the promise has previously resolved. + promise.then(function(blob) { + reader.readAsArrayBuffer(blob); + }); +} + +function loadImage(imageInfo, gl, mesh) { + var intToGLSamplerIndex = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2, gl.TEXTURE3, gl.TEXTURE4, + gl.TEXTURE5, gl.TEXTURE6, gl.TEXTURE7, gl.TEXTURE8, gl.TEXTURE9]; + var image = new Image(); + mesh.pendingTextures++; + image.src = imageInfo.uri; + image.onload = function() { + var texture = gl.createTexture(); + var glIndex = intToGLSamplerIndex[imageInfo.samplerIndex]; + gl.activeTexture(glIndex); + gl.bindTexture(gl.TEXTURE_2D, texture); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,/*imageInfo.colorSpace, imageInfo.colorSpace,*/ gl.UNSIGNED_BYTE, image); + + mesh.pendingTextures--; + + if (mesh.loadedBuffers === true && mesh.pendingTextures === 0) { + mesh.scene.drawScene(gl); + } + }; + + return image; +} + +function loadImages(imageInfos, gl, mesh) { + mesh.pendingTextures = 0; + for (var i in imageInfos) { + loadImage(imageInfos[i], gl, mesh); + } +} + +function applyState(gl, program, globalState, localState) { + gl.useProgram(program); + + var applyUniform = function(u, uniformName) { + if (!defined(localState.uniformLocations[uniformName])) { + localState.uniformLocations[uniformName] = gl.getUniformLocation(program, uniformName); + } + + if (u.funcName && defined(localState.uniformLocations[uniformName]) && u.vals) { + gl[u.funcName](localState.uniformLocations[uniformName], ...u.vals); + } + }; + + for (let uniform in globalState.uniforms) { + applyUniform(globalState.uniforms[uniform], uniform); + } + + for (let uniform in localState.uniforms) { + applyUniform(localState.uniforms[uniform], uniform); + } + + for (var attrib in localState.attributes) { + var a = localState.attributes[attrib]; + for (var cmd in a.cmds) { + var c = a.cmds[cmd]; + gl[c.funcName](...c.vals); + } + } +} + +function disableState(gl, globalState, localState) { + for (var attrib in localState.attributes) { + // do something. + gl.disableVertexAttribArray(localState.attributes[attrib].a_attribute); + } +} diff --git a/scene.js b/scene.js index 84a1dfa3..861e1912 100644 --- a/scene.js +++ b/scene.js @@ -1,314 +1,3 @@ -var assets = {}; - -function defined(value) { - return value !== undefined && value !== null; -} - -class Mesh { - constructor(gl, scene, globalState, modelPath, gltf, meshIdx) { - this.modelPath = modelPath; - this.scene = scene; - - this.defines = { - 'USE_MATHS': 1, - 'USE_IBL': 1, - }; - - this.glState = { - uniforms: {}, - uniformLocations: {}, - attributes: {}, - vertSource: globalState.vertSource, - fragSource: globalState.fragSource, - sRGBifAvailable : globalState.sRGBifAvailable - }; - - var primitives = gltf.meshes[meshIdx].primitives; - // todo: multiple primitives doesn't work. - for (let i = 0; i < primitives.length; i++) { - var primitive = primitives[Object.keys(primitives)[i]]; - - for (let attribute in primitive.attributes) { - switch (attribute) { - case "NORMAL": - this.defines.HAS_NORMALS = 1; - break; - case "TANGENT": - this.defines.HAS_TANGENTS = 1; - break; - case "TEXCOORD_0": - this.defines.HAS_UV = 1; - break; - } - } - - // Material - var materialName = primitive.material; - if (defined(materialName)) { - this.material = gltf.materials[materialName]; - } - var imageInfos = this.initTextures(gl, gltf); - - this.initProgram(gl, globalState); - - this.accessorsLoading = 0; - // Attributes - for (let attribute in primitive.attributes) { - getAccessorData(this, gl, gltf, modelPath, primitive.attributes[attribute], attribute); - } - - // Indices - getAccessorData(this, gl, gltf, modelPath, primitive.indices, 'INDEX'); - - loadImages(imageInfos, gl, this); - } - } - - - initProgram(gl, globalState) { - var definesToString = function(defines) { - var outStr = ''; - for (var def in defines) { - outStr += '#define ' + def + ' ' + defines[def] + '\n'; - } - return outStr; - }; - - var shaderDefines = definesToString(this.defines);//"#define USE_SAVED_TANGENTS 1\n#define USE_MATHS 1\n#define USE_IBL 1\n"; - if (globalState.hasLODExtension) { - shaderDefines += '#define USE_TEX_LOD 1\n'; - } - - var vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, shaderDefines + this.glState.vertSource); - gl.compileShader(vertexShader); - var compiled = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS); - if (!compiled) { - error.innerHTML += 'Failed to compile the vertex shader
'; - let compilationLog = gl.getShaderInfoLog(vertexShader); - error.innerHTML += 'Shader compiler log: ' + compilationLog + '
'; - } - - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, shaderDefines + this.glState.fragSource); - gl.compileShader(fragmentShader); - compiled = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS); - if (!compiled) { - error.innerHTML += 'Failed to compile the fragment shader
'; - let compilationLog = gl.getShaderInfoLog(fragmentShader); - error.innerHTML += 'Shader compiler log: ' + compilationLog + '
'; - } - - var program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - this.program = program; - } - - drawMesh(gl, transform, view, projection, globalState) { - // Update model matrix - var modelMatrix = mat4.create(); - mat4.multiply(modelMatrix, modelMatrix, transform); - - if (this.material && this.material.doubleSided) { - gl.disable(gl.CULL_FACE); - } else { - gl.enable(gl.CULL_FACE); - } - - // Update mvp matrix - var mvMatrix = mat4.create(); - var mvpMatrix = mat4.create(); - mat4.multiply(mvMatrix, view, modelMatrix); - mat4.multiply(mvpMatrix, projection, mvMatrix); - // these should actually be local to the mesh (not in global) - globalState.uniforms['u_mvpMatrix'].vals = [false, mvpMatrix]; - - // Update normal matrix - globalState.uniforms['u_modelMatrix'].vals = [false, modelMatrix]; - - applyState(gl, this.program, globalState, this.glState); - - // Draw - if (defined(this.indicesAccessor)) { - gl.drawElements(gl.TRIANGLES, this.indicesAccessor.count, gl.UNSIGNED_SHORT, this.indicesAccessor.byteOffset); - } - - disableState(gl, globalState, this.glState); - } - - initArrayBuffer(gl, data, num, type, attribute, stride, offset) { - var buffer = gl.createBuffer(); - if (!buffer) { - var error = document.GetElementById('error'); - error.innerHTML += 'Failed to create the buffer object
'; - return -1; - } - - gl.useProgram(this.program); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); - - var a_attribute = gl.getAttribLocation(this.program, attribute); - - this.glState.attributes[attribute] = { - 'cmds': [ - { 'funcName': 'bindBuffer', 'vals': [gl.ARRAY_BUFFER, buffer] }, - { 'funcName': 'vertexAttribPointer', 'vals': [a_attribute, num, type, false, stride, offset] }, - { 'funcName': 'enableVertexAttribArray', 'vals': [a_attribute] } - ], - 'a_attribute': a_attribute - }; - return true; - } - - initBuffers(gl, gltf) { - var error = document.getElementById('error'); - var indexBuffer = gl.createBuffer(); - if (!indexBuffer) { - error.innerHTML += 'Failed to create the buffer object
'; - return -1; - } - - if (!this.initArrayBuffer(gl, this.vertices, 3, gl.FLOAT, 'a_Position', this.verticesAccessor.byteStride, this.verticesAccessor.byteOffset)) { - error.innerHTML += 'Failed to initialize position buffer
'; - } - - if (this.normalsAccessor) { - if (!this.initArrayBuffer(gl, this.normals, 3, gl.FLOAT, 'a_Normal', this.normalsAccessor.byteStride, this.normalsAccessor.byteOffset)) { - error.innerHTML += 'Failed to initialize normal buffer
'; - } - } - - if (this.tangentsAccessor) { - if (!this.initArrayBuffer(gl, this.tangents, 4, gl.FLOAT, 'a_Tangent', this.tangentsAccessor.byteStride, this.tangentsAccessor.byteOffset)) { - error.innerHTML += 'Failed to initialize tangent buffer
'; - } - } - - if (this.texcoordsAccessor) { - if (!this.initArrayBuffer(gl, this.texcoords, 2, gl.FLOAT, 'a_UV', this.texcoordsAccessor.byteStride, this.texcoordsAccessor.byteOffset)) { - error.innerHTML += 'Failed to initialize texture buffer
'; - } - } - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - this.loadedBuffers = true; - if (this.pendingTextures === 0) { - this.scene.drawScene(gl); - } - } - - initTextures(gl, gltf) { - var imageInfos = {}; - var pbrMat = this.material ? this.material.pbrMetallicRoughness : null; - var samplerIndex = 3; // skip the first three because of the cubemaps - - // Base Color - var baseColorFactor = pbrMat && defined(pbrMat.baseColorFactor) ? pbrMat.baseColorFactor : [1.0, 1.0, 1.0, 1.0]; - this.glState.uniforms['u_BaseColorFactor'] = { - funcName: 'uniform4f', - vals: baseColorFactor - }; - if (pbrMat && pbrMat.baseColorTexture && gltf.textures.length > pbrMat.baseColorTexture.index) { - var baseColorTexInfo = gltf.textures[pbrMat.baseColorTexture.index]; - var baseColorSrc = this.modelPath + gltf.images[baseColorTexInfo.source].uri; - imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.glState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; - this.defines.HAS_BASECOLORMAP = 1; - } - else if (this.glState.uniforms['u_BaseColorSampler']) { - delete this.glState.uniforms['u_BaseColorSampler']; - } - - // Metallic-Roughness - var metallic = (pbrMat && defined(pbrMat.metallicFactor)) ? pbrMat.metallicFactor : 1.0; - var roughness = (pbrMat && defined(pbrMat.roughnessFactor)) ? pbrMat.roughnessFactor : 1.0; - this.glState.uniforms['u_MetallicRoughnessValues'] = { - funcName: 'uniform2f', - vals: [metallic, roughness] - }; - if (pbrMat && pbrMat.metallicRoughnessTexture && gltf.textures.length > pbrMat.metallicRoughnessTexture.index) { - var mrTexInfo = gltf.textures[pbrMat.metallicRoughnessTexture.index]; - var mrSrc = this.modelPath + gltf.images[mrTexInfo.source].uri; - // gltf.samplers[mrTexInfo.sampler].magFilter etc - imageInfos['metalRoughness'] = { 'uri': mrSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; - this.defines.HAS_METALROUGHNESSMAP = 1; - } - else { - if (this.glState.uniforms['u_MetallicRoughnessSampler']) { - delete this.glState.uniforms['u_MetallicRoughnessSampler']; - } - } - - // Normals - if (this.material && this.material.normalTexture && gltf.textures.length > this.material.normalTexture.index) { - var normalScale = defined(this.material.normalTexture.scale) ? this.material.normalTexture.scale : 1.0; - var normalsTexInfo = gltf.textures[this.material.normalTexture.index]; - var normalsSrc = this.modelPath + gltf.images[normalsTexInfo.source].uri; - imageInfos['normal'] = { 'uri': normalsSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_NormalSampler'] = { - funcName: 'uniform1i', - vals: [samplerIndex] - }; - this.glState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; - samplerIndex++; - this.defines.HAS_NORMALMAP = 1; - } - else if (this.glState.uniforms['u_NormalSampler']) { - delete this.glState.uniforms['u_NormalSampler']; - } - - // brdfLUT - var brdfLUT = 'textures/brdfLUT.png'; - imageInfos['brdfLUT'] = { 'uri': brdfLUT, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA, 'clamp': true }; - this.glState.uniforms['u_brdfLUT'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; - - // Emissive - if (this.material && this.material.emissiveTexture) { - var emissiveTexInfo = gltf.textures[this.material.emissiveTexture.index]; - var emissiveSrc = this.modelPath + gltf.images[emissiveTexInfo.source].uri; - imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.glState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; - this.defines.HAS_EMISSIVEMAP = 1; - var emissiveFactor = defined(this.material.emissiveFactor) ? this.material.emissiveFactor : [0.0, 0.0, 0.0]; - this.glState.uniforms['u_EmissiveFactor'] = { - funcName: 'uniform3f', - vals: emissiveFactor - }; - } - else if (this.glState.uniforms['u_EmissiveSampler']) { - delete this.glState.uniforms['u_EmissiveSampler']; - } - - // AO - if (this.material && this.material.occlusionTexture) { - var occlusionStrength = defined(this.material.occlusionTexture.strength) ? this.material.occlusionTexture.strength : 1.0; - var occlusionTexInfo = gltf.textures[this.material.occlusionTexture.index]; - var occlusionSrc = this.modelPath + gltf.images[occlusionTexInfo.source].uri; - imageInfos['occlusion'] = { 'uri': occlusionSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - this.glState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; - samplerIndex++; - this.defines.HAS_OCCLUSIONMAP = 1; - } - else if (this.glState.uniforms['u_OcclusionSampler']) { - delete this.glState.uniforms['u_OcclusionSampler']; - } - - return imageInfos; - } -} - class Scene { constructor(gl, glState, model, gltf) { this.globalState = glState; @@ -376,145 +65,3 @@ class Scene { } } -function getAccessorData(mesh, gl, gltf, model, accessorName, attribute) { - mesh.accessorsLoading++; - var accessor = gltf.accessors[accessorName]; - var bufferView = gltf.bufferViews[accessor.bufferView]; - var buffer = gltf.buffers[bufferView.buffer]; - var bin = buffer.uri; - - var reader = new FileReader(); - - reader.onload = function(e) { - var arrayBuffer = reader.result; - var start = defined(bufferView.byteOffset) ? bufferView.byteOffset : 0; - var end = start + bufferView.byteLength; - var slicedBuffer = arrayBuffer.slice(start, end); - var data; - if (accessor.componentType === 5126) { - data = new Float32Array(slicedBuffer); - } - else if (accessor.componentType === 5123) { - data = new Uint16Array(slicedBuffer); - } - switch (attribute) { - case "POSITION": mesh.vertices = data; - mesh.verticesAccessor = accessor; - break; - case "NORMAL": mesh.normals = data; - mesh.normalsAccessor = accessor; - break; - case "TANGENT": mesh.tangents = data; - mesh.tangentsAccessor = accessor; - break; - case "TEXCOORD_0": mesh.texcoords = data; - mesh.texcoordsAccessor = accessor; - break; - case "INDEX": mesh.indices = data; - mesh.indicesAccessor = accessor; - break; - } - - mesh.accessorsLoading--; - if (mesh.accessorsLoading === 0) { - mesh.initBuffers(gl, gltf); - } - }; - - var assetUrl = model + bin; - var promise; - if (assets.hasOwnProperty(assetUrl)) { - // We already requested this, and a promise already exists. - promise = assets[assetUrl]; - } else { - // We didn't request this yet, create a promise for it. - var deferred = $.Deferred(); - assets[assetUrl] = deferred; - promise = deferred.promise(); - var oReq = new XMLHttpRequest(); - oReq.open("GET", model + bin, true); - oReq.responseType = "blob"; - oReq.onload = function(e) { - deferred.resolve(oReq.response); - }; - oReq.send(); - } - - // This will fire when the promise is resolved, or immediately if the promise has previously resolved. - promise.then(function(blob) { - reader.readAsArrayBuffer(blob); - }); -} - -function loadImage(imageInfo, gl, mesh) { - var intToGLSamplerIndex = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2, gl.TEXTURE3, gl.TEXTURE4, - gl.TEXTURE5, gl.TEXTURE6, gl.TEXTURE7, gl.TEXTURE8, gl.TEXTURE9]; - var image = new Image(); - mesh.pendingTextures++; - image.src = imageInfo.uri; - image.onload = function() { - var texture = gl.createTexture(); - var glIndex = intToGLSamplerIndex[imageInfo.samplerIndex]; - gl.activeTexture(glIndex); - gl.bindTexture(gl.TEXTURE_2D, texture); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,/*imageInfo.colorSpace, imageInfo.colorSpace,*/ gl.UNSIGNED_BYTE, image); - - mesh.pendingTextures--; - - if (mesh.loadedBuffers === true && mesh.pendingTextures === 0) { - mesh.scene.drawScene(gl); - } - }; - - return image; -} - -function loadImages(imageInfos, gl, mesh) { - mesh.pendingTextures = 0; - for (var i in imageInfos) { - loadImage(imageInfos[i], gl, mesh); - } -} - -function applyState(gl, program, globalState, localState) { - gl.useProgram(program); - - var applyUniform = function(u, uniformName) { - if (!defined(localState.uniformLocations[uniformName])) { - localState.uniformLocations[uniformName] = gl.getUniformLocation(program, uniformName); - } - - if (u.funcName && defined(localState.uniformLocations[uniformName]) && u.vals) { - gl[u.funcName](localState.uniformLocations[uniformName], ...u.vals); - } - }; - - for (let uniform in globalState.uniforms) { - applyUniform(globalState.uniforms[uniform], uniform); - } - - for (let uniform in localState.uniforms) { - applyUniform(localState.uniforms[uniform], uniform); - } - - for (var attrib in localState.attributes) { - var a = localState.attributes[attrib]; - for (var cmd in a.cmds) { - var c = a.cmds[cmd]; - gl[c.funcName](...c.vals); - } - } -} - -function disableState(gl, globalState, localState) { - for (var attrib in localState.attributes) { - // do something. - gl.disableVertexAttribArray(localState.attributes[attrib].a_attribute); - } -} From 0a4849cf2bacd9bfbd0c08ea13fe5fb6cc64a25b Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 23 Jun 2017 16:29:52 -0400 Subject: [PATCH 2/8] Refactor accessorData and environment image callback. --- main.js | 20 ++++---- mesh.js | 146 ++++++++++++++++++++++++++++--------------------------- scene.js | 1 + 3 files changed, 86 insertions(+), 81 deletions(-) diff --git a/main.js b/main.js index 2837d555..e4b0ece3 100644 --- a/main.js +++ b/main.js @@ -42,6 +42,16 @@ function loadCubeMap(gl, envMap, type, state) { var path = "textures/" + envMap + "/" + type + "/" + type; + function onLoadEnvironmentImage(texture, face, image, j) { + return function() { + gl.activeTexture(activeTextureEnum); + gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + // todo: should this be srgb? or rgba? what's the HDR scale on this? + gl.texImage2D(face, j, state.sRGBifAvailable, state.sRGBifAvailable, gl.UNSIGNED_BYTE, image); + }; + } + for (var j = 0; j < mipLevels; j++) { var faces = [[path + "_right_" + j + ".jpg", gl.TEXTURE_CUBE_MAP_POSITIVE_X], [path + "_left_" + j + ".jpg", gl.TEXTURE_CUBE_MAP_NEGATIVE_X], @@ -52,15 +62,7 @@ function loadCubeMap(gl, envMap, type, state) { for (var i = 0; i < faces.length; i++) { var face = faces[i][1]; var image = new Image(); - image.onload = function(texture, face, image, j) { - return function() { - gl.activeTexture(activeTextureEnum); - gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - // todo: should this be srgb? or rgba? what's the HDR scale on this? - gl.texImage2D(face, j, state.sRGBifAvailable, state.sRGBifAvailable, gl.UNSIGNED_BYTE, image); - } - }(texture, face, image, j); + image.onload = onLoadEnvironmentImage(texture, face, image, j); image.src = faces[i][0]; } } diff --git a/mesh.js b/mesh.js index 9955f461..4a04c987 100644 --- a/mesh.js +++ b/mesh.js @@ -1,5 +1,3 @@ -var assets = {}; - function defined(value) { return value !== undefined && value !== null; } @@ -54,11 +52,11 @@ class Mesh { this.accessorsLoading = 0; // Attributes for (let attribute in primitive.attributes) { - getAccessorData(this, gl, gltf, modelPath, primitive.attributes[attribute], attribute); + this.getAccessorData(gl, gltf, modelPath, primitive.attributes[attribute], attribute); } // Indices - getAccessorData(this, gl, gltf, modelPath, primitive.indices, 'INDEX'); + this.getAccessorData(gl, gltf, modelPath, primitive.indices, 'INDEX'); loadImages(imageInfos, gl, this); } @@ -307,82 +305,86 @@ class Mesh { return imageInfos; } -} -// -// **** NOTE: The following functions are global **** -// + getAccessorData(gl, gltf, modelPath, accessorName, attribute) { + var mesh = this; + this.accessorsLoading++; + var accessor = gltf.accessors[accessorName]; + var bufferView = gltf.bufferViews[accessor.bufferView]; + var buffer = gltf.buffers[bufferView.buffer]; + var bin = buffer.uri; + + var reader = new FileReader(); + + reader.onload = function(e) { + var arrayBuffer = reader.result; + var start = defined(bufferView.byteOffset) ? bufferView.byteOffset : 0; + var end = start + bufferView.byteLength; + var slicedBuffer = arrayBuffer.slice(start, end); + var data; + if (accessor.componentType === 5126) { + data = new Float32Array(slicedBuffer); + } + else if (accessor.componentType === 5123) { + data = new Uint16Array(slicedBuffer); + } + switch (attribute) { + case "POSITION": mesh.vertices = data; + mesh.verticesAccessor = accessor; + break; + case "NORMAL": mesh.normals = data; + mesh.normalsAccessor = accessor; + break; + case "TANGENT": mesh.tangents = data; + mesh.tangentsAccessor = accessor; + break; + case "TEXCOORD_0": mesh.texcoords = data; + mesh.texcoordsAccessor = accessor; + break; + case "INDEX": mesh.indices = data; + mesh.indicesAccessor = accessor; + break; + default: + console.warn('Unknown attribute semantic: ' + attribute); + } -function getAccessorData(mesh, gl, gltf, model, accessorName, attribute) { - mesh.accessorsLoading++; - var accessor = gltf.accessors[accessorName]; - var bufferView = gltf.bufferViews[accessor.bufferView]; - var buffer = gltf.buffers[bufferView.buffer]; - var bin = buffer.uri; - - var reader = new FileReader(); - - reader.onload = function(e) { - var arrayBuffer = reader.result; - var start = defined(bufferView.byteOffset) ? bufferView.byteOffset : 0; - var end = start + bufferView.byteLength; - var slicedBuffer = arrayBuffer.slice(start, end); - var data; - if (accessor.componentType === 5126) { - data = new Float32Array(slicedBuffer); - } - else if (accessor.componentType === 5123) { - data = new Uint16Array(slicedBuffer); - } - switch (attribute) { - case "POSITION": mesh.vertices = data; - mesh.verticesAccessor = accessor; - break; - case "NORMAL": mesh.normals = data; - mesh.normalsAccessor = accessor; - break; - case "TANGENT": mesh.tangents = data; - mesh.tangentsAccessor = accessor; - break; - case "TEXCOORD_0": mesh.texcoords = data; - mesh.texcoordsAccessor = accessor; - break; - case "INDEX": mesh.indices = data; - mesh.indicesAccessor = accessor; - break; - } + mesh.accessorsLoading--; + if (mesh.accessorsLoading === 0) { + mesh.initBuffers(gl, gltf); + } + }; - mesh.accessorsLoading--; - if (mesh.accessorsLoading === 0) { - mesh.initBuffers(gl, gltf); + var assets = mesh.scene.assets; + var assetUrl = modelPath + bin; + var promise; + if (assets.hasOwnProperty(assetUrl)) { + // We already requested this, and a promise already exists. + promise = assets[assetUrl]; + } else { + // We didn't request this yet, create a promise for it. + var deferred = $.Deferred(); + assets[assetUrl] = deferred; + promise = deferred.promise(); + var oReq = new XMLHttpRequest(); + oReq.open("GET", assetUrl, true); + oReq.responseType = "blob"; + oReq.onload = function(e) { + deferred.resolve(oReq.response); + }; + oReq.send(); } - }; - var assetUrl = model + bin; - var promise; - if (assets.hasOwnProperty(assetUrl)) { - // We already requested this, and a promise already exists. - promise = assets[assetUrl]; - } else { - // We didn't request this yet, create a promise for it. - var deferred = $.Deferred(); - assets[assetUrl] = deferred; - promise = deferred.promise(); - var oReq = new XMLHttpRequest(); - oReq.open("GET", model + bin, true); - oReq.responseType = "blob"; - oReq.onload = function(e) { - deferred.resolve(oReq.response); - }; - oReq.send(); + // This will fire when the promise is resolved, or immediately if the promise has previously resolved. + promise.then(function(blob) { + reader.readAsArrayBuffer(blob); + }); } - - // This will fire when the promise is resolved, or immediately if the promise has previously resolved. - promise.then(function(blob) { - reader.readAsArrayBuffer(blob); - }); } +// +// **** NOTE: The following functions are global **** +// + function loadImage(imageInfo, gl, mesh) { var intToGLSamplerIndex = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2, gl.TEXTURE3, gl.TEXTURE4, gl.TEXTURE5, gl.TEXTURE6, gl.TEXTURE7, gl.TEXTURE8, gl.TEXTURE9]; diff --git a/scene.js b/scene.js index 861e1912..6632cf89 100644 --- a/scene.js +++ b/scene.js @@ -4,6 +4,7 @@ class Scene { this.nodes = gltf.nodes; this.meshes = []; + this.assets = {}; for (var meshIdx in gltf.meshes) { this.meshes.push(new Mesh(gl, this, this.globalState, model, gltf, meshIdx)); } From 90f54afad00868c81513dc31b1d3233aaed4206d Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 23 Jun 2017 17:10:57 -0400 Subject: [PATCH 3/8] The scene shares samplers now. --- mesh.js | 18 ++++++++---------- scene.js | 9 +++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/mesh.js b/mesh.js index 4a04c987..5dcc979d 100644 --- a/mesh.js +++ b/mesh.js @@ -204,7 +204,7 @@ class Mesh { initTextures(gl, gltf) { var imageInfos = {}; var pbrMat = this.material ? this.material.pbrMetallicRoughness : null; - var samplerIndex = 3; // skip the first three because of the cubemaps + var samplerIndex; // Base Color var baseColorFactor = pbrMat && defined(pbrMat.baseColorFactor) ? pbrMat.baseColorFactor : [1.0, 1.0, 1.0, 1.0]; @@ -215,9 +215,9 @@ class Mesh { if (pbrMat && pbrMat.baseColorTexture && gltf.textures.length > pbrMat.baseColorTexture.index) { var baseColorTexInfo = gltf.textures[pbrMat.baseColorTexture.index]; var baseColorSrc = this.modelPath + gltf.images[baseColorTexInfo.source].uri; + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri this.glState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; this.defines.HAS_BASECOLORMAP = 1; } else if (this.glState.uniforms['u_BaseColorSampler']) { @@ -235,9 +235,9 @@ class Mesh { var mrTexInfo = gltf.textures[pbrMat.metallicRoughnessTexture.index]; var mrSrc = this.modelPath + gltf.images[mrTexInfo.source].uri; // gltf.samplers[mrTexInfo.sampler].magFilter etc + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['metalRoughness'] = { 'uri': mrSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; this.glState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; this.defines.HAS_METALROUGHNESSMAP = 1; } else { @@ -251,13 +251,13 @@ class Mesh { var normalScale = defined(this.material.normalTexture.scale) ? this.material.normalTexture.scale : 1.0; var normalsTexInfo = gltf.textures[this.material.normalTexture.index]; var normalsSrc = this.modelPath + gltf.images[normalsTexInfo.source].uri; + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['normal'] = { 'uri': normalsSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; this.glState.uniforms['u_NormalSampler'] = { funcName: 'uniform1i', vals: [samplerIndex] }; this.glState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; - samplerIndex++; this.defines.HAS_NORMALMAP = 1; } else if (this.glState.uniforms['u_NormalSampler']) { @@ -266,17 +266,17 @@ class Mesh { // brdfLUT var brdfLUT = 'textures/brdfLUT.png'; + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['brdfLUT'] = { 'uri': brdfLUT, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA, 'clamp': true }; this.glState.uniforms['u_brdfLUT'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; // Emissive if (this.material && this.material.emissiveTexture) { var emissiveTexInfo = gltf.textures[this.material.emissiveTexture.index]; var emissiveSrc = this.modelPath + gltf.images[emissiveTexInfo.source].uri; + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri this.glState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - samplerIndex++; this.defines.HAS_EMISSIVEMAP = 1; var emissiveFactor = defined(this.material.emissiveFactor) ? this.material.emissiveFactor : [0.0, 0.0, 0.0]; this.glState.uniforms['u_EmissiveFactor'] = { @@ -293,10 +293,10 @@ class Mesh { var occlusionStrength = defined(this.material.occlusionTexture.strength) ? this.material.occlusionTexture.strength : 1.0; var occlusionTexInfo = gltf.textures[this.material.occlusionTexture.index]; var occlusionSrc = this.modelPath + gltf.images[occlusionTexInfo.source].uri; + samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['occlusion'] = { 'uri': occlusionSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; this.glState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; this.glState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; - samplerIndex++; this.defines.HAS_OCCLUSIONMAP = 1; } else if (this.glState.uniforms['u_OcclusionSampler']) { @@ -386,14 +386,12 @@ class Mesh { // function loadImage(imageInfo, gl, mesh) { - var intToGLSamplerIndex = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2, gl.TEXTURE3, gl.TEXTURE4, - gl.TEXTURE5, gl.TEXTURE6, gl.TEXTURE7, gl.TEXTURE8, gl.TEXTURE9]; var image = new Image(); mesh.pendingTextures++; image.src = imageInfo.uri; image.onload = function() { var texture = gl.createTexture(); - var glIndex = intToGLSamplerIndex[imageInfo.samplerIndex]; + var glIndex = gl.TEXTURE0 + imageInfo.samplerIndex; // gl.TEXTUREn enums are in numeric order. gl.activeTexture(glIndex); gl.bindTexture(gl.TEXTURE_2D, texture); diff --git a/scene.js b/scene.js index 6632cf89..6b5b32c7 100644 --- a/scene.js +++ b/scene.js @@ -5,11 +5,20 @@ class Scene { this.nodes = gltf.nodes; this.meshes = []; this.assets = {}; + this.samplerIndex = 3; // skip the first three because of the cubemaps for (var meshIdx in gltf.meshes) { this.meshes.push(new Mesh(gl, this, this.globalState, model, gltf, meshIdx)); } } + getNextSamplerIndex() { + var result = this.samplerIndex++; + if (result > 31) { + throw new Error('Too many texture samplers in use.'); + } + return result; + } + drawScene(gl) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); From a169250e9b5f5a83bda9a88026aaa58db538f7ca Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Tue, 11 Jul 2017 17:40:49 -0400 Subject: [PATCH 4/8] Add load spinner, protect from rendering while textures are pending. --- index.html | 2 ++ main.js | 3 +++ mesh.js | 12 +++++------- scene.js | 5 +++++ style.css | 31 +++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 770f9228..0a8f9c6b 100644 --- a/index.html +++ b/index.html @@ -43,5 +43,7 @@ + +
diff --git a/main.js b/main.js index eb7d84b2..f764bc96 100644 --- a/main.js +++ b/main.js @@ -76,6 +76,9 @@ function updateModel(value, gl, glState, viewMatrix, projectionMatrix, backBuffe var error = document.getElementById('error'); glState.scene = null; gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + var canvas2d = document.getElementById('canvas2d'); + frontBuffer.clearRect(0, 0, canvas2d.width, canvas2d.height); + document.getElementById('loadSpinner').style.display = 'block'; $.ajax({ url: 'models/' + value + '/glTF/' + value + '.gltf', diff --git a/mesh.js b/mesh.js index 95120371..d2f627ad 100644 --- a/mesh.js +++ b/mesh.js @@ -196,9 +196,7 @@ class Mesh { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); this.loadedBuffers = true; - if (this.pendingTextures === 0) { - this.scene.drawScene(gl); - } + this.scene.drawScene(gl); } initTextures(gl, gltf) { @@ -387,7 +385,7 @@ class Mesh { function loadImage(imageInfo, gl, mesh) { var image = new Image(); - mesh.pendingTextures++; + mesh.scene.pendingTextures++; image.src = imageInfo.uri; image.onload = function() { var texture = gl.createTexture(); @@ -402,9 +400,9 @@ function loadImage(imageInfo, gl, mesh) { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,/*imageInfo.colorSpace, imageInfo.colorSpace,*/ gl.UNSIGNED_BYTE, image); - mesh.pendingTextures--; + mesh.scene.pendingTextures--; - if (mesh.loadedBuffers === true && mesh.pendingTextures === 0) { + if (mesh.loadedBuffers === true && mesh.scene.pendingTextures === 0) { mesh.scene.drawScene(gl); } }; @@ -413,7 +411,7 @@ function loadImage(imageInfo, gl, mesh) { } function loadImages(imageInfos, gl, mesh) { - mesh.pendingTextures = 0; + mesh.scene.pendingTextures = 0; for (var i in imageInfos) { loadImage(imageInfos[i], gl, mesh); } diff --git a/scene.js b/scene.js index 6b5b32c7..ccb162a0 100644 --- a/scene.js +++ b/scene.js @@ -5,6 +5,7 @@ class Scene { this.nodes = gltf.nodes; this.meshes = []; this.assets = {}; + this.pendingTextures = 0; this.samplerIndex = 3; // skip the first three because of the cubemaps for (var meshIdx in gltf.meshes) { this.meshes.push(new Mesh(gl, this, this.globalState, model, gltf, meshIdx)); @@ -22,6 +23,10 @@ class Scene { drawScene(gl) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + if (this.pendingTextures > 0) { + return; + } + document.getElementById('loadSpinner').style.display = 'none'; var drawNodeRecursive = function(scene, node, parentTransform) { // Transform diff --git a/style.css b/style.css index 9bc83bc8..7cd6fb97 100644 --- a/style.css +++ b/style.css @@ -104,3 +104,34 @@ hr.line { margin-right: 10px; border: 1px solid black; } + +@keyframes spinKeys { + 0%,50%,100% { + animation-timing-function: cubic-bezier(0.5,0.5,0.5,0.5); + } + + 0% { + transform: rotate(0); + } + + 50% { + transform: rotate(180deg); + } + + 100% { + transform: rotate(360deg); + } +} + +#loadSpinner { + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + margin: -50px 0 0 -50px; + border: 5px solid; + border-color: #fff transparent transparent transparent; + border-radius: 50%; + animation: spinKeys 4s infinite linear; +} From be49b2962274e69fe35b484a266efc5bb6b49980 Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Wed, 12 Jul 2017 17:53:11 -0400 Subject: [PATCH 5/8] Reset camera between models. --- main.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/main.js b/main.js index f764bc96..1a4024ca 100644 --- a/main.js +++ b/main.js @@ -79,6 +79,7 @@ function updateModel(value, gl, glState, viewMatrix, projectionMatrix, backBuffe var canvas2d = document.getElementById('canvas2d'); frontBuffer.clearRect(0, 0, canvas2d.width, canvas2d.height); document.getElementById('loadSpinner').style.display = 'block'; + resetCamera(); $.ajax({ url: 'models/' + value + '/glTF/' + value + '.gltf', @@ -406,12 +407,20 @@ function init(vertSource, fragSource) { } // ***** Mouse Controls ***** // -var mouseDown = false; -var roll = Math.PI; -var pitch = 0.0; -var translate = 4.0; +var mouseDown; +var roll; +var pitch; +var translate; var lastMouseX = null; var lastMouseY = null; + +function resetCamera() { + roll = Math.PI; + pitch = 0.0; + translate = 4.0; + mouseDown = false; +} + function handleMouseDown(ev) { mouseDown = true; lastMouseX = ev.clientX; From b3f4583ceca53d5684eee64dc38285a0e5c74504 Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 14 Jul 2017 16:37:31 -0400 Subject: [PATCH 6/8] Move image management into scene. --- mesh.js | 39 ++++----------------------------------- scene.js | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/mesh.js b/mesh.js index d2f627ad..8c15fe1a 100644 --- a/mesh.js +++ b/mesh.js @@ -21,6 +21,8 @@ class Mesh { sRGBifAvailable : globalState.sRGBifAvailable }; + ++scene.pendingBuffers; + var primitives = gltf.meshes[meshIdx].primitives; // todo: multiple primitives doesn't work. for (let i = 0; i < primitives.length; i++) { @@ -58,7 +60,7 @@ class Mesh { // Indices this.getAccessorData(gl, gltf, modelPath, primitive.indices, 'INDEX'); - loadImages(imageInfos, gl, this); + scene.loadImages(imageInfos, gl, this); } } @@ -195,7 +197,7 @@ class Mesh { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - this.loadedBuffers = true; + --this.scene.pendingBuffers; this.scene.drawScene(gl); } @@ -383,39 +385,6 @@ class Mesh { // **** NOTE: The following functions are global **** // -function loadImage(imageInfo, gl, mesh) { - var image = new Image(); - mesh.scene.pendingTextures++; - image.src = imageInfo.uri; - image.onload = function() { - var texture = gl.createTexture(); - var glIndex = gl.TEXTURE0 + imageInfo.samplerIndex; // gl.TEXTUREn enums are in numeric order. - gl.activeTexture(glIndex); - gl.bindTexture(gl.TEXTURE_2D, texture); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,/*imageInfo.colorSpace, imageInfo.colorSpace,*/ gl.UNSIGNED_BYTE, image); - - mesh.scene.pendingTextures--; - - if (mesh.loadedBuffers === true && mesh.scene.pendingTextures === 0) { - mesh.scene.drawScene(gl); - } - }; - - return image; -} - -function loadImages(imageInfos, gl, mesh) { - mesh.scene.pendingTextures = 0; - for (var i in imageInfos) { - loadImage(imageInfos[i], gl, mesh); - } -} function applyState(gl, program, globalState, localState) { gl.useProgram(program); diff --git a/scene.js b/scene.js index ccb162a0..6fcfba9a 100644 --- a/scene.js +++ b/scene.js @@ -6,6 +6,7 @@ class Scene { this.meshes = []; this.assets = {}; this.pendingTextures = 0; + this.pendingBuffers = 0; this.samplerIndex = 3; // skip the first three because of the cubemaps for (var meshIdx in gltf.meshes) { this.meshes.push(new Mesh(gl, this, this.globalState, model, gltf, meshIdx)); @@ -23,7 +24,7 @@ class Scene { drawScene(gl) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - if (this.pendingTextures > 0) { + if (this.pendingTextures > 0 || this.pendingBuffers > 0) { return; } document.getElementById('loadSpinner').style.display = 'none'; @@ -78,5 +79,37 @@ class Scene { // draw to the front buffer this.frontBuffer.drawImage(this.backBuffer, 0, 0); } + + loadImage(imageInfo, gl) { + var scene = this; + var image = new Image(); + this.pendingTextures++; + image.src = imageInfo.uri; + image.onload = function() { + var texture = gl.createTexture(); + var glIndex = gl.TEXTURE0 + imageInfo.samplerIndex; // gl.TEXTUREn enums are in numeric order. + gl.activeTexture(glIndex); + gl.bindTexture(gl.TEXTURE_2D, texture); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, imageInfo.clamp ? gl.CLAMP_TO_EDGE : gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,/*imageInfo.colorSpace, imageInfo.colorSpace,*/ gl.UNSIGNED_BYTE, image); + + scene.pendingTextures--; + scene.drawScene(gl); + }; + + return image; + } + + loadImages(imageInfos, gl) { + this.pendingTextures = 0; + for (var i in imageInfos) { + this.loadImage(imageInfos[i], gl); + } + } } From 1e9ffbc9f1bb6cdc5452a5968a7a715840de5b9d Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 14 Jul 2017 17:01:10 -0400 Subject: [PATCH 7/8] Move apply/disable state into mesh. --- mesh.js | 125 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/mesh.js b/mesh.js index 8c15fe1a..fc178acd 100644 --- a/mesh.js +++ b/mesh.js @@ -12,15 +12,16 @@ class Mesh { 'USE_IBL': 1, }; - this.glState = { + this.localState = { uniforms: {}, uniformLocations: {}, - attributes: {}, - vertSource: globalState.vertSource, - fragSource: globalState.fragSource, - sRGBifAvailable : globalState.sRGBifAvailable + attributes: {} }; + this.vertSource = globalState.vertSource; + this.fragSource = globalState.fragSource; + this.sRGBifAvailable = globalState.sRGBifAvailable; + ++scene.pendingBuffers; var primitives = gltf.meshes[meshIdx].primitives; @@ -80,7 +81,7 @@ class Mesh { } var vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, shaderDefines + this.glState.vertSource); + gl.shaderSource(vertexShader, shaderDefines + this.vertSource); gl.compileShader(vertexShader); var compiled = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS); if (!compiled) { @@ -90,7 +91,7 @@ class Mesh { } var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, shaderDefines + this.glState.fragSource); + gl.shaderSource(fragmentShader, shaderDefines + this.fragSource); gl.compileShader(fragmentShader); compiled = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS); if (!compiled) { @@ -128,14 +129,14 @@ class Mesh { // Update normal matrix globalState.uniforms['u_ModelMatrix'].vals = [false, modelMatrix]; - applyState(gl, this.program, globalState, this.glState); + this.applyState(gl, globalState); // Draw if (defined(this.indicesAccessor)) { gl.drawElements(gl.TRIANGLES, this.indicesAccessor.count, gl.UNSIGNED_SHORT, this.indicesAccessor.byteOffset); } - disableState(gl, globalState, this.glState); + this.disableState(gl); } initArrayBuffer(gl, data, num, type, attribute, stride, offset) { @@ -153,7 +154,7 @@ class Mesh { var a_attribute = gl.getAttribLocation(this.program, attribute); - this.glState.attributes[attribute] = { + this.localState.attributes[attribute] = { 'cmds': [ { 'funcName': 'bindBuffer', 'vals': [gl.ARRAY_BUFFER, buffer] }, { 'funcName': 'vertexAttribPointer', 'vals': [a_attribute, num, type, false, stride, offset] }, @@ -208,7 +209,7 @@ class Mesh { // Base Color var baseColorFactor = pbrMat && defined(pbrMat.baseColorFactor) ? pbrMat.baseColorFactor : [1.0, 1.0, 1.0, 1.0]; - this.glState.uniforms['u_BaseColorFactor'] = { + this.localState.uniforms['u_BaseColorFactor'] = { funcName: 'uniform4f', vals: baseColorFactor }; @@ -216,18 +217,18 @@ class Mesh { var baseColorTexInfo = gltf.textures[pbrMat.baseColorTexture.index]; var baseColorSrc = this.modelPath + gltf.images[baseColorTexInfo.source].uri; samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.glState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.sRGBifAvailable }; // colorSpace, samplerindex, uri + this.localState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; this.defines.HAS_BASECOLORMAP = 1; } - else if (this.glState.uniforms['u_BaseColorSampler']) { - delete this.glState.uniforms['u_BaseColorSampler']; + else if (this.localState.uniforms['u_BaseColorSampler']) { + delete this.localState.uniforms['u_BaseColorSampler']; } // Metallic-Roughness var metallic = (pbrMat && defined(pbrMat.metallicFactor)) ? pbrMat.metallicFactor : 1.0; var roughness = (pbrMat && defined(pbrMat.roughnessFactor)) ? pbrMat.roughnessFactor : 1.0; - this.glState.uniforms['u_MetallicRoughnessValues'] = { + this.localState.uniforms['u_MetallicRoughnessValues'] = { funcName: 'uniform2f', vals: [metallic, roughness] }; @@ -237,12 +238,12 @@ class Mesh { // gltf.samplers[mrTexInfo.sampler].magFilter etc samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['metalRoughness'] = { 'uri': mrSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + this.localState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; this.defines.HAS_METALROUGHNESSMAP = 1; } else { - if (this.glState.uniforms['u_MetallicRoughnessSampler']) { - delete this.glState.uniforms['u_MetallicRoughnessSampler']; + if (this.localState.uniforms['u_MetallicRoughnessSampler']) { + delete this.localState.uniforms['u_MetallicRoughnessSampler']; } } @@ -253,39 +254,39 @@ class Mesh { var normalsSrc = this.modelPath + gltf.images[normalsTexInfo.source].uri; samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['normal'] = { 'uri': normalsSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_NormalSampler'] = { + this.localState.uniforms['u_NormalSampler'] = { funcName: 'uniform1i', vals: [samplerIndex] }; - this.glState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; + this.localState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; this.defines.HAS_NORMALMAP = 1; } - else if (this.glState.uniforms['u_NormalSampler']) { - delete this.glState.uniforms['u_NormalSampler']; + else if (this.localState.uniforms['u_NormalSampler']) { + delete this.localState.uniforms['u_NormalSampler']; } // brdfLUT var brdfLUT = 'textures/brdfLUT.png'; samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['brdfLUT'] = { 'uri': brdfLUT, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA, 'clamp': true }; - this.glState.uniforms['u_brdfLUT'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + this.localState.uniforms['u_brdfLUT'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; // Emissive if (this.material && this.material.emissiveTexture) { var emissiveTexInfo = gltf.textures[this.material.emissiveTexture.index]; var emissiveSrc = this.modelPath + gltf.images[emissiveTexInfo.source].uri; samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.glState.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.glState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.sRGBifAvailable }; // colorSpace, samplerindex, uri + this.localState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; this.defines.HAS_EMISSIVEMAP = 1; var emissiveFactor = defined(this.material.emissiveFactor) ? this.material.emissiveFactor : [0.0, 0.0, 0.0]; - this.glState.uniforms['u_EmissiveFactor'] = { + this.localState.uniforms['u_EmissiveFactor'] = { funcName: 'uniform3f', vals: emissiveFactor }; } - else if (this.glState.uniforms['u_EmissiveSampler']) { - delete this.glState.uniforms['u_EmissiveSampler']; + else if (this.localState.uniforms['u_EmissiveSampler']) { + delete this.localState.uniforms['u_EmissiveSampler']; } // AO @@ -295,12 +296,12 @@ class Mesh { var occlusionSrc = this.modelPath + gltf.images[occlusionTexInfo.source].uri; samplerIndex = this.scene.getNextSamplerIndex(); imageInfos['occlusion'] = { 'uri': occlusionSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.glState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; - this.glState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; + this.localState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + this.localState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; this.defines.HAS_OCCLUSIONMAP = 1; } - else if (this.glState.uniforms['u_OcclusionSampler']) { - delete this.glState.uniforms['u_OcclusionSampler']; + else if (this.localState.uniforms['u_OcclusionSampler']) { + delete this.localState.uniforms['u_OcclusionSampler']; } return imageInfos; @@ -379,46 +380,44 @@ class Mesh { reader.readAsArrayBuffer(blob); }); } -} -// -// **** NOTE: The following functions are global **** -// + applyState(gl, globalState) { + var program = this.program; + var localState = this.localState; + gl.useProgram(program); + var applyUniform = function(u, uniformName) { + if (!defined(localState.uniformLocations[uniformName])) { + localState.uniformLocations[uniformName] = gl.getUniformLocation(program, uniformName); + } -function applyState(gl, program, globalState, localState) { - gl.useProgram(program); + if (u.funcName && defined(localState.uniformLocations[uniformName]) && u.vals) { + gl[u.funcName](localState.uniformLocations[uniformName], ...u.vals); + } + }; - var applyUniform = function(u, uniformName) { - if (!defined(localState.uniformLocations[uniformName])) { - localState.uniformLocations[uniformName] = gl.getUniformLocation(program, uniformName); + for (let uniform in globalState.uniforms) { + applyUniform(globalState.uniforms[uniform], uniform); } - if (u.funcName && defined(localState.uniformLocations[uniformName]) && u.vals) { - gl[u.funcName](localState.uniformLocations[uniformName], ...u.vals); + for (let uniform in localState.uniforms) { + applyUniform(localState.uniforms[uniform], uniform); } - }; - - for (let uniform in globalState.uniforms) { - applyUniform(globalState.uniforms[uniform], uniform); - } - - for (let uniform in localState.uniforms) { - applyUniform(localState.uniforms[uniform], uniform); - } - for (var attrib in localState.attributes) { - var a = localState.attributes[attrib]; - for (var cmd in a.cmds) { - var c = a.cmds[cmd]; - gl[c.funcName](...c.vals); + for (var attrib in localState.attributes) { + var a = localState.attributes[attrib]; + for (var cmd in a.cmds) { + var c = a.cmds[cmd]; + gl[c.funcName](...c.vals); + } } } -} -function disableState(gl, globalState, localState) { - for (var attrib in localState.attributes) { - // do something. - gl.disableVertexAttribArray(localState.attributes[attrib].a_attribute); + disableState(gl) { + var localState = this.localState; + for (var attrib in localState.attributes) { + // do something. + gl.disableVertexAttribArray(localState.attributes[attrib].a_attribute); + } } } From e0597f52d578a0bd5fc53f460e15d958fa7482b2 Mon Sep 17 00:00:00 2001 From: Ed Mackey Date: Fri, 14 Jul 2017 17:33:38 -0400 Subject: [PATCH 8/8] Reduce code duplication in texture lookup. --- mesh.js | 53 ++++++++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/mesh.js b/mesh.js index fc178acd..365e98f6 100644 --- a/mesh.js +++ b/mesh.js @@ -202,6 +202,19 @@ class Mesh { this.scene.drawScene(gl); } + getImageInfo(gl, gltf, textureIndex, funcName, uniformName, colorSpace) { + var textureInfo = gltf.textures[textureIndex]; + var uri = this.modelPath + gltf.images[textureInfo.source].uri; + var samplerIndex = this.scene.getNextSamplerIndex(); + this.localState.uniforms[uniformName] = { 'funcName': funcName, 'vals': [samplerIndex] }; + + return { + 'uri': uri, + 'samplerIndex': samplerIndex, + 'colorSpace': colorSpace + }; + } + initTextures(gl, gltf) { var imageInfos = {}; var pbrMat = this.material ? this.material.pbrMetallicRoughness : null; @@ -214,11 +227,7 @@ class Mesh { vals: baseColorFactor }; if (pbrMat && pbrMat.baseColorTexture && gltf.textures.length > pbrMat.baseColorTexture.index) { - var baseColorTexInfo = gltf.textures[pbrMat.baseColorTexture.index]; - var baseColorSrc = this.modelPath + gltf.images[baseColorTexInfo.source].uri; - samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['baseColor'] = { 'uri': baseColorSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.localState.uniforms['u_BaseColorSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + imageInfos['baseColor'] = this.getImageInfo(gl, gltf, pbrMat.baseColorTexture.index, 'uniform1i', 'u_BaseColorSampler', this.sRGBifAvailable); this.defines.HAS_BASECOLORMAP = 1; } else if (this.localState.uniforms['u_BaseColorSampler']) { @@ -233,31 +242,17 @@ class Mesh { vals: [metallic, roughness] }; if (pbrMat && pbrMat.metallicRoughnessTexture && gltf.textures.length > pbrMat.metallicRoughnessTexture.index) { - var mrTexInfo = gltf.textures[pbrMat.metallicRoughnessTexture.index]; - var mrSrc = this.modelPath + gltf.images[mrTexInfo.source].uri; - // gltf.samplers[mrTexInfo.sampler].magFilter etc - samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['metalRoughness'] = { 'uri': mrSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.localState.uniforms['u_MetallicRoughnessSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + imageInfos['metalRoughness'] = this.getImageInfo(gl, gltf, pbrMat.metallicRoughnessTexture.index, 'uniform1i', 'u_MetallicRoughnessSampler', gl.RGBA); this.defines.HAS_METALROUGHNESSMAP = 1; } - else { - if (this.localState.uniforms['u_MetallicRoughnessSampler']) { - delete this.localState.uniforms['u_MetallicRoughnessSampler']; - } + else if (this.localState.uniforms['u_MetallicRoughnessSampler']) { + delete this.localState.uniforms['u_MetallicRoughnessSampler']; } // Normals if (this.material && this.material.normalTexture && gltf.textures.length > this.material.normalTexture.index) { + imageInfos['normal'] = this.getImageInfo(gl, gltf, this.material.normalTexture.index, 'uniform1i', 'u_NormalSampler', gl.RGBA); var normalScale = defined(this.material.normalTexture.scale) ? this.material.normalTexture.scale : 1.0; - var normalsTexInfo = gltf.textures[this.material.normalTexture.index]; - var normalsSrc = this.modelPath + gltf.images[normalsTexInfo.source].uri; - samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['normal'] = { 'uri': normalsSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.localState.uniforms['u_NormalSampler'] = { - funcName: 'uniform1i', - vals: [samplerIndex] - }; this.localState.uniforms['u_NormalScale'] = { 'funcName': 'uniform1f', 'vals': [normalScale] }; this.defines.HAS_NORMALMAP = 1; } @@ -273,11 +268,7 @@ class Mesh { // Emissive if (this.material && this.material.emissiveTexture) { - var emissiveTexInfo = gltf.textures[this.material.emissiveTexture.index]; - var emissiveSrc = this.modelPath + gltf.images[emissiveTexInfo.source].uri; - samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['emissive'] = { 'uri': emissiveSrc, 'samplerIndex': samplerIndex, 'colorSpace': this.sRGBifAvailable }; // colorSpace, samplerindex, uri - this.localState.uniforms['u_EmissiveSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; + imageInfos['emissive'] = this.getImageInfo(gl, gltf, this.material.emissiveTexture.index, 'uniform1i', 'u_EmissiveSampler', this.sRGBifAvailable); this.defines.HAS_EMISSIVEMAP = 1; var emissiveFactor = defined(this.material.emissiveFactor) ? this.material.emissiveFactor : [0.0, 0.0, 0.0]; this.localState.uniforms['u_EmissiveFactor'] = { @@ -291,12 +282,8 @@ class Mesh { // AO if (this.material && this.material.occlusionTexture) { + imageInfos['occlusion'] = this.getImageInfo(gl, gltf, this.material.occlusionTexture.index, 'uniform1i', 'u_OcclusionSampler', gl.RGBA); var occlusionStrength = defined(this.material.occlusionTexture.strength) ? this.material.occlusionTexture.strength : 1.0; - var occlusionTexInfo = gltf.textures[this.material.occlusionTexture.index]; - var occlusionSrc = this.modelPath + gltf.images[occlusionTexInfo.source].uri; - samplerIndex = this.scene.getNextSamplerIndex(); - imageInfos['occlusion'] = { 'uri': occlusionSrc, 'samplerIndex': samplerIndex, 'colorSpace': gl.RGBA }; - this.localState.uniforms['u_OcclusionSampler'] = { 'funcName': 'uniform1i', 'vals': [samplerIndex] }; this.localState.uniforms['u_OcclusionStrength'] = { 'funcName': 'uniform1f', 'vals': [occlusionStrength] }; this.defines.HAS_OCCLUSIONMAP = 1; }