From 3415df313d42dc7e23dd33c7e93656e715e9b91a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 27 Sep 2015 23:56:03 -0400 Subject: [PATCH 01/27] Added Model instancing --- .../development/3D Models Instancing.html | 156 +++++++ .../development/3D Models Instancing.jpg | Bin 0 -> 108168 bytes Source/Renderer/Buffer.js | 10 +- Source/Renderer/ShaderProgram.js | 4 +- Source/Renderer/ShaderSource.js | 43 +- Source/Scene/GroundPrimitive.js | 2 +- Source/Scene/Model.js | 303 +++++++++++-- Source/Scene/ModelInstanceCollection.js | 426 ++++++++++++++++++ Source/Scene/Primitive.js | 16 +- 9 files changed, 899 insertions(+), 61 deletions(-) create mode 100644 Apps/Sandcastle/gallery/development/3D Models Instancing.html create mode 100644 Apps/Sandcastle/gallery/development/3D Models Instancing.jpg create mode 100644 Source/Scene/ModelInstanceCollection.js diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html new file mode 100644 index 000000000000..2cb6e4fa3721 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -0,0 +1,156 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.jpg b/Apps/Sandcastle/gallery/development/3D Models Instancing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ca19bf3a5e7dcf6e2004f632fc3e0ce621b97327 GIT binary patch literal 108168 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

oj_!zN2GDQN>s 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 literal 0 HcmV?d00001 diff --git a/Source/Renderer/Buffer.js b/Source/Renderer/Buffer.js index 43c529a374b6..c94d9d1f38d4 100644 --- a/Source/Renderer/Buffer.js +++ b/Source/Renderer/Buffer.js @@ -6,7 +6,8 @@ define([ '../Core/destroyObject', '../Core/DeveloperError', '../Core/IndexDatatype', - './BufferUsage' + './BufferUsage', + './WebGLConstants' ], function( defaultValue, defined, @@ -14,7 +15,8 @@ define([ destroyObject, DeveloperError, IndexDatatype, - BufferUsage) { + BufferUsage, + WebGLConstants) { "use strict"; /** @@ -123,7 +125,7 @@ define([ return new Buffer({ context: options.context, - bufferTarget: options.context._gl.ARRAY_BUFFER, + bufferTarget: WebGLConstants.ARRAY_BUFFER, typedArray: options.typedArray, sizeInBytes: options.sizeInBytes, usage: options.usage @@ -195,7 +197,7 @@ define([ var bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype); var buffer = new Buffer({ context : context, - bufferTarget : context._gl.ELEMENT_ARRAY_BUFFER, + bufferTarget : WebGLConstants.ELEMENT_ARRAY_BUFFER, typedArray : options.typedArray, sizeInBytes : options.sizeInBytes, usage : options.usage diff --git a/Source/Renderer/ShaderProgram.js b/Source/Renderer/ShaderProgram.js index bb5d450e7cfb..5205075de8c7 100644 --- a/Source/Renderer/ShaderProgram.js +++ b/Source/Renderer/ShaderProgram.js @@ -167,9 +167,9 @@ define([ for (j = 0; j < fragmentUniformsCount; j++) { if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) { uniformName = vertexShaderUniforms[i]; - duplicateName = "czm_mediump_" + uniformName; + duplicateName = 'czm_mediump_' + uniformName; // Update fragmentShaderText with renamed uniforms - var re = new RegExp(uniformName + "\\b", "g"); + var re = new RegExp(uniformName + '\\b', 'g'); fragmentShaderText = fragmentShaderText.replace(re, duplicateName); duplicateUniformNames[duplicateName] = uniformName; } diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js index fd1827ae2263..ba30f1d23b11 100644 --- a/Source/Renderer/ShaderSource.js +++ b/Source/Renderer/ShaderSource.js @@ -182,20 +182,13 @@ define([ return '\n'; }); + // Remove precision qualifier + combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, ''); + // Replace main() for picked if desired. var pickColorQualifier = shaderSource.pickColorQualifier; if (defined(pickColorQualifier)) { - combinedSources = combinedSources.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); - combinedSources += '\ -\n' + pickColorQualifier + ' vec4 czm_pickColor;\n\ -void main()\n\ -{\n\ - czm_old_main();\n\ - if (gl_FragColor.a == 0.0) {\n\ - discard;\n\ - }\n\ - gl_FragColor = czm_pickColor;\n\ -}'; + combinedSources = ShaderSource.createPickFragmentShaderSource(combinedSources, pickColorQualifier); } // combine into single string @@ -331,5 +324,33 @@ void main()\n\ } } + ShaderSource.createPickVertexShaderSource = function(vertexShaderSource) { + var renamedVS = vertexShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); + var pickMain = 'attribute vec4 pickColor; \n' + + 'varying vec4 czm_pickColor; \n' + + 'void main() \n' + + '{ \n' + + ' czm_old_main(); \n' + + ' czm_pickColor = pickColor; \n' + + '}'; + + return renamedVS + '\n' + pickMain; + }; + + ShaderSource.createPickFragmentShaderSource = function(fragmentShaderSource, pickColorQualifier) { + var renamedFS = fragmentShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); + var pickMain = pickColorQualifier + ' vec4 czm_pickColor; \n' + + 'void main() \n' + + '{ \n' + + ' czm_old_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = czm_pickColor; \n' + + '}'; + + return renamedFS + '\n' + pickMain; + }; + return ShaderSource; }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index b0c35a2beb11..79fefeaa527d 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -532,7 +532,7 @@ define([ primitive._spPick = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._spPick, - vertexShaderSource : Primitive._createPickVertexShaderSource(vs), + vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), fragmentShaderSource : pickFS, attributeLocations : attributeLocations }); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index f36b549eb38c..0eb0e4236e09 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -186,6 +186,9 @@ define([ function setCachedGltf(model, cachedGltf) { model._cachedGltf = cachedGltf; model._animationIds = getAnimationIds(cachedGltf); + if (model._instanced) { + model._perInstanceUniforms = getPerInstanceUniforms(cachedGltf); + } } // glTF JSON can be big given embedded geometry, textures, and animations, so we @@ -250,6 +253,43 @@ define([ return animationIds; } + function getPerInstanceUniforms(cachedGltf) { + // Find all the uniforms that are updated per instance. Usually these are uniforms that include a model + // matrix. They will be changed to take an instanced vertex attribute instead. + var perInstanceUniforms = {}; + if (defined(cachedGltf) && defined(cachedGltf.gltf)) { + var techniques = cachedGltf.gltf.techniques; + for (var techniqueName in techniques) { + if (techniques.hasOwnProperty(techniqueName)) { + var technique = techniques[techniqueName]; + var parameters = technique.parameters; + var pass = technique.passes[technique.pass]; + var instanceProgram = pass.instanceProgram; + var programName = instanceProgram.program; + // Different techniques may share the same program, skip if already processed. + // This assumes techniques that share a program do not declare different semantics for the same uniforms. + if (!defined(perInstanceUniforms[programName])) { + var uniformMap = {}; + perInstanceUniforms[programName] = uniformMap; + var uniforms = instanceProgram.uniforms; + for (var uniformName in uniforms) { + if (uniforms.hasOwnProperty(uniformName)) { + var parameterName = uniforms[uniformName]; + var parameter = parameters[parameterName]; + var semantic = parameter.semantic; + if (defined(semantic) && (semantic.indexOf('MODEL') > -1)) { + uniformMap[uniformName] = semantic; + } + } + } + } + } + } + } + + return perInstanceUniforms; + } + var gltfCache = {}; /////////////////////////////////////////////////////////////////////////// @@ -287,6 +327,8 @@ define([ * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. + * @param {Boolean} [options.instanced=false] Whether the model will be instanced. + * @param {Object} [options.instancedAttributes] Instanced attributes for the vertex array. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @@ -305,6 +347,10 @@ define([ this._cachedGltf = undefined; this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); this._animationIds = undefined; + this._perInstanceUniforms = undefined; + this._instanced = defaultValue(options.instanced, false); + this._instancedAttributes = options.instancedAttributes; + var cachedGltf; if (defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready) { @@ -490,7 +536,6 @@ define([ programs : {}, pickPrograms : {}, textures : {}, - samplers : {}, renderStates : {} }; @@ -789,6 +834,7 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url to the .gltf file. + * @param {String} [options.cacheKey] The cache key for this model. * @param {Object} [options.headers] HTTP headers to send with the request. * @param {Boolean} [options.show=true] Determines if the model primitive will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. @@ -796,6 +842,8 @@ define([ * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom. * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. + * @param {Boolean} [options.instanced=false] Whether the model will be instanced. + * @param {Object} [options.instancedAttributes] Instanced attributes for the vertex array. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each {@link DrawCommand} in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @returns {Model} The newly created model. @@ -842,6 +890,12 @@ define([ // different relative paths could point to the same model. var cacheKey = defaultValue(options.cacheKey, getAbsoluteURL(url)); + // Instanced models will create different renderer resources, so change the cache key. + var instanced = defaultValue(options.instanced, false); + if (instanced) { + cacheKey = cacheKey + '(instanced)'; + } + options = clone(options); options.basePath = getBasePath(url); options.cacheKey = cacheKey; @@ -1355,15 +1409,137 @@ define([ return getStringFromTypedArray(loadResources.getBuffer(bufferView)); } - function createProgram(name, model, context) { + function createVertexShaderInstanced(name, shaderSource, model) { + var perInstanceUniforms = model._perInstanceUniforms[name]; + if (perInstanceUniforms.length === 0) { + return shaderSource; + } + + // All per-instance uniforms will be replaced with global variables. + var globalDeclarations = ''; + var globalDefinitions = ''; + var hasModelViewMatrix = false; + var regex; + + // Add instanced attributes + var instancedAttributes = model._instancedAttributes; + for (var attrName in instancedAttributes) { + if (instancedAttributes.hasOwnProperty(attrName)) { + globalDeclarations += 'attribute vec4 ' + attrName + ';\n'; + } + } + + // Add node transform uniform, the gltf node's offset which is the same for every instance + globalDeclarations += 'uniform mat4 czm_instanced_nodeTransform;\n'; + + // Create model matrix from the attributes + globalDeclarations += 'mat4 czm_instanced_model;\n'; + globalDefinitions += 'czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; + globalDefinitions += 'czm_instanced_model = czm_instanced_model * czm_instanced_nodeTransform;\n'; + + for (var uniform in perInstanceUniforms) { + if (perInstanceUniforms.hasOwnProperty(uniform)) { + var semantic = perInstanceUniforms[uniform]; + var globalVarName; + + // The modelViewInverseTranspose matrix (aka normal matrix) almost always appears alongside a modelView + // matrix, so re-use the pre-existing modelView matrix. Since we don't know which uniform will appear + // first in the loop check to make sure the modelView matrix is not added twice. + + if (semantic === 'MODEL') { + // Definition and declaration are already added + globalVarName = 'czm_instanced_model'; + } else if (semantic === 'MODELVIEW') { + globalVarName = 'czm_instanced_modelView'; + if (!hasModelViewMatrix) { + globalDeclarations += 'mat4 czm_instanced_modelView;\n'; + globalDefinitions += 'czm_instanced_modelView = czm_view * czm_instanced_model;\n'; + hasModelViewMatrix = true; + } + } else if (semantic === 'MODELVIEWPROJECTION') { + globalVarName = 'czm_instanced_modelViewProjection'; + globalDeclarations += 'mat4 czm_instanced_modelViewProjection;\n'; + globalDefinitions += 'czm_instanced_modelViewProjection = czm_viewProjection * czm_instanced_model;\n'; + } else if (semantic === 'MODELINVERSE') { + globalVarName = 'czm_instanced_inverseModel'; + globalDeclarations += 'mat4 czm_instanced_inverseModel;\n'; + globalDefinitions += 'czm_instanced_inverseModel = inverse(czm_instanced_model);\n'; + } else if (semantic === 'MODELVIEWINVERSE') { + globalVarName = 'czm_instanced_inverseModelView'; + globalDeclarations += 'mat4 czm_instanced_inverseModelView;\n'; + globalDefinitions += 'czm_instanced_inverseModelView = inverse(czm_instanced_model) * czm_inverseView;\n'; + } else if (semantic === 'MODELVIEWPROJECTIONINVERSE') { + globalVarName = 'czm_instanced_inverseModelViewProjection'; + globalDeclarations += 'mat4 czm_instanced_inverseModelViewProjection;\n'; + globalDefinitions += 'czm_instanced_inverseModelViewProjection = inverse(czm_instanced_model) * czm_inverseViewProjection;\n'; + } else if (semantic === 'MODELINVERSETRANSPOSE') { + // Non-uniform scale is not supported for instancing, so inverse-transpose matrix is simplified + globalVarName = 'czm_instanced_modelInverseTranspose'; + globalDeclarations += 'mat3 czm_instanced_modelInverseTranspose;\n'; + globalDefinitions += 'czm_instanced_modelInverseTranspose = mat3(czm_instanced_model);\n'; + } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { + // Non-uniform scale is not supported for instancing, so inverse-transpose matrix is simplified + if (!hasModelViewMatrix) { + globalDeclarations += 'mat4 czm_instanced_modelView;\n'; + globalDefinitions += 'czm_instanced_modelView = czm_view * czm_instanced_model;\n'; + hasModelViewMatrix = true; + } + globalVarName = 'czm_instanced_modelViewInverseTranspose'; + globalDeclarations += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; + globalDefinitions += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; + } + + // Remove the uniform declaration + regex = new RegExp('uniform.*' + uniform + '.*'); + shaderSource = shaderSource.replace(regex, ''); + + // Replace all occurrences of the uniform with the global variable + regex = new RegExp(uniform + '\\b', 'g'); + shaderSource = shaderSource.replace(regex, globalVarName); + } + } + + // Place definitions into the main function + shaderSource = shaderSource.replace(/void\s+main\s*\([\s\S]*?{/, 'void main() {\n' + globalDefinitions); + + // Append declarations to the top of the shader + shaderSource = globalDeclarations + shaderSource; + + return shaderSource; + } + + function createPickVertexShaderInstanced(shaderSource) { + var pickSource = + 'varying vec4 czm_pickColor;\n' + + 'void main() {\n' + + 'vec4 pickColor = czm_instanceData;\n' + + 'czm_pickColor = pickColor;\n'; + return shaderSource.replace(/void\s+main\s*\([\s\S]*?{/, 'void main() {\n' + pickSource); + } + + function createProgramInstanced(name, model, context) { var programs = model.gltf.programs; var shaders = model._loadResources.shaders; var program = programs[name]; - var attributeLocations = createAttributeLocations(program.attributes); var vs = getShaderSource(model, shaders[program.vertexShader]); var fs = getShaderSource(model, shaders[program.fragmentShader]); + // Assume the fragment shader does not need to be modified for instancing + vs = createVertexShaderInstanced(name, vs, model); + + var attributeLocations = createAttributeLocations(program.attributes); + var attributeCount = program.attributes.length; + + var instancedAttributes = model._instancedAttributes; + for (var attrName in instancedAttributes) { + if (instancedAttributes.hasOwnProperty(attrName)) { + attributeLocations[attrName] = attributeCount; + instancedAttributes[attrName].index = attributeCount; + attributeCount++; + } + } + model._rendererResources.programs[name] = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, @@ -1373,10 +1549,38 @@ define([ if (model.allowPicking) { // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 - var pickFS = new ShaderSource({ - sources : [fs], - pickColorQualifier : 'uniform' + var pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'varying'); + var pickVS = createPickVertexShaderInstanced(vs); + + model._rendererResources.pickPrograms[name] = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations }); + } + } + + function createProgram(name, model, context) { + var programs = model.gltf.programs; + var shaders = model._loadResources.shaders; + var program = programs[name]; + + var vs = getShaderSource(model, shaders[program.vertexShader]); + var fs = getShaderSource(model, shaders[program.fragmentShader]); + + var attributeLocations = createAttributeLocations(program.attributes); + + model._rendererResources.programs[name] = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + + if (model.allowPicking) { + // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 + var pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); model._rendererResources.pickPrograms[name] = ShaderProgram.fromCache({ context : context, @@ -1401,17 +1605,19 @@ define([ return; } + var createProgramFunction = model._instanced ? createProgramInstanced : createProgram; + if (model.asynchronous) { // Create one program per frame if (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); - createProgram(name, model, context); + createProgramFunction(name, model, context); } } else { // Create all loaded programs this frame while (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); - createProgram(name, model, context); + createProgramFunction(name, model, context); } } } @@ -1563,8 +1769,12 @@ define([ // Note: WebGL shader compiler may have optimized and removed some attributes from programAttributeLocations for (var location in programAttributeLocations){ if (programAttributeLocations.hasOwnProperty(location)) { - var parameter = parameters[attributes[location]]; - attributeLocations[parameter.semantic] = programAttributeLocations[location].index; + var attribute = attributes[location]; + // Ignore attributes that are added dynamically (like for instancing) + if (defined(attribute)) { + var parameter = parameters[attribute]; + attributeLocations[parameter.semantic] = programAttributeLocations[location].index; + } } } @@ -1670,12 +1880,11 @@ define([ function getChannelEvaluator(model, runtimeNode, targetPath, spline) { return function(localAnimationTime) { -// Workaround for https://github.com/KhronosGroup/glTF/issues/219 -/* - if (targetPath === 'translation') { - return; - } -*/ + // Workaround for https://github.com/KhronosGroup/glTF/issues/219 + + //if (targetPath === 'translation') { + // return; + //} runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]); runtimeNode.dirtyNumber = model._maxDirtyNumber; }; @@ -1779,9 +1988,10 @@ define([ // https://github.com/KhronosGroup/glTF/issues/258 var attributeLocations = getAttributeLocations(model, primitive); + var attrName; var attrs = []; var primitiveAttributes = primitive.attributes; - for (var attrName in primitiveAttributes) { + for (attrName in primitiveAttributes) { if (primitiveAttributes.hasOwnProperty(attrName)) { var attributeLocation = attributeLocations[attrName]; // Skip if the attribute is not used by the material, e.g., because the asset was exported @@ -1801,12 +2011,22 @@ define([ } } + if (model._instanced) { + var instancedAttributes = model._instancedAttributes; + for (attrName in instancedAttributes) { + if (instancedAttributes.hasOwnProperty(attrName)) { + attrs.push(instancedAttributes[attrName]); + } + } + } + + var vertexArrayName = meshName + '.primitive.' + i; var accessor = accessors[primitive.indices]; var indexBuffer = rendererBuffers[accessor.bufferView]; - rendererVertexArrays[meshName + '.primitive.' + i] = new VertexArray({ - context : context, - attributes : attrs, - indexBuffer : indexBuffer + rendererVertexArrays[vertexArrayName] = new VertexArray({ + context: context, + attributes: attrs, + indexBuffer: indexBuffer }); } } @@ -1992,7 +2212,7 @@ define([ return uniformState.viewportCartesian4; }; } - // JOINTMATRIX created in createCommands() + // JOINTMATRIX created in createCommand() }; /////////////////////////////////////////////////////////////////////////// @@ -2147,6 +2367,10 @@ define([ var pass = technique.passes[technique.pass]; var instanceProgram = pass.instanceProgram; var uniforms = instanceProgram.uniforms; + var perInstanceUniforms; + if (model._instanced) { + perInstanceUniforms = model._perInstanceUniforms[instanceProgram.program]; + } var uniformMap = {}; var uniformValues = {}; @@ -2169,7 +2393,9 @@ define([ // // https://github.com/KhronosGroup/glTF/issues/142 - if (defined(instanceParameters[parameterName])) { + if (model._instanced && defined(perInstanceUniforms[name])) { + // This uniform will be replaced with an instanced vertex attribute, don't include in uniform map. + } else if (defined(instanceParameters[parameterName])) { // Parameter overrides by the instance technique var uv = gltfUniformFunctions[parameter.type](instanceParameters[parameterName], model); uniformMap[name] = uv.func; @@ -2214,6 +2440,12 @@ define([ }; } + function createNodeTransformFunction(runtimeNode, i) { + return function() { + return runtimeNode.commands[i].command.modelMatrix; + }; + } + function createCommand(model, gltfNode, runtimeNode, context) { var nodeCommands = model._nodeCommands; var pickIds = model._pickIds; @@ -2263,7 +2495,8 @@ define([ boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(a.min), Cartesian3.fromArray(a.max)); } - var vertexArray = rendererVertexArrays[name + '.primitive.' + i]; + var vertexArrayName = name + '.primitive.' + i; + var vertexArray = rendererVertexArrays[vertexArrayName]; var count = ix.count; var offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices @@ -2276,6 +2509,12 @@ define([ uniformMap = combine(uniformMap, jointUniformMap); } + if (model._instanced) { + uniformMap = combine(uniformMap, { + czm_instanced_nodeTransform : createNodeTransformFunction(runtimeNode, i) + }); + } + var rs = rendererRenderStates[instanceTechnique.technique]; // GLTF_SPEC: Offical means to determine translucency. https://github.com/KhronosGroup/glTF/issues/105 var isTranslucent = rs.blending.enabled; @@ -2306,10 +2545,13 @@ define([ var pickId = context.createPickId(owner); pickIds.push(pickId); - var pickUniformMap = combine( - uniformMap, { + var pickUniformMap = uniformMap; + + if (!model._instanced) { + pickUniformMap = combine(uniformMap, { czm_pickColor : createPickColorFunction(pickId.color) }); + } pickCommand = new DrawCommand({ boundingVolume : new BoundingSphere(), // updated in update() @@ -2502,7 +2744,7 @@ define([ var command = primitiveCommand.command; Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, command.modelMatrix); - // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (inclug animation) + // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation) BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume); if (defined(model._rtcCenter)) { @@ -2924,6 +3166,11 @@ define([ return; } + // ModelInstanceCollection handles the model's command list directly + if (this._instanced) { + return; + } + // We don't check show at the top of the function since we // want to be able to progressively load models when they are not shown, // and then have them visible immediately when show is set to true. @@ -2944,7 +3191,7 @@ define([ } } - if (passes.pick) { + if (passes.pick && this.allowPicking) { for (i = 0; i < length; ++i) { nc = nodeCommands[i]; if (nc.show) { diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js new file mode 100644 index 000000000000..ae0e06e91dc4 --- /dev/null +++ b/Source/Scene/ModelInstanceCollection.js @@ -0,0 +1,426 @@ +/*global define*/ +define([ + '../Core/BoundingSphere', + '../Core/Cartesian3', + '../Core/clone', + '../Core/Color', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/destroyObject', + '../Core/Matrix4', + '../Renderer/Buffer', + '../Renderer/DrawCommand', + '../Renderer/WebGLConstants', + '../ThirdParty/when', + './Model', + './SceneMode' + ], function( + BoundingSphere, + Cartesian3, + clone, + Color, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + DeveloperError, + destroyObject, + Matrix4, + Buffer, + DrawCommand, + WebGLConstants, + when, + Model, + SceneMode) { + "use strict"; + + var LoadState = { + NEEDS_LOAD : 0, + LOADING : 1, + LOADED : 2, + FAILED : 3 + }; + + /** + * A 3D model instance collection. All instances reference the same underlying model, but have unique + * per-instance properties like model matrix, pick id, etc. + * + * @alias ModelInstanceCollection + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url to the .gltf file. + * @param {String} [options.cacheKey] The cache key for the model. + * @param {Object} [options.headers] HTTP headers to send with the request. + * @param {Boolean} [options.show=true] Determines if the collection will be shown. + * @param {Boolean} [options.allowPicking=false] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. + * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. + * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. + * + * @see Model + * @see Model.fromGltf + */ + + var ModelInstanceCollection = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + //>>includeStart('debug', pragmas.debug); + if (!defined(options.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + this._model = undefined; + this._boundingSphere = undefined; + this._instancingEnabled = false; + this._instances = defaultValue(options.instances, []); + this._show = options.show; + this._allowPicking = defaultValue(options.allowPicking, false); + this._ready = false; + this._readyPromise = when.defer(); + this._state = LoadState.NEEDS_LOAD; + this._drawCommands = []; + this._pickCommands = []; + this._modelCommands = undefined; + + // Passed on to Model + this._url = options.url; + this._cacheKey = options.cacheKey; + this._headers = options.headers; + this._asynchronous = options.asynchronous; + this._debugShowBoundingVolume = options.debugShowBoundingVolume; + this._debugWireframe = options.debugWireframe; + }; + + defineProperties(ModelInstanceCollection.prototype, { + model : { + get : function() { + return this._model; + } + }, + boundingSphere : { + get : function() { + return this._boundingSphere; + } + }, + allowPicking : { + get : function() { + return this._allowPicking; + } + }, + instanceCount : { + get : function() { + return this._instances.length; + } + }, + ready : { + get : function() { + return this._ready; + } + }, + readyPromise : { + get : function() { + return this._readyPromise; + } + } + }); + + function createModel(context, frameState, commandList, collection) { + var i; + var instanceCount = collection.instanceCount; + var modelOptions = { + url : collection._url, + cacheKey : collection._cacheKey, + headers : collection._headers, + asynchronous : collection._asynchronous, + debugShowBoundingVolume : collection._debugShowBoundingVolume, + debugWireframe : collection._debugWireframe, + allowPicking : collection._allowPicking + }; + + if (collection._instancingEnabled) { + var typedArray = new Float32Array(instanceCount * 16); + for (i = 0; i < instanceCount; ++i) { + var instance = collection._instances[i]; + var modelMatrix = instance.modelMatrix; + var pickColor = defined(instance.pickId) ? instance.pickId.color : Color.WHITE; + + // First three rows of the model matrix + typedArray[i * 16 + 0] = modelMatrix[0]; + typedArray[i * 16 + 1] = modelMatrix[4]; + typedArray[i * 16 + 2] = modelMatrix[8]; + typedArray[i * 16 + 3] = modelMatrix[12]; + typedArray[i * 16 + 4] = modelMatrix[1]; + typedArray[i * 16 + 5] = modelMatrix[5]; + typedArray[i * 16 + 6] = modelMatrix[9]; + typedArray[i * 16 + 7] = modelMatrix[13]; + typedArray[i * 16 + 8] = modelMatrix[2]; + typedArray[i * 16 + 9] = modelMatrix[6]; + typedArray[i * 16 + 10] = modelMatrix[10]; + typedArray[i * 16 + 11] = modelMatrix[14]; + + // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. + typedArray[i * 16 + 12] = pickColor.red; + typedArray[i * 16 + 13] = pickColor.green; + typedArray[i * 16 + 14] = pickColor.blue; + typedArray[i * 16 + 15] = pickColor.alpha; + } + + var vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : typedArray, + usage : WebGLConstants.STATIC_DRAW + }); + + var attributeNames = ['czm_modelMatrixRow0', 'czm_modelMatrixRow1', 'czm_modelMatrixRow2', 'czm_instanceData']; + var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT); + var instancedAttributes = {}; + + for (i = 0; i < 4; ++i) { + instancedAttributes[attributeNames[i]] = { + index : 0, // updated in Model + vertexBuffer : vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 4 * i, + strideInBytes : componentSizeInBytes * 16, + instanceDivisor : 1 + }; + } + + modelOptions.instanced = true; + modelOptions.instancedAttributes = instancedAttributes; + } + + collection._model = Model.fromGltf(modelOptions); + } + + function computeBoundingSphere(collection) { + var points = []; + var instanceCount = collection.instanceCount; + for (var i = 0; i < instanceCount; i++) { + var translation = new Cartesian3(); + Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); + points.push(translation); + } + + var boundingSphere = BoundingSphere.fromPoints(points); + boundingSphere.radius += collection.model._boundingSphere.radius; + return boundingSphere; + } + + function createCommands(collection, drawCommands, pickCommands) { + collection._modelCommands = drawCommands; + + var i, j; + var command; + var commandCount = drawCommands.length; + var instanceCount = collection.instanceCount; + var allowPicking = collection.allowPicking; + + var boundingSphere = computeBoundingSphere(collection); + var modelMatrix = Matrix4.fromTranslation(boundingSphere.center); + collection._boundingSphere = boundingSphere; + + if (collection._instancingEnabled) { + for (i = 0; i < commandCount; ++i) { + command = clone(drawCommands[i]); + command.instanceCount = instanceCount; + command.modelMatrix = modelMatrix; + command.boundingVolume = boundingSphere; + collection._drawCommands.push(command); + + if (allowPicking) { + command = clone(pickCommands[i]); + command.instanceCount = instanceCount; + command.modelMatrix = modelMatrix; + command.boundingVolume = boundingSphere; + collection._pickCommands.push(command); + } + } + } else { + // When instancing is disabled, create commands for every instance. + for (i = 0; i < commandCount; ++i) { + for (j = 0; j < instanceCount; ++j) { + command = clone(drawCommands[i]); + command.modelMatrix = new Matrix4(); + command.boundingVolume = new BoundingSphere(); + collection._drawCommands.push(command); + + if (allowPicking) { + command = clone(pickCommands[i]); + command.modelMatrix = new Matrix4(); + command.boundingVolume = new BoundingSphere(); + collection._pickCommands.push(command); + } + } + } + } + } + + function updateCommands(collection) { + // Only applies when instancing is disabled. The instanced shader handles node transformations. + if (collection._instancingEnabled) { + return; + } + + var i, j; + var modelCommands = collection._modelCommands; + var commandCount = modelCommands.length; + var instanceCount = collection.instanceCount; + var allowPicking = collection.allowPicking; + + for (i = 0; i < commandCount; ++i) { + var command = modelCommands[i]; + for (j = 0; j < instanceCount; ++j) { + var commandIndex = i*instanceCount+j; + var drawCommand = collection._drawCommands[commandIndex]; + var instanceMatrix = collection._instances[j].modelMatrix; + var nodeMatrix = command.modelMatrix; + var modelMatrix = drawCommand.modelMatrix; + Matrix4.multiplyTransformation(instanceMatrix, nodeMatrix, modelMatrix); + + var nodeBoundingSphere = command.boundingVolume; + var boundingSphere = drawCommand.boundingVolume; + BoundingSphere.transform(nodeBoundingSphere, instanceMatrix, boundingSphere); + + if (allowPicking) { + var pickCommand = collection._pickCommands[commandIndex]; + Matrix4.clone(modelMatrix, pickCommand.modelMatrix); + BoundingSphere.clone(boundingSphere, pickCommand.boundingVolume); + } + } + } + } + + function getModelCommands(model) { + var nodeCommands = model._nodeCommands; + var length = nodeCommands.length; + var i; + var nc; + + var drawCommands = []; + var pickCommands = []; + + for (i = 0; i < length; ++i) { + nc = nodeCommands[i]; + if (nc.show) { + drawCommands.push(nc.command); + pickCommands.push(nc.pickCommand); + } + } + + return { + draw: drawCommands, + pick: pickCommands + }; + } + + ModelInstanceCollection.prototype.update = function(context, frameState, commandList) { + if (frameState.mode !== SceneMode.SCENE3D) { + return; + } + + if (this._show === false) { + return; + } + + if (this.instanceCount === 0) { + return; + } + + this._instancingEnabled = context.instancedArrays; + + if (this._state === LoadState.NEEDS_LOAD) { + this._state = LoadState.LOADING; + createModel(context, frameState, commandList, this); + } + + var model = this._model; + model.update(context, frameState, []); + + if (model._ready && (this._state === LoadState.LOADING)) { + this._state = LoadState.LOADED; + this._ready = true; + + var modelCommands = getModelCommands(model); + createCommands(this, modelCommands.draw, modelCommands.pick); + + this._readyPromise.resolve(this); + return; + } + + if (this._state !== LoadState.LOADED) { + return; + } + + // If any node changes due to an animation, update the commands. This could be inefficient if the model is + // composed of many nodes and only one changes, however it is probably fine in the general use case. + if (model._maxDirtyNumber > 0) { + updateCommands(this); + } + + var passes = frameState.passes; + var commands; + var commandCount; + var i; + + if (passes.render) { + commands = this._drawCommands; + commandCount = commands.length; + for (i = 0; i < commandCount; i++) { + commandList.push(commands[i]); + } + } else if (passes.pick) { + commands = this._pickCommands; + commandCount = commands.length; + for (i = 0; i < commandCount; i++) { + commandList.push(commands[i]); + } + } + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see ModelInstanceCollection#destroy + */ + ModelInstanceCollection.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @see ModelInstanceCollection#isDestroyed + * + * @example + * instanceCollection = instanceCollection && instanceCollection.destroy(); + */ + ModelInstanceCollection.prototype.destroy = function() { + this._model.destroy(); + + return destroyObject(this); + }; + + return ModelInstanceCollection; +}); diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 4ac444ecc958..52405dcf4c1e 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -554,20 +554,6 @@ define([ return [forwardDecl, attributes, vertexShaderSource, computeFunctions].join('\n'); }; - Primitive._createPickVertexShaderSource = function(vertexShaderSource) { - var renamedVS = vertexShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); - var pickMain = - 'attribute vec4 pickColor; \n' + - 'varying vec4 czm_pickColor; \n' + - 'void main() \n' + - '{ \n' + - ' czm_old_main(); \n' + - ' czm_pickColor = pickColor; \n' + - '}'; - - return renamedVS + '\n' + pickMain; - }; - Primitive._appendShowToShader = function(primitive, vertexShaderSource) { if (!defined(primitive._attributeLocations.show)) { return vertexShaderSource; @@ -1065,7 +1051,7 @@ define([ primitive._pickSP = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._pickSP, - vertexShaderSource : Primitive._createPickVertexShaderSource(vs), + vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), fragmentShaderSource : pickFS, attributeLocations : attributeLocations }); From aecfb3c61680a38af66e9621b0d772c8990451eb Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Mon, 28 Sep 2015 14:57:42 -0400 Subject: [PATCH 02/27] Whitespace --- Apps/Sandcastle/gallery/development/3D Models Instancing.html | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index 2cb6e4fa3721..d45c4ec7beaa 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -35,7 +35,6 @@ var viewer = new Cesium.Viewer('cesiumContainer', { globe : globeVisible, skyAtmosphere : globeVisible - }); var scene = viewer.scene; From 37276daf69d34160567a942db55c0c6aedc1fabd Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Mon, 28 Sep 2015 15:11:45 -0400 Subject: [PATCH 03/27] Whitespace --- Source/Scene/Model.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 0eb0e4236e09..118f305da0b7 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -347,10 +347,10 @@ define([ this._cachedGltf = undefined; this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); this._animationIds = undefined; - this._perInstanceUniforms = undefined; + this._instanced = defaultValue(options.instanced, false); this._instancedAttributes = options.instancedAttributes; - + this._perInstanceUniforms = undefined; var cachedGltf; if (defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready) { From 5bab8d6540e6d810f2df66758b3ada206d2b07c8 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Mon, 28 Sep 2015 15:20:00 -0400 Subject: [PATCH 04/27] Whitespace --- Source/Scene/ModelInstanceCollection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index ae0e06e91dc4..2e2577fe5907 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -64,7 +64,6 @@ define([ * @see Model * @see Model.fromGltf */ - var ModelInstanceCollection = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); From a65a0386b3437dfa9cafefa34ec351df16164e05 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Mon, 28 Sep 2015 15:27:03 -0400 Subject: [PATCH 05/27] Tweak doc --- Source/Scene/ModelInstanceCollection.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 2e2577fe5907..39f0cbec62ff 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -60,9 +60,6 @@ define([ * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. - * - * @see Model - * @see Model.fromGltf */ var ModelInstanceCollection = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); From a5add4ec9c928924ca9b119b527caece92102168 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 28 Sep 2015 17:41:56 -0400 Subject: [PATCH 06/27] Fixed jittering by rendering relative to bounding volume --- Source/Scene/Model.js | 34 ++++-------- Source/Scene/ModelInstanceCollection.js | 72 +++++++++++++++++-------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 118f305da0b7..42e534e4f214 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -1431,35 +1431,27 @@ define([ // Add node transform uniform, the gltf node's offset which is the same for every instance globalDeclarations += 'uniform mat4 czm_instanced_nodeTransform;\n'; + globalDeclarations += 'uniform mat4 czm_instanced_boundingVolumeModelView;\n'; - // Create model matrix from the attributes + // Construct model view matrix globalDeclarations += 'mat4 czm_instanced_model;\n'; + globalDeclarations += 'mat4 czm_instanced_modelView;\n'; globalDefinitions += 'czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; - globalDefinitions += 'czm_instanced_model = czm_instanced_model * czm_instanced_nodeTransform;\n'; + globalDefinitions += 'czm_instanced_modelView = czm_instanced_boundingVolumeModelView * czm_instanced_model * czm_instanced_nodeTransform;\n'; for (var uniform in perInstanceUniforms) { if (perInstanceUniforms.hasOwnProperty(uniform)) { var semantic = perInstanceUniforms[uniform]; var globalVarName; - // The modelViewInverseTranspose matrix (aka normal matrix) almost always appears alongside a modelView - // matrix, so re-use the pre-existing modelView matrix. Since we don't know which uniform will appear - // first in the loop check to make sure the modelView matrix is not added twice. - if (semantic === 'MODEL') { - // Definition and declaration are already added globalVarName = 'czm_instanced_model'; } else if (semantic === 'MODELVIEW') { globalVarName = 'czm_instanced_modelView'; - if (!hasModelViewMatrix) { - globalDeclarations += 'mat4 czm_instanced_modelView;\n'; - globalDefinitions += 'czm_instanced_modelView = czm_view * czm_instanced_model;\n'; - hasModelViewMatrix = true; - } } else if (semantic === 'MODELVIEWPROJECTION') { globalVarName = 'czm_instanced_modelViewProjection'; globalDeclarations += 'mat4 czm_instanced_modelViewProjection;\n'; - globalDefinitions += 'czm_instanced_modelViewProjection = czm_viewProjection * czm_instanced_model;\n'; + globalDefinitions += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; } else if (semantic === 'MODELINVERSE') { globalVarName = 'czm_instanced_inverseModel'; globalDeclarations += 'mat4 czm_instanced_inverseModel;\n'; @@ -1467,23 +1459,17 @@ define([ } else if (semantic === 'MODELVIEWINVERSE') { globalVarName = 'czm_instanced_inverseModelView'; globalDeclarations += 'mat4 czm_instanced_inverseModelView;\n'; - globalDefinitions += 'czm_instanced_inverseModelView = inverse(czm_instanced_model) * czm_inverseView;\n'; + globalDefinitions += 'czm_instanced_inverseModelView = inverse(czm_instanced_modelView);\n'; } else if (semantic === 'MODELVIEWPROJECTIONINVERSE') { globalVarName = 'czm_instanced_inverseModelViewProjection'; globalDeclarations += 'mat4 czm_instanced_inverseModelViewProjection;\n'; - globalDefinitions += 'czm_instanced_inverseModelViewProjection = inverse(czm_instanced_model) * czm_inverseViewProjection;\n'; + globalDefinitions += 'czm_instanced_inverseModelViewProjection = inverse(czm_projection * czm_instanced_modelView);\n'; } else if (semantic === 'MODELINVERSETRANSPOSE') { // Non-uniform scale is not supported for instancing, so inverse-transpose matrix is simplified globalVarName = 'czm_instanced_modelInverseTranspose'; globalDeclarations += 'mat3 czm_instanced_modelInverseTranspose;\n'; globalDefinitions += 'czm_instanced_modelInverseTranspose = mat3(czm_instanced_model);\n'; } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { - // Non-uniform scale is not supported for instancing, so inverse-transpose matrix is simplified - if (!hasModelViewMatrix) { - globalDeclarations += 'mat4 czm_instanced_modelView;\n'; - globalDefinitions += 'czm_instanced_modelView = czm_view * czm_instanced_model;\n'; - hasModelViewMatrix = true; - } globalVarName = 'czm_instanced_modelViewInverseTranspose'; globalDeclarations += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; globalDefinitions += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; @@ -2545,9 +2531,11 @@ define([ var pickId = context.createPickId(owner); pickIds.push(pickId); - var pickUniformMap = uniformMap; + var pickUniformMap; - if (!model._instanced) { + if (model._instanced) { + pickUniformMap = clone(uniformMap); + } else { pickUniformMap = combine(uniformMap, { czm_pickColor : createPickColorFunction(pickId.color) }); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 39f0cbec62ff..6953fc7460f2 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -4,6 +4,7 @@ define([ '../Core/Cartesian3', '../Core/clone', '../Core/Color', + '../Core/combine', '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', @@ -22,6 +23,7 @@ define([ Cartesian3, clone, Color, + combine, ComponentDatatype, defaultValue, defined, @@ -72,6 +74,8 @@ define([ this._model = undefined; this._boundingSphere = undefined; + this._boundingSphereModel = undefined; + this._boundingSphereModelView = undefined; this._instancingEnabled = false; this._instances = defaultValue(options.instances, []); this._show = options.show; @@ -125,6 +129,21 @@ define([ } }); + function computeBoundingSphere(collection) { + var points = []; + var instanceCount = collection.instanceCount; + for (var i = 0; i < instanceCount; i++) { + var translation = new Cartesian3(); + Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); + points.push(translation); + } + + var boundingSphere = BoundingSphere.fromPoints(points); + collection._boundingSphere = boundingSphere; + collection._boundingSphereModel = Matrix4.fromTranslation(boundingSphere.center); + collection._boundingSphereModelView = new Matrix4(); + } + function createModel(context, frameState, commandList, collection) { var i; var instanceCount = collection.instanceCount; @@ -138,7 +157,10 @@ define([ allowPicking : collection._allowPicking }; + computeBoundingSphere(collection); + if (collection._instancingEnabled) { + var center = collection._boundingSphere.center; var typedArray = new Float32Array(instanceCount * 16); for (i = 0; i < instanceCount; ++i) { var instance = collection._instances[i]; @@ -149,15 +171,15 @@ define([ typedArray[i * 16 + 0] = modelMatrix[0]; typedArray[i * 16 + 1] = modelMatrix[4]; typedArray[i * 16 + 2] = modelMatrix[8]; - typedArray[i * 16 + 3] = modelMatrix[12]; + typedArray[i * 16 + 3] = modelMatrix[12] - center.x; typedArray[i * 16 + 4] = modelMatrix[1]; typedArray[i * 16 + 5] = modelMatrix[5]; typedArray[i * 16 + 6] = modelMatrix[9]; - typedArray[i * 16 + 7] = modelMatrix[13]; + typedArray[i * 16 + 7] = modelMatrix[13] - center.y; typedArray[i * 16 + 8] = modelMatrix[2]; typedArray[i * 16 + 9] = modelMatrix[6]; typedArray[i * 16 + 10] = modelMatrix[10]; - typedArray[i * 16 + 11] = modelMatrix[14]; + typedArray[i * 16 + 11] = modelMatrix[14] - center.z; // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. typedArray[i * 16 + 12] = pickColor.red; @@ -196,21 +218,13 @@ define([ collection._model = Model.fromGltf(modelOptions); } - function computeBoundingSphere(collection) { - var points = []; - var instanceCount = collection.instanceCount; - for (var i = 0; i < instanceCount; i++) { - var translation = new Cartesian3(); - Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); - points.push(translation); - } - - var boundingSphere = BoundingSphere.fromPoints(points); - boundingSphere.radius += collection.model._boundingSphere.radius; - return boundingSphere; + function createBoundingVolumeModelViewFunction(collection, context) { + return function() { + return Matrix4.multiplyTransformation(context._us.view, collection._boundingSphereModel, collection._boundingSphereModelView); + }; } - function createCommands(collection, drawCommands, pickCommands) { + function createCommands(collection, drawCommands, pickCommands, context) { collection._modelCommands = drawCommands; var i, j; @@ -219,23 +233,37 @@ define([ var instanceCount = collection.instanceCount; var allowPicking = collection.allowPicking; - var boundingSphere = computeBoundingSphere(collection); - var modelMatrix = Matrix4.fromTranslation(boundingSphere.center); - collection._boundingSphere = boundingSphere; + var boundingSphere = collection._boundingSphere; + var boundingSphereModel = collection._boundingSphereModel; + boundingSphere.radius += collection.model._boundingSphere.radius; if (collection._instancingEnabled) { for (i = 0; i < commandCount; ++i) { command = clone(drawCommands[i]); + + var uniformMap = combine(command.uniformMap, { + czm_instanced_boundingVolumeModelView : createBoundingVolumeModelViewFunction(collection, context) + }); + command.instanceCount = instanceCount; - command.modelMatrix = modelMatrix; + command.modelMatrix = boundingSphereModel; command.boundingVolume = boundingSphere; + command.uniformMap = uniformMap; + collection._drawCommands.push(command); if (allowPicking) { command = clone(pickCommands[i]); + + uniformMap = combine(command.uniformMap, { + czm_instanced_boundingVolumeModelView : createBoundingVolumeModelViewFunction(collection, context) + }); + command.instanceCount = instanceCount; - command.modelMatrix = modelMatrix; + command.modelMatrix = boundingSphereModel; command.boundingVolume = boundingSphere; + command.uniformMap = uniformMap; + collection._pickCommands.push(command); } } @@ -345,7 +373,7 @@ define([ this._ready = true; var modelCommands = getModelCommands(model); - createCommands(this, modelCommands.draw, modelCommands.pick); + createCommands(this, modelCommands.draw, modelCommands.pick, context); this._readyPromise.resolve(this); return; From 791a555c68839cf703e690134d19bde3f8d28e9d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 29 Sep 2015 17:55:37 -0400 Subject: [PATCH 07/27] Moved all instancing code into ModelInstanceCollection --- .../development/3D Models Instancing.html | 2 +- Source/Scene/Model.js | 352 ++++---------- Source/Scene/ModelInstanceCollection.js | 449 +++++++++++++----- 3 files changed, 433 insertions(+), 370 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index d45c4ec7beaa..a81e499003f8 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -64,7 +64,7 @@ })); collection.readyPromise.then(function(collection) { - var model = collection.model; + var model = collection._model; // Play and loop all animations at half-speed model.activeAnimations.addAll({ diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 42e534e4f214..a757406d6a64 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -186,9 +186,6 @@ define([ function setCachedGltf(model, cachedGltf) { model._cachedGltf = cachedGltf; model._animationIds = getAnimationIds(cachedGltf); - if (model._instanced) { - model._perInstanceUniforms = getPerInstanceUniforms(cachedGltf); - } } // glTF JSON can be big given embedded geometry, textures, and animations, so we @@ -253,43 +250,6 @@ define([ return animationIds; } - function getPerInstanceUniforms(cachedGltf) { - // Find all the uniforms that are updated per instance. Usually these are uniforms that include a model - // matrix. They will be changed to take an instanced vertex attribute instead. - var perInstanceUniforms = {}; - if (defined(cachedGltf) && defined(cachedGltf.gltf)) { - var techniques = cachedGltf.gltf.techniques; - for (var techniqueName in techniques) { - if (techniques.hasOwnProperty(techniqueName)) { - var technique = techniques[techniqueName]; - var parameters = technique.parameters; - var pass = technique.passes[technique.pass]; - var instanceProgram = pass.instanceProgram; - var programName = instanceProgram.program; - // Different techniques may share the same program, skip if already processed. - // This assumes techniques that share a program do not declare different semantics for the same uniforms. - if (!defined(perInstanceUniforms[programName])) { - var uniformMap = {}; - perInstanceUniforms[programName] = uniformMap; - var uniforms = instanceProgram.uniforms; - for (var uniformName in uniforms) { - if (uniforms.hasOwnProperty(uniformName)) { - var parameterName = uniforms[uniformName]; - var parameter = parameters[parameterName]; - var semantic = parameter.semantic; - if (defined(semantic) && (semantic.indexOf('MODEL') > -1)) { - uniformMap[uniformName] = semantic; - } - } - } - } - } - } - } - - return perInstanceUniforms; - } - var gltfCache = {}; /////////////////////////////////////////////////////////////////////////// @@ -327,8 +287,6 @@ define([ * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}. * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. - * @param {Boolean} [options.instanced=false] Whether the model will be instanced. - * @param {Object} [options.instancedAttributes] Instanced attributes for the vertex array. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @@ -348,9 +306,15 @@ define([ this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); this._animationIds = undefined; - this._instanced = defaultValue(options.instanced, false); - this._instancedAttributes = options.instancedAttributes; - this._perInstanceUniforms = undefined; + // These additional options are for internal use + this._precreatedAttributes = options.precreatedAttributes; + this._vertexShaderLoaded = options.vertexShaderLoaded; + this._fragmentShaderLoaded = options.fragmentShaderLoaded; + this._uniformMapLoaded = options.uniformMapLoaded; + this._pickVertexShaderLoaded = options.pickVertexShaderLoaded; + this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; + this._pickUniformMapLoaded = options.pickUniformMapLoaded; + this._ignoreCommands = defaultValue(options.ignoreCommands, false); var cachedGltf; if (defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready) { @@ -817,6 +781,10 @@ define([ return JSON.parse(json); } + Model.getDefaultCacheKey = function(url) { + return getAbsoluteURL(url); + }; + /** *

* Creates a model from a glTF asset. When the model is ready to render, i.e., when the external binary, image, @@ -834,7 +802,6 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url to the .gltf file. - * @param {String} [options.cacheKey] The cache key for this model. * @param {Object} [options.headers] HTTP headers to send with the request. * @param {Boolean} [options.show=true] Determines if the model primitive will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. @@ -842,8 +809,6 @@ define([ * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom. * @param {Boolean} [options.allowPicking=true] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. - * @param {Boolean} [options.instanced=false] Whether the model will be instanced. - * @param {Object} [options.instancedAttributes] Instanced attributes for the vertex array. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each {@link DrawCommand} in the model. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. * @returns {Model} The newly created model. @@ -888,13 +853,7 @@ define([ var url = options.url; // If no cache key is provided, use the absolute URL, since two URLs with // different relative paths could point to the same model. - var cacheKey = defaultValue(options.cacheKey, getAbsoluteURL(url)); - - // Instanced models will create different renderer resources, so change the cache key. - var instanced = defaultValue(options.instanced, false); - if (instanced) { - cacheKey = cacheKey + '(instanced)'; - } + var cacheKey = defaultValue(options.cacheKey, Model.getDefaultCacheKey(url)); options = clone(options); options.basePath = getBasePath(url); @@ -1397,132 +1356,44 @@ define([ return attributeLocations; } - function getShaderSource(model, shader) { + function getShaderSource(model, shader, programName, callback) { + var source; + if (defined(shader.source)) { - return shader.source; + source = shader.source; + } else { + var loadResources = model._loadResources; + var gltf = model.gltf; + var bufferView = gltf.bufferViews[shader.bufferView]; + source = getStringFromTypedArray(loadResources.getBuffer(bufferView)); } - var loadResources = model._loadResources; - var gltf = model.gltf; - var bufferView = gltf.bufferViews[shader.bufferView]; - - return getStringFromTypedArray(loadResources.getBuffer(bufferView)); - } - - function createVertexShaderInstanced(name, shaderSource, model) { - var perInstanceUniforms = model._perInstanceUniforms[name]; - if (perInstanceUniforms.length === 0) { - return shaderSource; - } - - // All per-instance uniforms will be replaced with global variables. - var globalDeclarations = ''; - var globalDefinitions = ''; - var hasModelViewMatrix = false; - var regex; - - // Add instanced attributes - var instancedAttributes = model._instancedAttributes; - for (var attrName in instancedAttributes) { - if (instancedAttributes.hasOwnProperty(attrName)) { - globalDeclarations += 'attribute vec4 ' + attrName + ';\n'; - } - } - - // Add node transform uniform, the gltf node's offset which is the same for every instance - globalDeclarations += 'uniform mat4 czm_instanced_nodeTransform;\n'; - globalDeclarations += 'uniform mat4 czm_instanced_boundingVolumeModelView;\n'; - - // Construct model view matrix - globalDeclarations += 'mat4 czm_instanced_model;\n'; - globalDeclarations += 'mat4 czm_instanced_modelView;\n'; - globalDefinitions += 'czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; - globalDefinitions += 'czm_instanced_modelView = czm_instanced_boundingVolumeModelView * czm_instanced_model * czm_instanced_nodeTransform;\n'; - - for (var uniform in perInstanceUniforms) { - if (perInstanceUniforms.hasOwnProperty(uniform)) { - var semantic = perInstanceUniforms[uniform]; - var globalVarName; - - if (semantic === 'MODEL') { - globalVarName = 'czm_instanced_model'; - } else if (semantic === 'MODELVIEW') { - globalVarName = 'czm_instanced_modelView'; - } else if (semantic === 'MODELVIEWPROJECTION') { - globalVarName = 'czm_instanced_modelViewProjection'; - globalDeclarations += 'mat4 czm_instanced_modelViewProjection;\n'; - globalDefinitions += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; - } else if (semantic === 'MODELINVERSE') { - globalVarName = 'czm_instanced_inverseModel'; - globalDeclarations += 'mat4 czm_instanced_inverseModel;\n'; - globalDefinitions += 'czm_instanced_inverseModel = inverse(czm_instanced_model);\n'; - } else if (semantic === 'MODELVIEWINVERSE') { - globalVarName = 'czm_instanced_inverseModelView'; - globalDeclarations += 'mat4 czm_instanced_inverseModelView;\n'; - globalDefinitions += 'czm_instanced_inverseModelView = inverse(czm_instanced_modelView);\n'; - } else if (semantic === 'MODELVIEWPROJECTIONINVERSE') { - globalVarName = 'czm_instanced_inverseModelViewProjection'; - globalDeclarations += 'mat4 czm_instanced_inverseModelViewProjection;\n'; - globalDefinitions += 'czm_instanced_inverseModelViewProjection = inverse(czm_projection * czm_instanced_modelView);\n'; - } else if (semantic === 'MODELINVERSETRANSPOSE') { - // Non-uniform scale is not supported for instancing, so inverse-transpose matrix is simplified - globalVarName = 'czm_instanced_modelInverseTranspose'; - globalDeclarations += 'mat3 czm_instanced_modelInverseTranspose;\n'; - globalDefinitions += 'czm_instanced_modelInverseTranspose = mat3(czm_instanced_model);\n'; - } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { - globalVarName = 'czm_instanced_modelViewInverseTranspose'; - globalDeclarations += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; - globalDefinitions += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; - } - - // Remove the uniform declaration - regex = new RegExp('uniform.*' + uniform + '.*'); - shaderSource = shaderSource.replace(regex, ''); - - // Replace all occurrences of the uniform with the global variable - regex = new RegExp(uniform + '\\b', 'g'); - shaderSource = shaderSource.replace(regex, globalVarName); - } + // Allow callback to modify the shader source + if (defined(callback)) { + source = callback(source, programName); } - // Place definitions into the main function - shaderSource = shaderSource.replace(/void\s+main\s*\([\s\S]*?{/, 'void main() {\n' + globalDefinitions); - - // Append declarations to the top of the shader - shaderSource = globalDeclarations + shaderSource; - - return shaderSource; - } - - function createPickVertexShaderInstanced(shaderSource) { - var pickSource = - 'varying vec4 czm_pickColor;\n' + - 'void main() {\n' + - 'vec4 pickColor = czm_instanceData;\n' + - 'czm_pickColor = pickColor;\n'; - return shaderSource.replace(/void\s+main\s*\([\s\S]*?{/, 'void main() {\n' + pickSource); + return source; } - function createProgramInstanced(name, model, context) { + function createProgram(name, model, context) { var programs = model.gltf.programs; var shaders = model._loadResources.shaders; var program = programs[name]; - var vs = getShaderSource(model, shaders[program.vertexShader]); - var fs = getShaderSource(model, shaders[program.fragmentShader]); - - // Assume the fragment shader does not need to be modified for instancing - vs = createVertexShaderInstanced(name, vs, model); - var attributeLocations = createAttributeLocations(program.attributes); - var attributeCount = program.attributes.length; - - var instancedAttributes = model._instancedAttributes; - for (var attrName in instancedAttributes) { - if (instancedAttributes.hasOwnProperty(attrName)) { - attributeLocations[attrName] = attributeCount; - instancedAttributes[attrName].index = attributeCount; - attributeCount++; + var vs = getShaderSource(model, shaders[program.vertexShader], name, model._vertexShaderLoaded); + var fs = getShaderSource(model, shaders[program.fragmentShader], name, model._fragmentShaderLoaded); + + // Add pre-created attributes to attributeLocations + var attributesLength = program.attributes.length; + var precreatedAttributes = model._precreatedAttributes; + if (defined(precreatedAttributes)) { + for (var attrName in precreatedAttributes) { + if (precreatedAttributes.hasOwnProperty(attrName)) { + attributeLocations[attrName] = attributesLength; + attributesLength++; + } } } @@ -1535,42 +1406,21 @@ define([ if (model.allowPicking) { // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 - var pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'varying'); - var pickVS = createPickVertexShaderInstanced(vs); + var pickVS; + var pickFS; - model._rendererResources.pickPrograms[name] = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : pickVS, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); - } - } - - function createProgram(name, model, context) { - var programs = model.gltf.programs; - var shaders = model._loadResources.shaders; - var program = programs[name]; - - var vs = getShaderSource(model, shaders[program.vertexShader]); - var fs = getShaderSource(model, shaders[program.fragmentShader]); - - var attributeLocations = createAttributeLocations(program.attributes); - - model._rendererResources.programs[name] = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - - if (model.allowPicking) { - // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 - var pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); + if (defined(model._pickFragmentShaderLoaded)) { + // If a pick fragment shader callback is defined, it overrides model picking + pickVS = getShaderSource(model, shaders[program.vertexShader], name, model._pickVertexShaderLoaded); + pickFS = getShaderSource(model, shaders[program.fragmentShader], name, model._pickFragmentShaderLoaded); + } else { + pickVS = vs; + pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); + } model._rendererResources.pickPrograms[name] = ShaderProgram.fromCache({ context : context, - vertexShaderSource : vs, + vertexShaderSource : pickVS, fragmentShaderSource : pickFS, attributeLocations : attributeLocations }); @@ -1591,19 +1441,17 @@ define([ return; } - var createProgramFunction = model._instanced ? createProgramInstanced : createProgram; - if (model.asynchronous) { // Create one program per frame if (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); - createProgramFunction(name, model, context); + createProgram(name, model, context); } } else { // Create all loaded programs this frame while (loadResources.programsToCreate.length > 0) { name = loadResources.programsToCreate.dequeue(); - createProgramFunction(name, model, context); + createProgram(name, model, context); } } } @@ -1756,10 +1604,13 @@ define([ for (var location in programAttributeLocations){ if (programAttributeLocations.hasOwnProperty(location)) { var attribute = attributes[location]; - // Ignore attributes that are added dynamically (like for instancing) + var index = programAttributeLocations[location].index; if (defined(attribute)) { var parameter = parameters[attribute]; - attributeLocations[parameter.semantic] = programAttributeLocations[location].index; + attributeLocations[parameter.semantic] = index; + } else { + // Pre-created attributes + attributeLocations[location] = index; } } } @@ -1974,17 +1825,19 @@ define([ // https://github.com/KhronosGroup/glTF/issues/258 var attributeLocations = getAttributeLocations(model, primitive); - var attrName; - var attrs = []; + var attributeName; + var attributeLocation; + var attribute; + var attributes = []; var primitiveAttributes = primitive.attributes; - for (attrName in primitiveAttributes) { - if (primitiveAttributes.hasOwnProperty(attrName)) { - var attributeLocation = attributeLocations[attrName]; + for (attributeName in primitiveAttributes) { + if (primitiveAttributes.hasOwnProperty(attributeName)) { + attributeLocation = attributeLocations[attributeName]; // Skip if the attribute is not used by the material, e.g., because the asset was exported // with an attribute that wasn't used and the asset wasn't optimized. if (defined(attributeLocation)) { - var a = accessors[primitiveAttributes[attrName]]; - attrs.push({ + var a = accessors[primitiveAttributes[attributeName]]; + attributes.push({ index : attributeLocation, vertexBuffer : rendererBuffers[a.bufferView], componentsPerAttribute : getModelAccessor(a).componentsPerAttribute, @@ -1997,21 +1850,26 @@ define([ } } - if (model._instanced) { - var instancedAttributes = model._instancedAttributes; - for (attrName in instancedAttributes) { - if (instancedAttributes.hasOwnProperty(attrName)) { - attrs.push(instancedAttributes[attrName]); + // Add pre-created attributes + var precreatedAttributes = model._precreatedAttributes; + if (defined(precreatedAttributes)) { + for (attributeName in precreatedAttributes) { + if (precreatedAttributes.hasOwnProperty(attributeName)) { + attributeLocation = attributeLocations[attributeName]; + if (defined(attributeLocation)) { + attribute = precreatedAttributes[attributeName]; + attribute.index = attributeLocation; + attributes.push(attribute); + } } } } - var vertexArrayName = meshName + '.primitive.' + i; var accessor = accessors[primitive.indices]; var indexBuffer = rendererBuffers[accessor.bufferView]; - rendererVertexArrays[vertexArrayName] = new VertexArray({ + rendererVertexArrays[meshName + '.primitive.' + i] = new VertexArray({ context: context, - attributes: attrs, + attributes: attributes, indexBuffer: indexBuffer }); } @@ -2353,10 +2211,6 @@ define([ var pass = technique.passes[technique.pass]; var instanceProgram = pass.instanceProgram; var uniforms = instanceProgram.uniforms; - var perInstanceUniforms; - if (model._instanced) { - perInstanceUniforms = model._perInstanceUniforms[instanceProgram.program]; - } var uniformMap = {}; var uniformValues = {}; @@ -2379,9 +2233,7 @@ define([ // // https://github.com/KhronosGroup/glTF/issues/142 - if (model._instanced && defined(perInstanceUniforms[name])) { - // This uniform will be replaced with an instanced vertex attribute, don't include in uniform map. - } else if (defined(instanceParameters[parameterName])) { + if (defined(instanceParameters[parameterName])) { // Parameter overrides by the instance technique var uv = gltfUniformFunctions[parameter.type](instanceParameters[parameterName], model); uniformMap[name] = uv.func; @@ -2426,12 +2278,6 @@ define([ }; } - function createNodeTransformFunction(runtimeNode, i) { - return function() { - return runtimeNode.commands[i].command.modelMatrix; - }; - } - function createCommand(model, gltfNode, runtimeNode, context) { var nodeCommands = model._nodeCommands; var pickIds = model._pickIds; @@ -2481,8 +2327,7 @@ define([ boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(a.min), Cartesian3.fromArray(a.max)); } - var vertexArrayName = name + '.primitive.' + i; - var vertexArray = rendererVertexArrays[vertexArrayName]; + var vertexArray = rendererVertexArrays[name + '.primitive.' + i]; var count = ix.count; var offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices @@ -2495,10 +2340,9 @@ define([ uniformMap = combine(uniformMap, jointUniformMap); } - if (model._instanced) { - uniformMap = combine(uniformMap, { - czm_instanced_nodeTransform : createNodeTransformFunction(runtimeNode, i) - }); + // Allow callback to modify the uniformMap + if (defined(model._uniformMapLoaded)) { + uniformMap = model._uniformMapLoaded(uniformMap, instanceProgram.program, runtimeNode); } var rs = rendererRenderStates[instanceTechnique.technique]; @@ -2533,12 +2377,13 @@ define([ var pickUniformMap; - if (model._instanced) { - pickUniformMap = clone(uniformMap); - } else { - pickUniformMap = combine(uniformMap, { - czm_pickColor : createPickColorFunction(pickId.color) - }); + pickUniformMap = combine(uniformMap, { + czm_pickColor : createPickColorFunction(pickId.color) + }); + + // Allow callback to modify the uniformMap + if (defined(model._pickUniformMapLoaded)) { + uniformMap = model._pickUniformMapLoaded(uniformMap); } pickCommand = new DrawCommand({ @@ -2724,13 +2569,14 @@ define([ var commands = n.commands; if ((n.dirtyNumber === maxDirtyNumber) || modelTransformChanged || justLoaded) { + var nodeMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix); var commandsLength = commands.length; if (commandsLength > 0) { // Node has meshes, which has primitives. Update their commands. for (var j = 0 ; j < commandsLength; ++j) { var primitiveCommand = commands[j]; var command = primitiveCommand.command; - Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, command.modelMatrix); + Matrix4.clone(nodeMatrix, command.modelMatrix); // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation) BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume); @@ -2745,9 +2591,6 @@ define([ BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume); } } - } else { - // Node has a light or camera - n.computedMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix); } } @@ -3154,15 +2997,10 @@ define([ return; } - // ModelInstanceCollection handles the model's command list directly - if (this._instanced) { - return; - } - // We don't check show at the top of the function since we // want to be able to progressively load models when they are not shown, // and then have them visible immediately when show is set to true. - if (show) { + if (show && !this._ignoreCommands) { // PERFORMANCE_IDEA: This is terrible var passes = frameState.passes; var nodeCommands = this._nodeCommands; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 6953fc7460f2..4494d5ddafe5 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -12,9 +12,11 @@ define([ '../Core/DeveloperError', '../Core/destroyObject', '../Core/Matrix4', + '../Core/RuntimeError', '../Renderer/Buffer', + '../Renderer/BufferUsage', '../Renderer/DrawCommand', - '../Renderer/WebGLConstants', + '../Renderer/ShaderSource', '../ThirdParty/when', './Model', './SceneMode' @@ -31,9 +33,11 @@ define([ DeveloperError, destroyObject, Matrix4, + RuntimeError, Buffer, + BufferUsage, DrawCommand, - WebGLConstants, + ShaderSource, when, Model, SceneMode) { @@ -55,13 +59,15 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.url The url to the .gltf file. - * @param {String} [options.cacheKey] The cache key for the model. + * @param {Boolean} [options.dynamic] Collection is set to stream instance data every frame. * @param {Object} [options.headers] HTTP headers to send with the request. * @param {Boolean} [options.show=true] Determines if the collection will be shown. * @param {Boolean} [options.allowPicking=false] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. + * + * @private */ var ModelInstanceCollection = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -72,21 +78,30 @@ define([ } //>>includeEnd('debug'); - this._model = undefined; - this._boundingSphere = undefined; - this._boundingSphereModel = undefined; - this._boundingSphereModelView = undefined; - this._instancingEnabled = false; this._instances = defaultValue(options.instances, []); + this._instancingSupported = false; + this._dynamic = defaultValue(options.dynamic, false); this._show = options.show; this._allowPicking = defaultValue(options.allowPicking, false); this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; + + this._model = undefined; + this._typedArray = undefined; + this._vertexBuffer = undefined; + this._createVertexBuffer = true; + this._instancedAttributes = undefined; + this._instancedUniformsByProgram = undefined; + this._drawCommands = []; this._pickCommands = []; this._modelCommands = undefined; + this._boundingSphere = new BoundingSphere(); + this._boundingSphereModel = new Matrix4(); + this._boundingSphereModelView = new Matrix4(); + // Passed on to Model this._url = options.url; this._cacheKey = options.cacheKey; @@ -97,11 +112,6 @@ define([ }; defineProperties(ModelInstanceCollection.prototype, { - model : { - get : function() { - return this._model; - } - }, boundingSphere : { get : function() { return this._boundingSphere; @@ -112,7 +122,7 @@ define([ return this._allowPicking; } }, - instanceCount : { + instancesLength : { get : function() { return this._instances.length; } @@ -129,24 +139,264 @@ define([ } }); - function computeBoundingSphere(collection) { + function getInstancedUniforms(collection, programName) { + if (defined(collection._instancedUniformsByProgram)) { + return collection._instancedUniformsByProgram[programName]; + } + + var instancedUniformsByProgram = {}; + + var modelSemantics = ['MODEL', 'MODELVIEW', 'MODELVIEWPROJECTION', 'MODELINVERSE', 'MODELVIEWINVERSE', 'MODELVIEWPROJECTIONINVERSE', 'MODELINVERSETRANSPOSE', 'MODELVIEWINVERSETRANSPOSE']; + var supportedSemantics = ['MODELVIEW', 'MODELVIEWPROJECTION', 'MODELVIEWINVERSETRANSPOSE']; + + var gltf = collection._model.gltf; + var techniques = gltf.techniques; + for (var techniqueName in techniques) { + if (techniques.hasOwnProperty(techniqueName)) { + var technique = techniques[techniqueName]; + var parameters = technique.parameters; + var pass = technique.passes[technique.pass]; + var instanceProgram = pass.instanceProgram; + var instanceProgramName = instanceProgram.program; + // Different techniques may share the same program, skip if already processed. + // This assumes techniques that share a program do not declare different semantics for the same uniforms. + if (!defined(instancedUniformsByProgram[instanceProgramName])) { + var uniformMap = {}; + instancedUniformsByProgram[instanceProgramName] = uniformMap; + var uniforms = instanceProgram.uniforms; + for (var uniformName in uniforms) { + if (uniforms.hasOwnProperty(uniformName)) { + var parameterName = uniforms[uniformName]; + var parameter = parameters[parameterName]; + var semantic = parameter.semantic; + if (defined(semantic) && (modelSemantics.indexOf(semantic) > -1)) { + if (supportedSemantics.indexOf(semantic) > -1) { + uniformMap[uniformName] = semantic; + } else { + // TODO : Maybe fail silently and fallback to a different approach + throw new RuntimeError('Model shader cannot be optimized for instancing'); + } + } + } + } + } + } + } + + collection._instancedUniformsByProgram = instancedUniformsByProgram; + return instancedUniformsByProgram[programName]; + } + + var vertexShaderCached; + + function getVertexShaderCallback(collection) { + return function(vs, programName) { + var instancedUniforms = getInstancedUniforms(collection, programName); + var dynamic = collection._dynamic; + + // All per-instance uniforms will be replaced with global variables. + var shaderHeader = ''; + var shaderMain = ''; + var regex; + + // Add instanced attributes + var instancedAttributes = collection._instancedAttributes; + for (var attrName in instancedAttributes) { + if (instancedAttributes.hasOwnProperty(attrName)) { + shaderHeader += 'attribute vec4 ' + attrName + ';\n'; + } + } + + shaderHeader += 'uniform mat4 czm_instanced_nodeLocal;\n'; + shaderHeader += 'uniform mat4 czm_instanced_boundsModelView;\n'; + shaderHeader += 'mat4 czm_instanced_modelView;\n'; + shaderMain += 'mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; + + if (dynamic) { + // czm_instanced_model is the modelView matrix + shaderMain += 'czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeLocal;\n'; + } else { + // czm_instanced_model is the model's local offset from the bounding volume + shaderMain += 'czm_instanced_modelView = czm_instanced_boundsModelView * czm_instanced_model * czm_instanced_nodeLocal;\n'; + } + + for (var uniform in instancedUniforms) { + if (instancedUniforms.hasOwnProperty(uniform)) { + var semantic = instancedUniforms[uniform]; + + var varName; + if (semantic === 'MODELVIEW') { + varName = 'czm_instanced_modelView'; + } else if (semantic === 'MODELVIEWPROJECTION') { + varName = 'czm_instanced_modelViewProjection'; + shaderHeader += 'mat4 czm_instanced_modelViewProjection;\n'; + shaderMain += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; + } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { + varName = 'czm_instanced_modelViewInverseTranspose'; + shaderHeader += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; + shaderMain += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; + } + + // Remove the uniform declaration + regex = new RegExp('uniform.*' + uniform + '.*'); + vs = vs.replace(regex, ''); + + // Replace all occurrences of the uniform with the global variable + regex = new RegExp(uniform + '\\b', 'g'); + vs = vs.replace(regex, varName); + } + } + + // Place the instancing code into the shader + var instancingCode = shaderHeader + 'void main() {\n' + shaderMain; + vs = vs.replace(/void\s+main\s*\([\s\S]*?{/, instancingCode); + + vertexShaderCached = vs; + return vs; + }; + } + + function getPickVertexShaderCallback() { + return function (vs) { + // Use the vertex shader that was generated earlier + vs = vertexShaderCached; + var pickCode = + 'varying vec4 czm_pickColor;\n' + + 'void main() {\n' + + 'vec4 pickColor = czm_instanceData;\n' + + 'czm_pickColor = pickColor;\n'; + return vs.replace(/void\s+main\s*\([\s\S]*?{/, pickCode); + }; + } + + function getPickFragmentShaderCallback() { + return function(fs) { + return ShaderSource.createPickFragmentShaderSource(fs, 'varying'); + }; + } + + function createBoundsModelViewFunction(collection, context) { + return function() { + return Matrix4.multiplyTransformation(context.uniformState.view, collection._boundingSphereModel, collection._boundingSphereModelView); + }; + } + + function createNodeLocalFunction(node) { + return function() { + return node.computedMatrix; + }; + } + + function getUniformMapCallback(collection, context) { + return function(uniformMap, programName, node) { + uniformMap = combine(uniformMap, { + czm_instanced_boundsModelView : (collection._dynamic ? undefined : createBoundsModelViewFunction(collection, context)), + czm_instanced_nodeLocal : createNodeLocalFunction(node) + }); + + // Remove instanced uniforms from the uniform map + var instancedUniforms = getInstancedUniforms(collection, programName); + for (var uniform in instancedUniforms) { + if (instancedUniforms.hasOwnProperty(uniform)) { + uniformMap[uniform] = undefined; + } + } + + return uniformMap; + }; + } + + function getPickUniformMapCallback() { + return function(uniformMap) { + uniformMap.czm_pickColor = undefined; + return uniformMap; + }; + } + + var instanceMatrix = new Matrix4(); + + function updateVertexBuffer(collection, context) { + if (!collection._instancingSupported) { + return; + } + + var typedArray = collection._typedArray; + var vertexBuffer = collection._vertexBuffer; + var createVertexBuffer = collection._createVertexBuffer; + + var instancesLength = collection.instancesLength; + var dynamic = collection._dynamic; + var viewMatrix = context.uniformState.view; + var center = dynamic ? Cartesian3.ZERO : collection._boundingSphere.center; + + if (createVertexBuffer) { + typedArray = new Float32Array(instancesLength * 16); + } + + for (var i = 0; i < instancesLength; ++i) { + var instance = collection._instances[i]; + var modelMatrix = instance.modelMatrix; + var pickColor = defined(instance.pickId) ? instance.pickId.color : Color.WHITE; + + if (dynamic) { + Matrix4.multiplyTransformation(viewMatrix, modelMatrix, instanceMatrix); + } else { + instanceMatrix = modelMatrix; + } + + // First three rows of the model matrix + typedArray[i * 16 + 0] = instanceMatrix[0]; + typedArray[i * 16 + 1] = instanceMatrix[4]; + typedArray[i * 16 + 2] = instanceMatrix[8]; + typedArray[i * 16 + 3] = instanceMatrix[12] - center.x; + typedArray[i * 16 + 4] = instanceMatrix[1]; + typedArray[i * 16 + 5] = instanceMatrix[5]; + typedArray[i * 16 + 6] = instanceMatrix[9]; + typedArray[i * 16 + 7] = instanceMatrix[13] - center.y; + typedArray[i * 16 + 8] = instanceMatrix[2]; + typedArray[i * 16 + 9] = instanceMatrix[6]; + typedArray[i * 16 + 10] = instanceMatrix[10]; + typedArray[i * 16 + 11] = instanceMatrix[14] - center.z; + + // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. + typedArray[i * 16 + 12] = pickColor.red; + typedArray[i * 16 + 13] = pickColor.green; + typedArray[i * 16 + 14] = pickColor.blue; + typedArray[i * 16 + 15] = pickColor.alpha; + } + + if (createVertexBuffer) { + var usage = dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW; + vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : typedArray, + usage : usage + }); + collection._vertexBuffer = vertexBuffer; + collection._typedArray = typedArray; + collection._createVertexBuffer = false; + } else { + vertexBuffer.copyFromArrayView(typedArray); + } + } + + function updateBoundingSphere(collection) { var points = []; - var instanceCount = collection.instanceCount; - for (var i = 0; i < instanceCount; i++) { + var instancesLength = collection.instancesLength; + for (var i = 0; i < instancesLength; i++) { var translation = new Cartesian3(); Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); points.push(translation); } - var boundingSphere = BoundingSphere.fromPoints(points); - collection._boundingSphere = boundingSphere; - collection._boundingSphereModel = Matrix4.fromTranslation(boundingSphere.center); - collection._boundingSphereModelView = new Matrix4(); + var boundingSphere = collection._boundingSphere; + BoundingSphere.fromPoints(points, boundingSphere); + Matrix4.fromTranslation(boundingSphere.center, collection._boundingSphereModel); } - function createModel(context, frameState, commandList, collection) { + function createModel(collection, context) { var i; - var instanceCount = collection.instanceCount; + var instancingSupported = collection._instancingSupported; var modelOptions = { url : collection._url, cacheKey : collection._cacheKey, @@ -154,45 +404,21 @@ define([ asynchronous : collection._asynchronous, debugShowBoundingVolume : collection._debugShowBoundingVolume, debugWireframe : collection._debugWireframe, - allowPicking : collection._allowPicking + allowPicking : collection._allowPicking, + precreatedAttributes : undefined, + vertexShaderLoaded : undefined, + fragmentShaderLoaded : undefined, + uniformMapLoaded : undefined, + pickVertexShaderLoaded : undefined, + pickFragmentShaderLoaded : undefined, + pickUniformMapLoaded : undefined, + ignoreCommands : false }; - computeBoundingSphere(collection); - - if (collection._instancingEnabled) { - var center = collection._boundingSphere.center; - var typedArray = new Float32Array(instanceCount * 16); - for (i = 0; i < instanceCount; ++i) { - var instance = collection._instances[i]; - var modelMatrix = instance.modelMatrix; - var pickColor = defined(instance.pickId) ? instance.pickId.color : Color.WHITE; - - // First three rows of the model matrix - typedArray[i * 16 + 0] = modelMatrix[0]; - typedArray[i * 16 + 1] = modelMatrix[4]; - typedArray[i * 16 + 2] = modelMatrix[8]; - typedArray[i * 16 + 3] = modelMatrix[12] - center.x; - typedArray[i * 16 + 4] = modelMatrix[1]; - typedArray[i * 16 + 5] = modelMatrix[5]; - typedArray[i * 16 + 6] = modelMatrix[9]; - typedArray[i * 16 + 7] = modelMatrix[13] - center.y; - typedArray[i * 16 + 8] = modelMatrix[2]; - typedArray[i * 16 + 9] = modelMatrix[6]; - typedArray[i * 16 + 10] = modelMatrix[10]; - typedArray[i * 16 + 11] = modelMatrix[14] - center.z; - - // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. - typedArray[i * 16 + 12] = pickColor.red; - typedArray[i * 16 + 13] = pickColor.green; - typedArray[i * 16 + 14] = pickColor.blue; - typedArray[i * 16 + 15] = pickColor.alpha; - } + updateBoundingSphere(collection); - var vertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : typedArray, - usage : WebGLConstants.STATIC_DRAW - }); + if (instancingSupported) { + updateVertexBuffer(collection, context); var attributeNames = ['czm_modelMatrixRow0', 'czm_modelMatrixRow1', 'czm_modelMatrixRow2', 'czm_instanceData']; var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT); @@ -201,7 +427,7 @@ define([ for (i = 0; i < 4; ++i) { instancedAttributes[attributeNames[i]] = { index : 0, // updated in Model - vertexBuffer : vertexBuffer, + vertexBuffer : collection._vertexBuffer, componentsPerAttribute : 4, componentDatatype : ComponentDatatype.FLOAT, normalize : false, @@ -211,66 +437,60 @@ define([ }; } - modelOptions.instanced = true; - modelOptions.instancedAttributes = instancedAttributes; + collection._instancedAttributes = instancedAttributes; + + // Instanced models will create different renderer resources, so change the cache key. + var url = collection._url; + var cacheKey = defaultValue(collection._cacheKey, Model.getDefaultCacheKey(url)); + cacheKey += '(instanced)'; + + modelOptions.precreatedAttributes = instancedAttributes; + modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); + modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context); + modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(); + modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(); + modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(); + modelOptions.cacheKey = cacheKey; + modelOptions.ignoreCommands = true; } collection._model = Model.fromGltf(modelOptions); } - function createBoundingVolumeModelViewFunction(collection, context) { - return function() { - return Matrix4.multiplyTransformation(context._us.view, collection._boundingSphereModel, collection._boundingSphereModelView); - }; - } - - function createCommands(collection, drawCommands, pickCommands, context) { + function createCommands(collection, drawCommands, pickCommands) { collection._modelCommands = drawCommands; - var i, j; + var i; + var j; var command; - var commandCount = drawCommands.length; - var instanceCount = collection.instanceCount; + var commandsLength = drawCommands.length; + var instancesLength = collection.instancesLength; var allowPicking = collection.allowPicking; var boundingSphere = collection._boundingSphere; var boundingSphereModel = collection._boundingSphereModel; - boundingSphere.radius += collection.model._boundingSphere.radius; + boundingSphere.radius += collection._model._boundingSphere.radius; - if (collection._instancingEnabled) { - for (i = 0; i < commandCount; ++i) { + if (collection._instancingSupported) { + for (i = 0; i < commandsLength; ++i) { command = clone(drawCommands[i]); - - var uniformMap = combine(command.uniformMap, { - czm_instanced_boundingVolumeModelView : createBoundingVolumeModelViewFunction(collection, context) - }); - - command.instanceCount = instanceCount; + command.instanceCount = instancesLength; command.modelMatrix = boundingSphereModel; command.boundingVolume = boundingSphere; - command.uniformMap = uniformMap; - collection._drawCommands.push(command); if (allowPicking) { command = clone(pickCommands[i]); - - uniformMap = combine(command.uniformMap, { - czm_instanced_boundingVolumeModelView : createBoundingVolumeModelViewFunction(collection, context) - }); - - command.instanceCount = instanceCount; + command.instanceCount = instancesLength; command.modelMatrix = boundingSphereModel; command.boundingVolume = boundingSphere; - command.uniformMap = uniformMap; - collection._pickCommands.push(command); } } } else { // When instancing is disabled, create commands for every instance. - for (i = 0; i < commandCount; ++i) { - for (j = 0; j < instanceCount; ++j) { + for (i = 0; i < commandsLength; ++i) { + for (j = 0; j < instancesLength; ++j) { command = clone(drawCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); @@ -289,27 +509,28 @@ define([ function updateCommands(collection) { // Only applies when instancing is disabled. The instanced shader handles node transformations. - if (collection._instancingEnabled) { + if (collection._instancingSupported) { return; } - var i, j; + var i; + var j; var modelCommands = collection._modelCommands; - var commandCount = modelCommands.length; - var instanceCount = collection.instanceCount; + var commandsLength = modelCommands.length; + var instancesLength = collection.instancesLength; var allowPicking = collection.allowPicking; - for (i = 0; i < commandCount; ++i) { - var command = modelCommands[i]; - for (j = 0; j < instanceCount; ++j) { - var commandIndex = i*instanceCount+j; + for (i = 0; i < commandsLength; ++i) { + var modelCommand = modelCommands[i]; + for (j = 0; j < instancesLength; ++j) { + var commandIndex = i*instancesLength+j; var drawCommand = collection._drawCommands[commandIndex]; var instanceMatrix = collection._instances[j].modelMatrix; - var nodeMatrix = command.modelMatrix; + var nodeMatrix = modelCommand.modelMatrix; var modelMatrix = drawCommand.modelMatrix; Matrix4.multiplyTransformation(instanceMatrix, nodeMatrix, modelMatrix); - var nodeBoundingSphere = command.boundingVolume; + var nodeBoundingSphere = modelCommand.boundingVolume; var boundingSphere = drawCommand.boundingVolume; BoundingSphere.transform(nodeBoundingSphere, instanceMatrix, boundingSphere); @@ -354,15 +575,15 @@ define([ return; } - if (this.instanceCount === 0) { + if (this.instancesLength === 0) { return; } - this._instancingEnabled = context.instancedArrays; + this._instancingSupported = context.instancedArrays; if (this._state === LoadState.NEEDS_LOAD) { this._state = LoadState.LOADING; - createModel(context, frameState, commandList, this); + createModel(this, context); } var model = this._model; @@ -373,7 +594,7 @@ define([ this._ready = true; var modelCommands = getModelCommands(model); - createCommands(this, modelCommands.draw, modelCommands.pick, context); + createCommands(this, modelCommands.draw, modelCommands.pick); this._readyPromise.resolve(this); return; @@ -389,21 +610,25 @@ define([ updateCommands(this); } + if (this._dynamic) { + updateVertexBuffer(this, context); + } + var passes = frameState.passes; var commands; - var commandCount; + var commandsLength; var i; if (passes.render) { commands = this._drawCommands; - commandCount = commands.length; - for (i = 0; i < commandCount; i++) { + commandsLength = commands.length; + for (i = 0; i < commandsLength; i++) { commandList.push(commands[i]); } } else if (passes.pick) { commands = this._pickCommands; - commandCount = commands.length; - for (i = 0; i < commandCount; i++) { + commandsLength = commands.length; + for (i = 0; i < commandsLength; i++) { commandList.push(commands[i]); } } From b938c610353b9f03c1e6e711d88c5ceee99ebb4d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 30 Sep 2015 10:03:30 -0400 Subject: [PATCH 08/27] Small fix --- Source/Scene/Model.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index a757406d6a64..bdf65118b394 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -2375,15 +2375,13 @@ define([ var pickId = context.createPickId(owner); pickIds.push(pickId); - var pickUniformMap; - - pickUniformMap = combine(uniformMap, { + var pickUniformMap = combine(uniformMap, { czm_pickColor : createPickColorFunction(pickId.color) }); // Allow callback to modify the uniformMap if (defined(model._pickUniformMapLoaded)) { - uniformMap = model._pickUniformMapLoaded(uniformMap); + pickUniformMap = model._pickUniformMapLoaded(uniformMap); } pickCommand = new DrawCommand({ From 33c4acd41add15be8a75d5ade7b557b48a4c9349 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 30 Sep 2015 11:12:31 -0400 Subject: [PATCH 09/27] Tweak comment --- Source/Scene/Model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index bdf65118b394..c6f39f566d0b 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -306,7 +306,7 @@ define([ this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); this._animationIds = undefined; - // These additional options are for internal use + // Undocumented options this._precreatedAttributes = options.precreatedAttributes; this._vertexShaderLoaded = options.vertexShaderLoaded; this._fragmentShaderLoaded = options.fragmentShaderLoaded; From 02781ce723bea8b98dbb40bb4428bb84f1b4f864 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 30 Sep 2015 11:16:48 -0400 Subject: [PATCH 10/27] Style tweak --- Source/Scene/Model.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index c6f39f566d0b..ca99f6c51a0a 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -1391,8 +1391,7 @@ define([ if (defined(precreatedAttributes)) { for (var attrName in precreatedAttributes) { if (precreatedAttributes.hasOwnProperty(attrName)) { - attributeLocations[attrName] = attributesLength; - attributesLength++; + attributeLocations[attrName] = attributesLength++; } } } From 12904b8e30c0e55b0852d45451988b42f2942230 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 30 Sep 2015 11:59:03 -0400 Subject: [PATCH 11/27] Style tweak --- Source/Scene/ModelInstanceCollection.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 4494d5ddafe5..2222b21b6964 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -546,14 +546,12 @@ define([ function getModelCommands(model) { var nodeCommands = model._nodeCommands; var length = nodeCommands.length; - var i; - var nc; var drawCommands = []; var pickCommands = []; - for (i = 0; i < length; ++i) { - nc = nodeCommands[i]; + for (var i = 0; i < length; ++i) { + var nc = nodeCommands[i]; if (nc.show) { drawCommands.push(nc.command); pickCommands.push(nc.pickCommand); From a416ee8007d1ade584f7ba2fe2955687aab1c487 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 30 Sep 2015 12:00:30 -0400 Subject: [PATCH 12/27] Style tweak --- Source/Scene/ModelInstanceCollection.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 2222b21b6964..8728471a5a11 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -513,16 +513,14 @@ define([ return; } - var i; - var j; var modelCommands = collection._modelCommands; var commandsLength = modelCommands.length; var instancesLength = collection.instancesLength; var allowPicking = collection.allowPicking; - for (i = 0; i < commandsLength; ++i) { + for (var i = 0; i < commandsLength; ++i) { var modelCommand = modelCommands[i]; - for (j = 0; j < instancesLength; ++j) { + for (var j = 0; j < instancesLength; ++j) { var commandIndex = i*instancesLength+j; var drawCommand = collection._drawCommands[commandIndex]; var instanceMatrix = collection._instances[j].modelMatrix; From 4f14143bb40dd5b654418bcd28cc98ab8f1260da Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 30 Sep 2015 13:17:59 -0400 Subject: [PATCH 13/27] Updated model instancing Sandcastle demo --- .../development/3D Models Instancing.html | 281 ++++++++++++------ .../development/3D Models Instancing.jpg | Bin 108168 -> 250808 bytes 2 files changed, 195 insertions(+), 86 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index a81e499003f8..732e536db67b 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -30,117 +30,226 @@ var debugShowBoundingVolume = false; var debugWireframe = false; -var globeVisible = false; +var globeVisible = true; var viewer = new Cesium.Viewer('cesiumContainer', { - globe : globeVisible, - skyAtmosphere : globeVisible + globe : globeVisible ? undefined : false, + skyAtmosphere : globeVisible ? undefined : false }); var scene = viewer.scene; +var context = scene.context; +var camera = viewer.camera; +scene.debugShowFramesPerSecond = true; -function createCollection(url, count) { - var instances = []; - var longitude = -123.0744619; - var latitude = 44.0503706; - var height = 5000.0; - for (var i = 0; i < count; ++i) { - var heading = Math.random(); - var pitch = Math.random(); - var roll = Math.random(); - var position = new Cesium.Cartesian3.fromDegrees(longitude, latitude, height + i * 20); - - var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(position, heading, pitch, roll); - instances.push({ - modelMatrix : modelMatrix - }); - } +var instancedArraysExtension = context._instancedArrays; +var count = 1024; +var spacing = 0.0002; +var url = '../../SampleData/models/CesiumAir/Cesium_Air.bgltf'; +var dynamic = false; +var useCollection = true; + +var centerLongitude = -123.0744619; +var centerLatitude = 44.0503706; +var height = 5000.0; + +function orientCamera(center, radius) { + var controller = scene.screenSpaceCameraController; + var r = Math.max(radius, camera.frustum.near); + controller.minimumZoomDistance = r * 0.5; + + var heading = Cesium.Math.toRadians(230.0); + var pitch = Cesium.Math.toRadians(-20.0); + camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0)); +} +function createCollection(instances) { var collection = scene.primitives.add(new Cesium.ModelInstanceCollection({ url : url, instances : instances, + dynamic : dynamic, debugShowBoundingVolume : debugShowBoundingVolume, debugWireframe : debugWireframe })); collection.readyPromise.then(function(collection) { - var model = collection._model; - // Play and loop all animations at half-speed - model.activeAnimations.addAll({ + collection._model.activeAnimations.addAll({ speedup : 0.5, loop : Cesium.ModelAnimationLoop.REPEAT }); + orientCamera(collection.boundingSphere.center, collection.boundingSphere.radius); + }).otherwise(function(error){ + window.alert(error); + }); +} + +function createModels(instances) { + var points = []; + var model; + + var length = instances.length; + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var modelMatrix = instance.modelMatrix; + var translation = new Cesium.Cartesian3(); + Cesium.Matrix4.getTranslation(modelMatrix, translation); + points.push(translation); + + model = Cesium.Model.fromGltf({ + url : url, + modelMatrix : modelMatrix, + debugShowBoundingVolume : debugShowBoundingVolume, + debugWireframe : debugWireframe + }); - var camera = viewer.camera; + scene.primitives.add(model); - // Zoom to model - var controller = scene.screenSpaceCameraController; - var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); - controller.minimumZoomDistance = r * 0.5; + model.readyPromise.then(function(model) { + // Play and loop all animations at half-speed + model.activeAnimations.addAll({ + speedup : 0.5, + loop : Cesium.ModelAnimationLoop.REPEAT + }); + }).otherwise(function(error){ + window.alert(error); + }); + } - var origin = collection.boundingSphere.center; - var heading = Cesium.Math.toRadians(230.0); - var pitch = Cesium.Math.toRadians(-20.0); - camera.lookAt(origin, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0)); - }).otherwise(function(error){ - window.alert(error); + model.readyPromise.then(function(model) { + var boundingSphere = new Cesium.BoundingSphere(); + Cesium.BoundingSphere.fromPoints(points, boundingSphere); + orientCamera(boundingSphere.center, boundingSphere.radius + model.boundingSphere.radius); }); } -var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); -handler.setInputAction(function(movement) { - var pick = scene.pick(movement.endPosition); - if (Cesium.defined(pick) && Cesium.defined(pick.node) && Cesium.defined(pick.mesh)) { - // Output glTF node and mesh under the mouse. - console.log('node: ' + pick.node.name + '. mesh: ' + pick.mesh.name); - } -}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - -/////////////////////////////////////////////////////////////////////////// - -createCollection('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 10); - -//function createModel(url, height) { -// height = Cesium.defaultValue(height, 0.0); -// -// var origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height); -// var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, 0.0, 0.0, 0.0); -// -// var model = scene.primitives.add(Cesium.Model.fromGltf({ -// url : url, -// modelMatrix : modelMatrix, -// debugShowBoundingVolume : true -// -// //minimumPixelSize : 128 -// })); -// -// model.readyPromise.then(function(model) { -// // Play and loop all animations at half-speed -// model.activeAnimations.addAll({ -// speedup : 0.5, -// loop : Cesium.ModelAnimationLoop.REPEAT -// }); -// -// var camera = viewer.camera; -// -// // Zoom to model -// var controller = scene.screenSpaceCameraController; -// var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); -// controller.minimumZoomDistance = r * 0.5; -// -// var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3()); -// var heading = Cesium.Math.toRadians(230.0); -// var pitch = Cesium.Math.toRadians(-20.0); -// camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0)); -// }).otherwise(function(error) { -// window.alert(error); -// }); -//} - -// height, heading, pitch, roll -//createModel('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 5000.0); -//createModel('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 5020.0, 0.0, 0.0, -20.0); +function reset() { + scene.primitives.removeAll(); + + var instances = []; + var gridSize = Math.sqrt(count); + + for (var y = 0; y < gridSize; ++y) { + for (var x = 0; x < gridSize; ++x) { + var longitude = centerLongitude + spacing * (x - gridSize/2); + var latitude = centerLatitude + spacing * (y - gridSize/2); + var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height); + + var heading = Math.random(); + var pitch = Math.random(); + var roll = Math.random(); + var scale = (Math.random() + 1.0)/2.0; + + var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(position, heading, pitch, roll); + Cesium.Matrix4.multiplyByUniformScale(modelMatrix, scale, modelMatrix); + + instances.push({ + modelMatrix : modelMatrix + }); + } + } + + useCollection ? createCollection(instances) : createModels(instances); +} + +Sandcastle.addToolbarMenu([ { + text : '1024 instances', + onselect : function() { + count = 1024; + reset(); + } +}, { + text : '100 instances', + onselect : function() { + count = 100; + reset(); + } +}, { + text : '25 instances', + onselect : function() { + count = 25; + reset(); + } +}, { + text : '4 instances', + onselect : function() { + count = 4; + reset(); + } +}, { + text : '10000 instances', + onselect : function() { + count = 10000; + reset(); + } +}]); + +Sandcastle.addToolbarMenu([ { + text : 'Aircraft', + onselect : function() { + url = '../../SampleData/models/CesiumAir/Cesium_Air.bgltf'; + spacing = 0.0002; + reset(); + } +}, { + text : 'Ground vehicle', + onselect : function() { + url = '../../SampleData/models/CesiumGround/Cesium_Ground.bgltf'; + spacing = 0.00008; + reset(); + } +}, { + text : 'Milk truck', + onselect : function() { + url = '../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.bgltf'; + spacing = 0.00008; + reset(); + } +}, { + text : 'Skinned character', + onselect : function() { + url = '../../SampleData/models/CesiumMan/Cesium_Man.bgltf'; + spacing = 0.00001; + reset(); + } +}]); + + +Sandcastle.addToolbarMenu([ { + text : 'Instancing Enabled', + onselect : function() { + context._instancedArrays = instancedArraysExtension; + useCollection = true; + reset(); + } +}, { + text : 'Instancing Disabled', + onselect : function() { + context._instancedArrays = undefined; + useCollection = true; + reset(); + } +}, { + text : 'Individual models', + onselect : function() { + useCollection = false; + reset(); + } +}]); + +Sandcastle.addToolbarMenu([ { + text : 'Static', + onselect : function() { + dynamic = false; + reset(); + } +}, { + text : 'Dynamic', + onselect : function() { + dynamic = true; + reset(); + } +}]); //Sandcastle_End Sandcastle.finishedLoading(); diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.jpg b/Apps/Sandcastle/gallery/development/3D Models Instancing.jpg index ca19bf3a5e7dcf6e2004f632fc3e0ce621b97327..312c700cb4e99753a5d89b17ee25c9871943bd19 100644 GIT binary patch literal 250808 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

GlP=5_RR<(v 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

=}_eoT%*vEE4gA- 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 literal 108168 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

oj_!zN2GDQN>s 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 From b48b4f243ae0647edc028a936ee80b472f48b533 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 30 Sep 2015 16:20:50 -0400 Subject: [PATCH 14/27] Minor fixes --- .../development/3D Models Instancing.html | 1 - Source/Scene/Model.js | 9 +- Source/Scene/ModelInstanceCollection.js | 195 +++++++++++++----- 3 files changed, 145 insertions(+), 60 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index 732e536db67b..44506b1eb669 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -125,7 +125,6 @@ function reset() { scene.primitives.removeAll(); - var instances = []; var gridSize = Math.sqrt(count); diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index ca99f6c51a0a..7753885af1d8 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -781,7 +781,12 @@ define([ return JSON.parse(json); } - Model.getDefaultCacheKey = function(url) { + /** + * Get the default cache key from the provided url + * + * @private + */ + Model._getDefaultCacheKey = function(url) { return getAbsoluteURL(url); }; @@ -853,7 +858,7 @@ define([ var url = options.url; // If no cache key is provided, use the absolute URL, since two URLs with // different relative paths could point to the same model. - var cacheKey = defaultValue(options.cacheKey, Model.getDefaultCacheKey(url)); + var cacheKey = defaultValue(options.cacheKey, Model._getDefaultCacheKey(url)); options = clone(options); options.basePath = getBasePath(url); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 8728471a5a11..491708479933 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -12,6 +12,7 @@ define([ '../Core/DeveloperError', '../Core/destroyObject', '../Core/Matrix4', + '../Core/PrimitiveType', '../Core/RuntimeError', '../Renderer/Buffer', '../Renderer/BufferUsage', @@ -33,6 +34,7 @@ define([ DeveloperError, destroyObject, Matrix4, + PrimitiveType, RuntimeError, Buffer, BufferUsage, @@ -58,37 +60,46 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {String} options.url The url to the .gltf file. - * @param {Boolean} [options.dynamic] Collection is set to stream instance data every frame. + * @param {String} [options.url] The url to the .gltf file. * @param {Object} [options.headers] HTTP headers to send with the request. + * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] The object for the glTF JSON or an arraybuffer of Binary glTF defined by the CESIUM_binary_glTF extension. + * @param {String} [options.basePath=''] The base path that paths in the glTF JSON are relative to. + * @param {Boolean} [options.dynamic] Collection is set to stream instance data every frame. * @param {Boolean} [options.show=true] Determines if the collection will be shown. * @param {Boolean} [options.allowPicking=false] When true, each glTF mesh and primitive is pickable with {@link Scene#pick}. * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. * + * @exception {DeveloperError} Must specify either or , but not both. + * @exception {DeveloperError} Shader program cannot be optimized for instancing. Parameters cannot have any of the following semantics: MODEL, MODELINVERSE, MODELVIEWINVERSE, MODELVIEWPROJECTIONINVERSE, MODELINVERSETRANSPOSE. + * * @private */ var ModelInstanceCollection = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); - if (!defined(options.url)) { - throw new DeveloperError('options.url is required.'); + if (!defined(options.gltf) && !defined(options.url)) { + throw new DeveloperError('Either options.gltf or options.url is required.'); + } + + if (defined(options.gltf) && defined(options.url)) { + throw new DeveloperError('Cannot pass in both options.gltf and options.url.'); } //>>includeEnd('debug'); this._instances = defaultValue(options.instances, []); this._instancingSupported = false; this._dynamic = defaultValue(options.dynamic, false); - this._show = options.show; + this._show = defaultValue(options.show, true); this._allowPicking = defaultValue(options.allowPicking, false); this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; this._model = undefined; - this._typedArray = undefined; + this._instanceDataArray = undefined; this._vertexBuffer = undefined; this._createVertexBuffer = true; this._instancedAttributes = undefined; @@ -104,11 +115,39 @@ define([ // Passed on to Model this._url = options.url; - this._cacheKey = options.cacheKey; this._headers = options.headers; + this._gltf = options.gltf; + this._basePath = options.basePath; + this._cacheKey = options.cacheKey; this._asynchronous = options.asynchronous; - this._debugShowBoundingVolume = options.debugShowBoundingVolume; - this._debugWireframe = options.debugWireframe; + + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds + * to one draw command. A glTF mesh has an array of primitives, often of length one. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + this._debugShowBoundingVolume = false; + + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the model in wireframe. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugWireframe = defaultValue(options.debugWireframe, false); + this._debugWireframe = false; + }; defineProperties(ModelInstanceCollection.prototype, { @@ -122,7 +161,7 @@ define([ return this._allowPicking; } }, - instancesLength : { + length : { get : function() { return this._instances.length; } @@ -173,8 +212,10 @@ define([ if (supportedSemantics.indexOf(semantic) > -1) { uniformMap[uniformName] = semantic; } else { - // TODO : Maybe fail silently and fallback to a different approach - throw new RuntimeError('Model shader cannot be optimized for instancing'); + throw new DeveloperError('Shader program cannot be optimized for instancing. ' + + 'Parameter "' + parameter + '" in program "' + programName + + '" uses unsupported semantic "' + semantic + '"' + ); } } } @@ -208,7 +249,6 @@ define([ } shaderHeader += 'uniform mat4 czm_instanced_nodeLocal;\n'; - shaderHeader += 'uniform mat4 czm_instanced_boundsModelView;\n'; shaderHeader += 'mat4 czm_instanced_modelView;\n'; shaderMain += 'mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; @@ -217,7 +257,8 @@ define([ shaderMain += 'czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeLocal;\n'; } else { // czm_instanced_model is the model's local offset from the bounding volume - shaderMain += 'czm_instanced_modelView = czm_instanced_boundsModelView * czm_instanced_model * czm_instanced_nodeLocal;\n'; + shaderHeader += 'uniform mat4 czm_instanced_collectionModelView;\n'; + shaderMain += 'czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeLocal;\n'; } for (var uniform in instancedUniforms) { @@ -290,7 +331,7 @@ define([ function getUniformMapCallback(collection, context) { return function(uniformMap, programName, node) { uniformMap = combine(uniformMap, { - czm_instanced_boundsModelView : (collection._dynamic ? undefined : createBoundsModelViewFunction(collection, context)), + czm_instanced_collectionModelView : (collection._dynamic ? undefined : createBoundsModelViewFunction(collection, context)), czm_instanced_nodeLocal : createNodeLocalFunction(node) }); @@ -320,17 +361,17 @@ define([ return; } - var typedArray = collection._typedArray; + var instanceDataArray = collection._instanceDataArray; var vertexBuffer = collection._vertexBuffer; var createVertexBuffer = collection._createVertexBuffer; - var instancesLength = collection.instancesLength; + var instancesLength = collection.length; var dynamic = collection._dynamic; var viewMatrix = context.uniformState.view; var center = dynamic ? Cartesian3.ZERO : collection._boundingSphere.center; if (createVertexBuffer) { - typedArray = new Float32Array(instancesLength * 16); + instanceDataArray = new Float32Array(instancesLength * 16); } for (var i = 0; i < instancesLength; ++i) { @@ -344,45 +385,46 @@ define([ instanceMatrix = modelMatrix; } + var offset = i * 16; + // First three rows of the model matrix - typedArray[i * 16 + 0] = instanceMatrix[0]; - typedArray[i * 16 + 1] = instanceMatrix[4]; - typedArray[i * 16 + 2] = instanceMatrix[8]; - typedArray[i * 16 + 3] = instanceMatrix[12] - center.x; - typedArray[i * 16 + 4] = instanceMatrix[1]; - typedArray[i * 16 + 5] = instanceMatrix[5]; - typedArray[i * 16 + 6] = instanceMatrix[9]; - typedArray[i * 16 + 7] = instanceMatrix[13] - center.y; - typedArray[i * 16 + 8] = instanceMatrix[2]; - typedArray[i * 16 + 9] = instanceMatrix[6]; - typedArray[i * 16 + 10] = instanceMatrix[10]; - typedArray[i * 16 + 11] = instanceMatrix[14] - center.z; + instanceDataArray[offset + 0] = instanceMatrix[0]; + instanceDataArray[offset + 1] = instanceMatrix[4]; + instanceDataArray[offset + 2] = instanceMatrix[8]; + instanceDataArray[offset + 3] = instanceMatrix[12] - center.x; + instanceDataArray[offset + 4] = instanceMatrix[1]; + instanceDataArray[offset + 5] = instanceMatrix[5]; + instanceDataArray[offset + 6] = instanceMatrix[9]; + instanceDataArray[offset + 7] = instanceMatrix[13] - center.y; + instanceDataArray[offset + 8] = instanceMatrix[2]; + instanceDataArray[offset + 9] = instanceMatrix[6]; + instanceDataArray[offset + 10] = instanceMatrix[10]; + instanceDataArray[offset + 11] = instanceMatrix[14] - center.z; // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. - typedArray[i * 16 + 12] = pickColor.red; - typedArray[i * 16 + 13] = pickColor.green; - typedArray[i * 16 + 14] = pickColor.blue; - typedArray[i * 16 + 15] = pickColor.alpha; + instanceDataArray[offset + 12] = pickColor.red; + instanceDataArray[offset + 13] = pickColor.green; + instanceDataArray[offset + 14] = pickColor.blue; + instanceDataArray[offset + 15] = pickColor.alpha; } if (createVertexBuffer) { - var usage = dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW; vertexBuffer = Buffer.createVertexBuffer({ context : context, - typedArray : typedArray, - usage : usage + typedArray : instanceDataArray, + usage : dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW }); collection._vertexBuffer = vertexBuffer; - collection._typedArray = typedArray; + collection._instanceDataArray = instanceDataArray; collection._createVertexBuffer = false; } else { - vertexBuffer.copyFromArrayView(typedArray); + vertexBuffer.copyFromArrayView(instanceDataArray); } } function updateBoundingSphere(collection) { var points = []; - var instancesLength = collection.instancesLength; + var instancesLength = collection.length; for (var i = 0; i < instancesLength; i++) { var translation = new Cartesian3(); Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); @@ -395,15 +437,14 @@ define([ } function createModel(collection, context) { - var i; var instancingSupported = collection._instancingSupported; var modelOptions = { url : collection._url, - cacheKey : collection._cacheKey, headers : collection._headers, + gltf : collection._gltf, + basePath : collection._basePath, + cacheKey : collection._cacheKey, asynchronous : collection._asynchronous, - debugShowBoundingVolume : collection._debugShowBoundingVolume, - debugWireframe : collection._debugWireframe, allowPicking : collection._allowPicking, precreatedAttributes : undefined, vertexShaderLoaded : undefined, @@ -424,7 +465,7 @@ define([ var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT); var instancedAttributes = {}; - for (i = 0; i < 4; ++i) { + for (var i = 0; i < 4; ++i) { instancedAttributes[attributeNames[i]] = { index : 0, // updated in Model vertexBuffer : collection._vertexBuffer, @@ -440,9 +481,14 @@ define([ collection._instancedAttributes = instancedAttributes; // Instanced models will create different renderer resources, so change the cache key. + var cacheKey = collection._cacheKey; var url = collection._url; - var cacheKey = defaultValue(collection._cacheKey, Model.getDefaultCacheKey(url)); - cacheKey += '(instanced)'; + if (defined(url)) { + cacheKey = defaultValue(cacheKey, Model._getDefaultCacheKey(url)); + } + if (defined(cacheKey)) { + cacheKey += '#instanced'; + } modelOptions.precreatedAttributes = instancedAttributes; modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); @@ -454,7 +500,11 @@ define([ modelOptions.ignoreCommands = true; } - collection._model = Model.fromGltf(modelOptions); + if (defined(collection._url)) { + collection._model = Model.fromGltf(modelOptions); + } else { + collection._model = new Model(modelOptions); + } } function createCommands(collection, drawCommands, pickCommands) { @@ -464,12 +514,12 @@ define([ var j; var command; var commandsLength = drawCommands.length; - var instancesLength = collection.instancesLength; + var instancesLength = collection.length; var allowPicking = collection.allowPicking; var boundingSphere = collection._boundingSphere; var boundingSphereModel = collection._boundingSphereModel; - boundingSphere.radius += collection._model._boundingSphere.radius; + boundingSphere.radius += collection._model.boundingSphere.radius; if (collection._instancingSupported) { for (i = 0; i < commandsLength; ++i) { @@ -507,6 +557,32 @@ define([ } } + function updateWireframe(collection) { + if (collection._debugWireframe !== collection.debugWireframe) { + collection._debugWireframe = collection.debugWireframe; + + // This assumes the original primitive was TRIANGLES and that the triangles + // are connected for the wireframe to look perfect. + var primitiveType = collection.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES; + var commands = collection._drawCommands; + var length = commands.length; + for (var i = 0; i < length; i++) { + commands[i].primitiveType = primitiveType; + } + } + } + function updateShowBoundingVolume(collection) { + if (collection.debugShowBoundingVolume !== collection._debugShowBoundingVolume) { + collection._debugShowBoundingVolume = collection.debugShowBoundingVolume; + + var commands = collection._drawCommands; + var length = commands.length; + for (var i = 0; i < length; i++) { + commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume; + } + } + } + function updateCommands(collection) { // Only applies when instancing is disabled. The instanced shader handles node transformations. if (collection._instancingSupported) { @@ -515,13 +591,13 @@ define([ var modelCommands = collection._modelCommands; var commandsLength = modelCommands.length; - var instancesLength = collection.instancesLength; + var instancesLength = collection.length; var allowPicking = collection.allowPicking; for (var i = 0; i < commandsLength; ++i) { var modelCommand = modelCommands[i]; for (var j = 0; j < instancesLength; ++j) { - var commandIndex = i*instancesLength+j; + var commandIndex = i * instancesLength + j; var drawCommand = collection._drawCommands[commandIndex]; var instanceMatrix = collection._instances[j].modelMatrix; var nodeMatrix = modelCommand.modelMatrix; @@ -562,16 +638,18 @@ define([ }; } + var emptyCommandList = []; + ModelInstanceCollection.prototype.update = function(context, frameState, commandList) { if (frameState.mode !== SceneMode.SCENE3D) { return; } - if (this._show === false) { + if (!this._show) { return; } - if (this.instancesLength === 0) { + if (this.length === 0) { return; } @@ -583,16 +661,16 @@ define([ } var model = this._model; - model.update(context, frameState, []); + model.update(context, frameState, emptyCommandList); - if (model._ready && (this._state === LoadState.LOADING)) { + if (model.ready && (this._state === LoadState.LOADING)) { this._state = LoadState.LOADED; this._ready = true; var modelCommands = getModelCommands(model); createCommands(this, modelCommands.draw, modelCommands.pick); - this._readyPromise.resolve(this); + this.readyPromise.resolve(this); return; } @@ -610,6 +688,9 @@ define([ updateVertexBuffer(this, context); } + updateWireframe(this); + updateShowBoundingVolume(this); + var passes = frameState.passes; var commands; var commandsLength; From b820a55c57921d10b529032784d87e79881b8725 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 2 Oct 2015 17:52:24 -0400 Subject: [PATCH 15/27] Rendering show and color using instanced attributes for now --- .../development/3D Models Instancing.html | 6 +- Source/Renderer/ShaderSource.js | 5 + Source/Scene/ModelInstance.js | 105 ++++++++ Source/Scene/ModelInstanceCollection.js | 254 ++++++++++++------ 4 files changed, 292 insertions(+), 78 deletions(-) create mode 100644 Source/Scene/ModelInstance.js diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index 44506b1eb669..61f7e0721443 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -138,12 +138,16 @@ var pitch = Math.random(); var roll = Math.random(); var scale = (Math.random() + 1.0)/2.0; + var color = Cesium.Color.fromRandom(); + var show = (Math.random() > 0.5) ? true : false; var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(position, heading, pitch, roll); Cesium.Matrix4.multiplyByUniformScale(modelMatrix, scale, modelMatrix); instances.push({ - modelMatrix : modelMatrix + modelMatrix : modelMatrix, + color : color, + show : show }); } } diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js index ba30f1d23b11..f8abc251bd0d 100644 --- a/Source/Renderer/ShaderSource.js +++ b/Source/Renderer/ShaderSource.js @@ -285,6 +285,11 @@ define([ }); }; + ShaderSource.replaceMain = function(source, renamedMain) { + renamedMain = 'void ' + renamedMain + '()'; + return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain); + }; + /** * Create a single string containing the full, combined vertex shader with all dependencies and defines. * diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js new file mode 100644 index 000000000000..4ae01cef870b --- /dev/null +++ b/Source/Scene/ModelInstance.js @@ -0,0 +1,105 @@ +/*global define*/ +define([ + '../Core/clone', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/Matrix4' + ], function( + clone, + Color, + defaultValue, + defined, + defineProperties, + Matrix4) { + "use strict"; + + /** + * A model instance, contained within a ModelInstanceCollection + * + * @private + */ + var ModelInstance = function(options, collection) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this._collection = collection; + this._index = -1; + this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + this._color = options.color; + this._show = defaultValue(options.show, true); + this._pickId = undefined; + this._dirty = false; + }; + + function makeDirty(instance) { + instance._collection._updateInstance(instance); + instance._dirty = true; + } + + defineProperties(ModelInstance.prototype, { + index : { + get : function() { + return this._index; + } + }, + + modelMatrix : { + get : function() { + return this._modelMatrix + }, + set : function(value) { + if (Matrix4.equals(this._modelMatrix, value)) { + this._modelMatrix = Matrix4.clone(value); + makeDirty(this); + } + } + + }, + + show : { + get : function() { + return this._show; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + if (this._show !== value) { + this._show = value; + makeDirty(this); + } + } + }, + + color : { + get : function() { + if (!defined(this._color)) { + this._color = new Color(); + } + return this._color; + }, + set : function(value) { + if (!Color.equals(this._color, value)) { + Color.clone(value, this._color); + makeDirty(this); + } + } + } + }); + + ModelInstance.prototype.getPickId = function(context, model) { + if (!defined(this._pickId)) { + // TODO : change this later + this._pickId = context.createPickId({ + id : 10 + }); + } + + return this._pickId; + }; + + return ModelInstance; +}); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 491708479933..a7db4152f917 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -20,6 +20,7 @@ define([ '../Renderer/ShaderSource', '../ThirdParty/when', './Model', + './ModelInstance', './SceneMode' ], function( BoundingSphere, @@ -42,6 +43,7 @@ define([ ShaderSource, when, Model, + ModelInstance, SceneMode) { "use strict"; @@ -89,20 +91,27 @@ define([ } //>>includeEnd('debug'); - this._instances = defaultValue(options.instances, []); + this._instances = []; + + var instances = defaultValue(options.instances, []); + var length = instances.length; + for (var i = 0; i < length; i++) { + this.add(instances[i]); + } + this._instancingSupported = false; this._dynamic = defaultValue(options.dynamic, false); this._show = defaultValue(options.show, true); - this._allowPicking = defaultValue(options.allowPicking, false); + this._allowPicking = defaultValue(options.allowPicking, true); this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; this._model = undefined; - this._instanceDataArray = undefined; + this._instanceBufferData = undefined; this._vertexBuffer = undefined; this._createVertexBuffer = true; - this._instancedAttributes = undefined; + this._vertexBufferDirty = false; this._instancedUniformsByProgram = undefined; this._drawCommands = []; @@ -147,10 +156,14 @@ define([ */ this.debugWireframe = defaultValue(options.debugWireframe, false); this._debugWireframe = false; - }; defineProperties(ModelInstanceCollection.prototype, { + show : { + get : function() { + return this._show; + } + }, boundingSphere : { get : function() { return this._boundingSphere; @@ -178,6 +191,25 @@ define([ } }); + ModelInstanceCollection.prototype._updateInstance = function(instance) { + // TODO : update the whole buffer for now, but later do sub-commits + this._vertexBufferDirty = true; + }; + + ModelInstanceCollection.prototype.add = function(instance) { + var instance = new ModelInstance(instance, this); + instance._index = this.length; + this._instances.push(instance); + this._vertexBufferDirty = true; + this._createVertexBuffer = true; + + return instance; + }; + + ModelInstanceCollection.prototype.remove = function(instance) { + // TODO : finish later + }; + function getInstancedUniforms(collection, programName) { if (defined(collection._instancedUniformsByProgram)) { return collection._instancedUniformsByProgram[programName]; @@ -212,10 +244,12 @@ define([ if (supportedSemantics.indexOf(semantic) > -1) { uniformMap[uniformName] = semantic; } else { + //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Shader program cannot be optimized for instancing. ' + 'Parameter "' + parameter + '" in program "' + programName + '" uses unsupported semantic "' + semantic + '"' ); + //>>includeEnd('debug'); } } } @@ -234,79 +268,125 @@ define([ return function(vs, programName) { var instancedUniforms = getInstancedUniforms(collection, programName); var dynamic = collection._dynamic; - - // All per-instance uniforms will be replaced with global variables. - var shaderHeader = ''; - var shaderMain = ''; var regex; - // Add instanced attributes - var instancedAttributes = collection._instancedAttributes; - for (var attrName in instancedAttributes) { - if (instancedAttributes.hasOwnProperty(attrName)) { - shaderHeader += 'attribute vec4 ' + attrName + ';\n'; - } - } - - shaderHeader += 'uniform mat4 czm_instanced_nodeLocal;\n'; - shaderHeader += 'mat4 czm_instanced_modelView;\n'; - shaderMain += 'mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n'; - - if (dynamic) { - // czm_instanced_model is the modelView matrix - shaderMain += 'czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeLocal;\n'; - } else { - // czm_instanced_model is the model's local offset from the bounding volume - shaderHeader += 'uniform mat4 czm_instanced_collectionModelView;\n'; - shaderMain += 'czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeLocal;\n'; - } + var renamedSource = ShaderSource.replaceMain(vs, 'gltf_main'); + // TODO : can I add all these global vars and just let the shader compiler optimize them out + var globalVarsHeader = ''; + var globalVarsMain = ''; for (var uniform in instancedUniforms) { if (instancedUniforms.hasOwnProperty(uniform)) { var semantic = instancedUniforms[uniform]; - var varName; if (semantic === 'MODELVIEW') { varName = 'czm_instanced_modelView'; } else if (semantic === 'MODELVIEWPROJECTION') { varName = 'czm_instanced_modelViewProjection'; - shaderHeader += 'mat4 czm_instanced_modelViewProjection;\n'; - shaderMain += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; + globalVarsHeader += 'mat4 czm_instanced_modelViewProjection;\n'; + globalVarsMain += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { varName = 'czm_instanced_modelViewInverseTranspose'; - shaderHeader += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; - shaderMain += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; + globalVarsHeader += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; + globalVarsMain += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; } // Remove the uniform declaration regex = new RegExp('uniform.*' + uniform + '.*'); - vs = vs.replace(regex, ''); + renamedSource = renamedSource.replace(regex, ''); // Replace all occurrences of the uniform with the global variable regex = new RegExp(uniform + '\\b', 'g'); - vs = vs.replace(regex, varName); + renamedSource = renamedSource.replace(regex, varName); } } - // Place the instancing code into the shader - var instancingCode = shaderHeader + 'void main() {\n' + shaderMain; - vs = vs.replace(/void\s+main\s*\([\s\S]*?{/, instancingCode); - - vertexShaderCached = vs; - return vs; + var dataFromAttr = + 'const float SHIFT_LEFT8 = 256.0;\n' + + 'const float SHIFT_RIGHT8 = 1.0 / 256.0;\n' + + '#ifdef RENDER_FOR_PICK\n' + + 'varying vec4 czm_pickColor;\n' + + '#else\n' + + 'varying vec4 czm_color;\n' + + '#endif\n' + + 'attribute vec4 czm_instanceData;\n' + + 'void czm_setColorShow() {\n' + + ' #ifdef RENDER_FOR_PICK\n' + + ' float temp = czm_instanceData.y;\n' + + ' #else\n' + + ' float temp = czm_instanceData.x;\n' + + ' #endif\n' + + ' vec4 color;\n' + + ' temp = temp * SHIFT_RIGHT8;\n' + + ' color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n' + + ' temp = floor(temp) * SHIFT_RIGHT8;\n' + + ' color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n' + + ' color.r = floor(temp);\n' + + ' temp = czm_instanceData.z * SHIFT_RIGHT8;\n' + + ' #ifdef RENDER_FOR_PICK\n' + + ' color.a = (temp - floor(temp)) * SHIFT_LEFT8;\n' + + ' color /= 255.0;\n' + + ' czm_pickColor = color;\n' + + ' #else\n' + + ' color.a = floor(temp);\n' + + ' color /= 255.0;\n' + + ' czm_color = color;\n' + + ' #endif\n' + + ' float show = czm_instanceData.w;\n' + + ' gl_Position *= show;\n' + + '}\n'; + + var dynamicDefine = dynamic ? '#define DYNAMIC\n' : ''; + + var newVS = + dataFromAttr + + dynamicDefine + + globalVarsHeader + + 'attribute vec4 czm_modelMatrixRow0;\n' + + 'attribute vec4 czm_modelMatrixRow1;\n' + + 'attribute vec4 czm_modelMatrixRow2;\n' + + 'uniform mat4 czm_instanced_nodeLocal;\n' + + 'mat4 czm_instanced_modelView;\n' + + '#ifndef DYNAMIC\n' + + 'uniform mat4 czm_instanced_collectionModelView;\n' + + '#endif\n' + + renamedSource + + 'void main()\n' + + '{\n' + + ' mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n' + + ' #ifdef DYNAMIC\n' + // czm_instanced_model is the modelView matrix + ' czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeLocal;\n' + + ' #else\n' + // czm_instanced_model is the model's local offset from the bounding volume + ' czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeLocal;\n' + + ' #endif\n' + + globalVarsMain + + ' gltf_main();\n' + + ' czm_setColorShow();\n' + + '}'; + vertexShaderCached = newVS; + return newVS; }; } + function getFragmentShaderCallback() { + return function(fs) { + var renamedSource = ShaderSource.replaceMain(fs, 'gltf_main'); + var newMain = + 'varying vec4 czm_color;\n' + + 'void main()\n' + + '{\n' + + ' gltf_main();\n' + + ' gl_FragColor.rgb *= czm_color.rgb;\n' + + '}'; + + return renamedSource + '\n' + newMain; + } + } + function getPickVertexShaderCallback() { return function (vs) { // Use the vertex shader that was generated earlier - vs = vertexShaderCached; - var pickCode = - 'varying vec4 czm_pickColor;\n' + - 'void main() {\n' + - 'vec4 pickColor = czm_instanceData;\n' + - 'czm_pickColor = pickColor;\n'; - return vs.replace(/void\s+main\s*\([\s\S]*?{/, pickCode); + return '#define RENDER_FOR_PICK\n' + vertexShaderCached; }; } @@ -355,13 +435,15 @@ define([ } var instanceMatrix = new Matrix4(); + var LEFT_SHIFT16 = 65536.0; // 2^16 + var LEFT_SHIFT8 = 256.0; // 2^8 function updateVertexBuffer(collection, context) { if (!collection._instancingSupported) { return; } - var instanceDataArray = collection._instanceDataArray; + var instanceBufferData = collection._instanceBufferData; var vertexBuffer = collection._vertexBuffer; var createVertexBuffer = collection._createVertexBuffer; @@ -371,13 +453,12 @@ define([ var center = dynamic ? Cartesian3.ZERO : collection._boundingSphere.center; if (createVertexBuffer) { - instanceDataArray = new Float32Array(instancesLength * 16); + instanceBufferData = new Float32Array(instancesLength * 16); } for (var i = 0; i < instancesLength; ++i) { var instance = collection._instances[i]; var modelMatrix = instance.modelMatrix; - var pickColor = defined(instance.pickId) ? instance.pickId.color : Color.WHITE; if (dynamic) { Matrix4.multiplyTransformation(viewMatrix, modelMatrix, instanceMatrix); @@ -385,40 +466,58 @@ define([ instanceMatrix = modelMatrix; } + // TODO : check if has color/pickColor first + var color = instance.color; + var pickColor = instance.getPickId(context).color; + var show = instance.show ? 1.0 : 0.0; + + var red = Color.floatToByte(color.red); + var green = Color.floatToByte(color.green); + var blue = Color.floatToByte(color.blue); + var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; + + red = Color.floatToByte(pickColor.red); + green = Color.floatToByte(pickColor.green); + blue = Color.floatToByte(pickColor.blue); + var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; + + var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT8 + Color.floatToByte(pickColor.alpha); + var compressed3 = show ? 1.0 : 0.0; + var offset = i * 16; // First three rows of the model matrix - instanceDataArray[offset + 0] = instanceMatrix[0]; - instanceDataArray[offset + 1] = instanceMatrix[4]; - instanceDataArray[offset + 2] = instanceMatrix[8]; - instanceDataArray[offset + 3] = instanceMatrix[12] - center.x; - instanceDataArray[offset + 4] = instanceMatrix[1]; - instanceDataArray[offset + 5] = instanceMatrix[5]; - instanceDataArray[offset + 6] = instanceMatrix[9]; - instanceDataArray[offset + 7] = instanceMatrix[13] - center.y; - instanceDataArray[offset + 8] = instanceMatrix[2]; - instanceDataArray[offset + 9] = instanceMatrix[6]; - instanceDataArray[offset + 10] = instanceMatrix[10]; - instanceDataArray[offset + 11] = instanceMatrix[14] - center.z; - - // Other instance data like pickColor, color, etc. Colors can be packed like in BillboardCollection as needed. - instanceDataArray[offset + 12] = pickColor.red; - instanceDataArray[offset + 13] = pickColor.green; - instanceDataArray[offset + 14] = pickColor.blue; - instanceDataArray[offset + 15] = pickColor.alpha; + instanceBufferData[offset + 0] = instanceMatrix[0]; + instanceBufferData[offset + 1] = instanceMatrix[4]; + instanceBufferData[offset + 2] = instanceMatrix[8]; + instanceBufferData[offset + 3] = instanceMatrix[12] - center.x; + instanceBufferData[offset + 4] = instanceMatrix[1]; + instanceBufferData[offset + 5] = instanceMatrix[5]; + instanceBufferData[offset + 6] = instanceMatrix[9]; + instanceBufferData[offset + 7] = instanceMatrix[13] - center.y; + instanceBufferData[offset + 8] = instanceMatrix[2]; + instanceBufferData[offset + 9] = instanceMatrix[6]; + instanceBufferData[offset + 10] = instanceMatrix[10]; + instanceBufferData[offset + 11] = instanceMatrix[14] - center.z; + + // Other instance data: color, pickColor, show + instanceBufferData[offset + 12] = compressed0; + instanceBufferData[offset + 13] = compressed1; + instanceBufferData[offset + 14] = compressed2; + instanceBufferData[offset + 15] = compressed3; } if (createVertexBuffer) { vertexBuffer = Buffer.createVertexBuffer({ context : context, - typedArray : instanceDataArray, + typedArray : instanceBufferData, usage : dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW }); collection._vertexBuffer = vertexBuffer; - collection._instanceDataArray = instanceDataArray; + collection._instanceBufferData = instanceBufferData; collection._createVertexBuffer = false; } else { - vertexBuffer.copyFromArrayView(instanceDataArray); + vertexBuffer.copyFromArrayView(instanceBufferData); } } @@ -478,8 +577,6 @@ define([ }; } - collection._instancedAttributes = instancedAttributes; - // Instanced models will create different renderer resources, so change the cache key. var cacheKey = collection._cacheKey; var url = collection._url; @@ -492,6 +589,7 @@ define([ modelOptions.precreatedAttributes = instancedAttributes; modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(); modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context); modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(); modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(); @@ -539,11 +637,13 @@ define([ } } else { // When instancing is disabled, create commands for every instance. + var instances = collection._instances; for (i = 0; i < commandsLength; ++i) { for (j = 0; j < instancesLength; ++j) { command = clone(drawCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); + //command.show = instances[j].show; TODO : show isn't part of DrawCommand, instead prevent it from being added collection._drawCommands.push(command); if (allowPicking) { @@ -645,7 +745,7 @@ define([ return; } - if (!this._show) { + if (!this.show) { return; } @@ -684,7 +784,7 @@ define([ updateCommands(this); } - if (this._dynamic) { + if (this._dynamic || this._vertexBufferDirty) { updateVertexBuffer(this, context); } From 13d14c07ec7a478c75a76ef7a87df33f3792abe3 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 4 Oct 2015 14:55:30 -0400 Subject: [PATCH 16/27] Support color and show when instancing is disabled --- Source/Scene/Model.js | 47 +++++++++--------- Source/Scene/ModelInstance.js | 6 ++- Source/Scene/ModelInstanceCollection.js | 65 ++++++++++++++++--------- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 7753885af1d8..a4eb8b75e179 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -1361,24 +1361,23 @@ define([ return attributeLocations; } - function getShaderSource(model, shader, programName, callback) { - var source; - + function getShaderSource(model, shader) { if (defined(shader.source)) { - source = shader.source; - } else { - var loadResources = model._loadResources; - var gltf = model.gltf; - var bufferView = gltf.bufferViews[shader.bufferView]; - source = getStringFromTypedArray(loadResources.getBuffer(bufferView)); + return shader.source; } - // Allow callback to modify the shader source + var loadResources = model._loadResources; + var gltf = model.gltf; + var bufferView = gltf.bufferViews[shader.bufferView]; + + return getStringFromTypedArray(loadResources.getBuffer(bufferView)); + } + + function modifyShader(shader, programName, callback) { if (defined(callback)) { - source = callback(source, programName); + shader = callback(shader, programName); } - - return source; + return shader; } function createProgram(name, model, context) { @@ -1387,8 +1386,11 @@ define([ var program = programs[name]; var attributeLocations = createAttributeLocations(program.attributes); - var vs = getShaderSource(model, shaders[program.vertexShader], name, model._vertexShaderLoaded); - var fs = getShaderSource(model, shaders[program.fragmentShader], name, model._fragmentShaderLoaded); + var vs = getShaderSource(model, shaders[program.vertexShader]); + var fs = getShaderSource(model, shaders[program.fragmentShader]); + + var drawVS = modifyShader(vs, name, model._vertexShaderLoaded); + var drawFS = modifyShader(fs, name, model._fragmentShaderLoaded); // Add pre-created attributes to attributeLocations var attributesLength = program.attributes.length; @@ -1403,22 +1405,17 @@ define([ model._rendererResources.programs[name] = ShaderProgram.fromCache({ context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, + vertexShaderSource : drawVS, + fragmentShaderSource : drawFS, attributeLocations : attributeLocations }); if (model.allowPicking) { // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181 - var pickVS; - var pickFS; + var pickVS = modifyShader(vs, name, model._pickVertexShaderLoaded); + var pickFS = modifyShader(fs, name, model._pickFragmentShaderLoaded); - if (defined(model._pickFragmentShaderLoaded)) { - // If a pick fragment shader callback is defined, it overrides model picking - pickVS = getShaderSource(model, shaders[program.vertexShader], name, model._pickVertexShaderLoaded); - pickFS = getShaderSource(model, shaders[program.fragmentShader], name, model._pickFragmentShaderLoaded); - } else { - pickVS = vs; + if (!model._pickFragmentShaderLoaded) { pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); } diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js index 4ae01cef870b..f12d2080d456 100644 --- a/Source/Scene/ModelInstance.js +++ b/Source/Scene/ModelInstance.js @@ -5,6 +5,7 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/DeveloperError', '../Core/Matrix4' ], function( clone, @@ -12,6 +13,7 @@ define([ defaultValue, defined, defineProperties, + DeveloperError, Matrix4) { "use strict"; @@ -45,7 +47,7 @@ define([ modelMatrix : { get : function() { - return this._modelMatrix + return this._modelMatrix; }, set : function(value) { if (Matrix4.equals(this._modelMatrix, value)) { @@ -90,7 +92,7 @@ define([ } }); - ModelInstance.prototype.getPickId = function(context, model) { + ModelInstance.prototype.getPickId = function(context) { if (!defined(this._pickId)) { // TODO : change this later this._pickId = context.createPickId({ diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index a7db4152f917..d775f97f9655 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -95,7 +95,7 @@ define([ var instances = defaultValue(options.instances, []); var length = instances.length; - for (var i = 0; i < length; i++) { + for (var i = 0; i < length; ++i) { this.add(instances[i]); } @@ -368,11 +368,11 @@ define([ }; } - function getFragmentShaderCallback() { + function getFragmentShaderCallback(qualifier) { return function(fs) { var renamedSource = ShaderSource.replaceMain(fs, 'gltf_main'); var newMain = - 'varying vec4 czm_color;\n' + + qualifier + ' vec4 czm_color;\n' + 'void main()\n' + '{\n' + ' gltf_main();\n' + @@ -380,7 +380,7 @@ define([ '}'; return renamedSource + '\n' + newMain; - } + }; } function getPickVertexShaderCallback() { @@ -524,7 +524,7 @@ define([ function updateBoundingSphere(collection) { var points = []; var instancesLength = collection.length; - for (var i = 0; i < instancesLength; i++) { + for (var i = 0; i < instancesLength; ++i) { var translation = new Cartesian3(); Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); points.push(translation); @@ -589,13 +589,15 @@ define([ modelOptions.precreatedAttributes = instancedAttributes; modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); - modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(); + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback('varying'); modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context); modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(); modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(); modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(); modelOptions.cacheKey = cacheKey; modelOptions.ignoreCommands = true; + } else { + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback('uniform'); } if (defined(collection._url)) { @@ -605,7 +607,19 @@ define([ } } - function createCommands(collection, drawCommands, pickCommands) { + function createColorFunction(instance) { + return function() { + return instance.color; + }; + } + + function createPickColorFunction(instance, context) { + return function() { + return instance.getPickId(context).color; + }; + } + + function createCommands(collection, drawCommands, pickCommands, context) { collection._modelCommands = drawCommands; var i; @@ -643,13 +657,16 @@ define([ command = clone(drawCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); - //command.show = instances[j].show; TODO : show isn't part of DrawCommand, instead prevent it from being added + command.uniformMap = clone(command.uniformMap); + command.uniformMap.czm_color = createColorFunction(instances[j]); collection._drawCommands.push(command); if (allowPicking) { command = clone(pickCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); + command.uniformMap = clone(command.uniformMap); + command.uniformMap.czm_pickColor = createPickColorFunction(instances[j], context); collection._pickCommands.push(command); } } @@ -666,7 +683,7 @@ define([ var primitiveType = collection.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES; var commands = collection._drawCommands; var length = commands.length; - for (var i = 0; i < length; i++) { + for (var i = 0; i < length; ++i) { commands[i].primitiveType = primitiveType; } } @@ -677,7 +694,7 @@ define([ var commands = collection._drawCommands; var length = commands.length; - for (var i = 0; i < length; i++) { + for (var i = 0; i < length; ++i) { commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume; } } @@ -768,7 +785,7 @@ define([ this._ready = true; var modelCommands = getModelCommands(model); - createCommands(this, modelCommands.draw, modelCommands.pick); + createCommands(this, modelCommands.draw, modelCommands.pick, context); this.readyPromise.resolve(this); return; @@ -792,21 +809,23 @@ define([ updateShowBoundingVolume(this); var passes = frameState.passes; - var commands; - var commandsLength; + var commands = passes.render ? this._drawCommands : this._pickCommands; + var commandsLength = commands.length; var i; - - if (passes.render) { - commands = this._drawCommands; - commandsLength = commands.length; - for (i = 0; i < commandsLength; i++) { + + if (this._instancingSupported) { + for (i = 0; i < commandsLength; ++i) { commandList.push(commands[i]); } - } else if (passes.pick) { - commands = this._pickCommands; - commandsLength = commands.length; - for (i = 0; i < commandsLength; i++) { - commandList.push(commands[i]); + } else { + // Don't push commands if the instance's show is false + var instances = this._instances; + var instancesLength = this.length; + for (i = 0; i < commandsLength; ++i) { + var instance = instances[i%instancesLength]; + if (instance.show) { + commandList.push(commands[i]); + } } } }; From 77b2547e4c67d30524f367a09ae34ce11de73a39 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 4 Oct 2015 22:47:11 -0400 Subject: [PATCH 17/27] Instancing using batch texture --- Source/Scene/CollectionContent.js | 620 ++++++++++++++++++++++++ Source/Scene/ModelInstance.js | 65 +-- Source/Scene/ModelInstanceCollection.js | 209 ++++---- 3 files changed, 731 insertions(+), 163 deletions(-) create mode 100644 Source/Scene/CollectionContent.js diff --git a/Source/Scene/CollectionContent.js b/Source/Scene/CollectionContent.js new file mode 100644 index 000000000000..541a558a3b43 --- /dev/null +++ b/Source/Scene/CollectionContent.js @@ -0,0 +1,620 @@ +/*global define*/ +define([ + '../Core/Cartesian2', + '../Core/Cartesian4', + '../Core/clone', + '../Core/Color', + '../Core/combine', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/PixelFormat', + '../Renderer/ContextLimits', + '../Renderer/PixelDatatype', + '../Renderer/Sampler', + '../Renderer/ShaderSource', + '../Renderer/Texture', + '../Renderer/TextureMinificationFilter', + '../Renderer/TextureMagnificationFilter' +], function( + Cartesian2, + Cartesian4, + clone, + Color, + combine, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + PixelFormat, + ContextLimits, + PixelDatatype, + Sampler, + ShaderSource, + Texture, + TextureMinificationFilter, + TextureMagnificationFilter) { + "use strict"; + + /** + * Manage batch values like show/color for models within a collection + * + * @private + */ + var CollectionData = function(size) { + this._batchSize = defaultValue(size, 0); + this._batchValues = undefined; // Per-model show/color + this._batchValuesDirty = false; + this._batchTexture = undefined; + this._defaultTexture = undefined; + + this._pickTexture = undefined; + this._pickIds = []; + + this._models = undefined; + + this._batchTable = undefined; + + // Dimensions for batch and pick textures + var textureDimensions; + var textureStep; + + var batchSize = this._batchSize; + if (batchSize > 0) { + // PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case + // when more than one row is needed (e.g., > 16K models in one tile) + var width = Math.min(batchSize, ContextLimits.maximumTextureSize); + var height = Math.ceil(batchSize / ContextLimits.maximumTextureSize); + var stepX = 1.0 / width; + var centerX = stepX * 0.5; + var stepY = 1.0 / height; + var centerY = stepY * 0.5; + + textureDimensions = new Cartesian2(width, height); + textureStep = new Cartesian4(stepX, centerX, stepY, centerY); + } + + this._textureDimensions = textureDimensions; + this._textureStep = textureStep; + }; + + defineProperties(CollectionData.prototype, { + /** + * DOC_TBA + * + * @memberof CollectionData.prototype + * + * @type {Number} + * @readonly + */ + batchSize : { + get : function() { + return this._batchSize; + } + } + }); + + function getByteLength(content) { + var dimensions = content._textureDimensions; + return (dimensions.x * dimensions.y) * 4; + } + + function getBatchValues(content) { + if (!defined(content._batchValues)) { + // Default batch texture to RGBA = 255: white highlight (RGB) and show = true (A). + var byteLength = getByteLength(content); + var bytes = new Uint8Array(byteLength); + for (var i = 0; i < byteLength; ++i) { + bytes[i] = 255; + } + + content._batchValues = bytes; + } + + return content._batchValues; + } + + CollectionData.prototype.setShow = function(batchId, value) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + if (value && !defined(this._batchValues)) { + // Avoid allocating since the default is show = true + return; + } + + var batchValues = getBatchValues(this); + var offset = (batchId * 4) + 3; // Store show/hide in alpha + var newValue = value ? 255 : 0; + + if (batchValues[offset] !== newValue) { + batchValues[offset] = newValue; + this._batchValuesDirty = true; + } + }; + + CollectionData.prototype.getShow = function(batchId) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + //>>includeEnd('debug'); + + if (!defined(this._batchValues)) { + // Batched models default to true + return true; + } + + var offset = (batchId * 4) + 3; // Store show/hide in alpha + return this._batchValues[offset] === 255; + }; + + var scratchColor = new Array(4); + + CollectionData.prototype.setColor = function(batchId, value) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + if (Color.equals(value, Color.WHITE) && !defined(this._batchValues)) { + // Avoid allocating since the default is white + return; + } + + var batchValues = getBatchValues(this); + var offset = batchId * 4; + var newValue = value.toBytes(scratchColor); + + if ((batchValues[offset] !== newValue[0]) || + (batchValues[offset + 1] !== newValue[1]) || + (batchValues[offset + 2] !== newValue[2])) { + batchValues[offset] = newValue[0]; + batchValues[offset + 1] = newValue[1]; + batchValues[offset + 2] = newValue[2]; + this._batchValuesDirty = true; + } + }; + + CollectionData.prototype.setAllColor = function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + var batchSize = this._batchSize; + for (var i = 0; i < batchSize; ++i) { + // PERFORMANCE_IDEA: duplicate part of setColor here to factor things out of the loop + this.setColor(i, value); + } + }; + + CollectionData.prototype.getColor = function(batchId, color) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + + if (!defined(color)) { + throw new DeveloperError('color is required.'); + } + //>>includeEnd('debug'); + + if (!defined(this._batchValues)) { + // Batched models default to WHITE + return Color.clone(Color.WHITE, color); + } + + var batchValues = this._batchValues; + var offset = batchId * 4; + return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], 255, color); + }; + + /////////////////////////////////////////////////////////////////////////// + + CollectionData.prototype.hasProperty = function(name) { + //>>includeStart('debug', pragmas.debug); + if (!defined(name)) { + throw new DeveloperError('name is required.'); + } + //>>includeEnd('debug'); + + var batchTable = this._batchTable; + return defined(batchTable) && defined(batchTable[name]); + }; + + CollectionData.prototype.getPropertyNames = function() { + var names = []; + var batchTable = this._batchTable; + + if (!batchTable) { + return names; + } + + for (var name in batchTable) { + if (batchTable.hasOwnProperty(name)) { + names.push(name); + } + } + + return names; + }; + + CollectionData.prototype.getProperty = function(batchId, name) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + + if (!defined(name)) { + throw new DeveloperError('name is required.'); + } + //>>includeEnd('debug'); + + if (!defined(this._batchTable)) { + return undefined; + } + + var propertyValues = this._batchTable[name]; + + if (!defined(propertyValues)) { + return undefined; + } + + return clone(propertyValues[batchId], true); + }; + + CollectionData.prototype.setProperty = function(batchId, name, value) { + var batchSize = this._batchSize; + //>>includeStart('debug', pragmas.debug); + if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + } + + if (!defined(name)) { + throw new DeveloperError('name is required.'); + } + //>>includeEnd('debug'); + + if (!defined(this._batchTable)) { + // Tile payload did not have a batch table. Create one for new user-defined properties. + this._batchTable = {}; + } + + var propertyValues = this._batchTable[name]; + + if (!defined(propertyValues)) { + // Property does not exist. Create it. + this._batchTable[name] = new Array(batchSize); + propertyValues = this._batchTable[name]; + } + + propertyValues[batchId] = clone(value, true); + }; + + function getGlslComputeSt(content) { + // GLSL batchId is zero-based: [0, batchSize - 1] + if (content._textureDimensions.y === 1) { + return 'uniform vec4 tiles3d_textureStep; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = tiles3d_textureStep.x; \n' + + ' float centerX = tiles3d_textureStep.y; \n' + + ' return vec2(centerX + (batchId * stepX), 0.5); \n' + + '} \n'; + } + + return 'uniform vec4 tiles3d_textureStep; \n' + + 'uniform vec2 tiles3d_textureDimensions; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = tiles3d_textureStep.x; \n' + + ' float centerX = tiles3d_textureStep.y; \n' + + ' float stepY = tiles3d_textureStep.z; \n' + + ' float centerY = tiles3d_textureStep.w; \n' + + ' float xId = mod(batchId, tiles3d_textureDimensions.x); \n' + + ' float yId = floor(batchId / tiles3d_textureDimensions.x); \n' + + ' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' + + '} \n'; + } + + CollectionData.prototype.modifyVertexShader = function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per model (e.g., building) show/hide in the vertex shader + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec3 tiles3d_modelColor; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' vec2 st = computeSt(a_batchId); \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + + ' float show = modelProperties.a; \n' + + ' gl_Position *= show; \n' + // Per batched model show/hide + ' tiles3d_modelColor = modelProperties.rgb; \n' + // Pass batched model color to fragment shader + '}'; + } else { + newMain = + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' tiles3d_modelSt = computeSt(a_batchId); \n' + + '}'; + } + + return renamedSource + '\n' + getGlslComputeSt(this) + newMain; + }; + + CollectionData.prototype.modifyFragmentShader = function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per patched model (e.g., building) show/hide already + // happened in the fragment shader + newMain = + 'varying vec3 tiles3d_modelColor; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' gl_FragColor.rgb *= tiles3d_modelColor; \n' + + '}'; + } else { + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + + ' if (modelProperties.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gltf_main(); \n' + + ' gl_FragColor.rgb *= modelProperties.rgb; \n' + + '}'; + } + + return renamedSource + '\n' + newMain; + }; + + CollectionData.prototype.modifyUniformMap = function(uniformMap) { + var that = this; + var batchUniformMap = { + tiles3d_batchTexture : function() { + // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tiles3d_textureDimensions : function() { + return that._textureDimensions; + }, + tiles3d_textureStep : function() { + return that._textureStep; + } + }; + + return combine(uniformMap, batchUniformMap); + }; + + /////////////////////////////////////////////////////////////////////////// + + CollectionData.prototype.modifyPickVertexShader = function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per batched model (e.g., building) show/hide in the vertex shader + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' vec2 st = computeSt(a_batchId); \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + + ' float show = modelProperties.a; \n' + + ' gl_Position *= show; \n' + // Per batched model show/hide + ' tiles3d_modelSt = st; \n' + + '}'; + } else { + newMain = + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' tiles3d_modelSt = computeSt(a_batchId); \n' + + '}'; + } + + return renamedSource + '\n' + getGlslComputeSt(this) + newMain; + }; + + CollectionData.prototype.modifyPickFragmentShader = function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per batched model (e.g., building) show/hide already + // happened in the fragment shader + newMain = + 'uniform sampler2D tiles3d_pickTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + + '}'; + } else { + newMain = + 'uniform sampler2D tiles3d_pickTexture; \n' + + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + + ' if (modelProperties.a == 0.0) { \n' + + ' discard; \n' + // Per batched model show/hide + ' } \n' + + ' gltf_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + + '}'; + } + + return renamedSource + '\n' + newMain; + }; + + CollectionData.prototype.modifyPickUniformMap = function(uniformMap) { + var that = this; + var batchUniformMap = { + tiles3d_batchTexture : function() { + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tiles3d_textureDimensions : function() { + return that._textureDimensions; + }, + tiles3d_textureStep : function() { + return that._textureStep; + }, + tiles3d_pickTexture : function() { + return that._pickTexture; + } + }; + + return combine(uniformMap, batchUniformMap); + }; + + /////////////////////////////////////////////////////////////////////////// + + function createTexture(content, context, bytes) { + var dimensions = content._textureDimensions; + return new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + source : { + width : dimensions.x, + height : dimensions.y, + arrayBufferView : bytes + }, + sampler : new Sampler({ + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }) + }); + } + + function createPickTexture(content, context) { + var batchSize = content._batchSize; + if (!defined(content._pickTexture) && (batchSize > 0)) { + + var pickIds = content._pickIds; + var byteLength = getByteLength(content); + var bytes = new Uint8Array(byteLength); + + // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating + // a continuous range of pickIds and then converting the base pickId + batchId + // to RGBA in the shader. The only consider is precision issues, which might + // not be an issue in WebGL 2. + for (var i = 0; i < batchSize; ++i) { + var pickId = context.createPickId({id : i}); // TODO : change this + pickIds.push(pickId); + + var pickColor = pickId.color; + var offset = i * 4; + bytes[offset] = Color.floatToByte(pickColor.red); + bytes[offset + 1] = Color.floatToByte(pickColor.green); + bytes[offset + 2] = Color.floatToByte(pickColor.blue); + bytes[offset + 3] = Color.floatToByte(pickColor.alpha); + } + + content._pickTexture = createTexture(content, context, bytes); + } + } + + function createBatchTexture(content, context) { + if (!defined(content._batchTexture)) { + content._batchTexture = createTexture(content, context, content._batchValues); + content._batchValuesDirty = false; + } + } + + function updateBatchTexture(content, context) { + if (content._batchValuesDirty) { + var dimensions = content._textureDimensions; + // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained + // texture updates when less than, for example, 10%, of the values changed. Or + // even just optimize the common case when one model show/color changed. + content._batchTexture.copyFrom({ + width : dimensions.x, + height : dimensions.y, + arrayBufferView : content._batchValues + }); + } + } + + CollectionData.prototype.update = function(context, frameState) { + this._defaultTexture = context.defaultTexture; + + if (frameState.passes.pick) { + // Create pick texture on-demand + createPickTexture(this, context); + } + + if (this._batchValuesDirty) { + // Create batch texture on-demand + createBatchTexture(this, context); + updateBatchTexture(this, context); // Apply per-model show/color updates + + this._batchValuesDirty = false; + } + }; + + CollectionData.prototype.isDestroyed = function() { + return false; + }; + + CollectionData.prototype.destroy = function() { + this._batchTexture = this._batchTexture && this._batchTexture.destroy(); + this._pickTexture = this._pickTexture && this._pickTexture.destroy(); + + var pickIds = this._pickIds; + var length = pickIds.length; + for (var i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + + return destroyObject(this); + }; + + return CollectionData; +}); diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js index f12d2080d456..678b41318803 100644 --- a/Source/Scene/ModelInstance.js +++ b/Source/Scene/ModelInstance.js @@ -1,19 +1,15 @@ /*global define*/ define([ - '../Core/clone', '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError', '../Core/Matrix4' ], function( - clone, Color, defaultValue, defined, defineProperties, - DeveloperError, Matrix4) { "use strict"; @@ -22,29 +18,20 @@ define([ * * @private */ - var ModelInstance = function(options, collection) { + var ModelInstance = function(options, collection, index, batchId) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); this._collection = collection; - this._index = -1; + this._content = collection._content; + this._index = index; + this._batchId = batchId; this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); - this._color = options.color; - this._show = defaultValue(options.show, true); - this._pickId = undefined; - this._dirty = false; - }; + this._color = undefined; // for calling getColor - function makeDirty(instance) { - instance._collection._updateInstance(instance); - instance._dirty = true; - } + this.show = defaultValue(options.show, true); + this.color = defaultValue(options.color, Color.WHITE); + }; defineProperties(ModelInstance.prototype, { - index : { - get : function() { - return this._index; - } - }, - modelMatrix : { get : function() { return this._modelMatrix; @@ -52,27 +39,17 @@ define([ set : function(value) { if (Matrix4.equals(this._modelMatrix, value)) { this._modelMatrix = Matrix4.clone(value); - makeDirty(this); + this._collection._updateInstance(this); } } - }, show : { get : function() { - return this._show; + return this._content.getShow(this._batchId); }, set : function(value) { - //>>includeStart('debug', pragmas.debug); - if (!defined(value)) { - throw new DeveloperError('value is required.'); - } - //>>includeEnd('debug'); - - if (this._show !== value) { - this._show = value; - makeDirty(this); - } + this._content.setShow(this._batchId, value); } }, @@ -81,26 +58,20 @@ define([ if (!defined(this._color)) { this._color = new Color(); } - return this._color; + return this._content.getColor(this._batchId, this._color); }, set : function(value) { - if (!Color.equals(this._color, value)) { - Color.clone(value, this._color); - makeDirty(this); - } + this._content.setColor(this._batchId, value); } } }); - ModelInstance.prototype.getPickId = function(context) { - if (!defined(this._pickId)) { - // TODO : change this later - this._pickId = context.createPickId({ - id : 10 - }); - } + ModelInstance.prototype.getProperty = function(name) { + return this._content.getProperty(this._batchId, name); + }; - return this._pickId; + ModelInstance.prototype.setProperty = function(name, value) { + this._content.setProperty(this._batchId, name, value); }; return ModelInstance; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index d775f97f9655..f87460dea64f 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -19,6 +19,7 @@ define([ '../Renderer/DrawCommand', '../Renderer/ShaderSource', '../ThirdParty/when', + './CollectionContent', './Model', './ModelInstance', './SceneMode' @@ -42,6 +43,7 @@ define([ DrawCommand, ShaderSource, when, + CollectionContent, Model, ModelInstance, SceneMode) { @@ -91,14 +93,6 @@ define([ } //>>includeEnd('debug'); - this._instances = []; - - var instances = defaultValue(options.instances, []); - var length = instances.length; - for (var i = 0; i < length; ++i) { - this.add(instances[i]); - } - this._instancingSupported = false; this._dynamic = defaultValue(options.dynamic, false); this._show = defaultValue(options.show, true); @@ -107,6 +101,15 @@ define([ this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; + var instances = defaultValue(options.instances, []); + var length = instances.length; + this._content = defined(options.content) ? options.content : new CollectionContent(length); + this._instances = new Array(length); + + for (var i = 0; i < length; ++i) { + this._instances[i] = new ModelInstance(instances[i], this, i, i); + } + this._model = undefined; this._instanceBufferData = undefined; this._vertexBuffer = undefined; @@ -192,22 +195,16 @@ define([ }); ModelInstanceCollection.prototype._updateInstance = function(instance) { - // TODO : update the whole buffer for now, but later do sub-commits + // TODO : update the whole buffer for now, but later do sub-commits based on the instance's index this._vertexBufferDirty = true; }; ModelInstanceCollection.prototype.add = function(instance) { - var instance = new ModelInstance(instance, this); - instance._index = this.length; - this._instances.push(instance); + var i = new ModelInstance(instance, this, this.length, this.length); + this._instances.push(i); this._vertexBufferDirty = true; this._createVertexBuffer = true; - - return instance; - }; - - ModelInstanceCollection.prototype.remove = function(instance) { - // TODO : finish later + return i; }; function getInstancedUniforms(collection, programName) { @@ -270,7 +267,7 @@ define([ var dynamic = collection._dynamic; var regex; - var renamedSource = ShaderSource.replaceMain(vs, 'gltf_main'); + var renamedSource = ShaderSource.replaceMain(vs, 'czm_old_main'); // TODO : can I add all these global vars and just let the shader compiler optimize them out var globalVarsHeader = ''; @@ -301,50 +298,15 @@ define([ } } - var dataFromAttr = - 'const float SHIFT_LEFT8 = 256.0;\n' + - 'const float SHIFT_RIGHT8 = 1.0 / 256.0;\n' + - '#ifdef RENDER_FOR_PICK\n' + - 'varying vec4 czm_pickColor;\n' + - '#else\n' + - 'varying vec4 czm_color;\n' + - '#endif\n' + - 'attribute vec4 czm_instanceData;\n' + - 'void czm_setColorShow() {\n' + - ' #ifdef RENDER_FOR_PICK\n' + - ' float temp = czm_instanceData.y;\n' + - ' #else\n' + - ' float temp = czm_instanceData.x;\n' + - ' #endif\n' + - ' vec4 color;\n' + - ' temp = temp * SHIFT_RIGHT8;\n' + - ' color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n' + - ' temp = floor(temp) * SHIFT_RIGHT8;\n' + - ' color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n' + - ' color.r = floor(temp);\n' + - ' temp = czm_instanceData.z * SHIFT_RIGHT8;\n' + - ' #ifdef RENDER_FOR_PICK\n' + - ' color.a = (temp - floor(temp)) * SHIFT_LEFT8;\n' + - ' color /= 255.0;\n' + - ' czm_pickColor = color;\n' + - ' #else\n' + - ' color.a = floor(temp);\n' + - ' color /= 255.0;\n' + - ' czm_color = color;\n' + - ' #endif\n' + - ' float show = czm_instanceData.w;\n' + - ' gl_Position *= show;\n' + - '}\n'; - var dynamicDefine = dynamic ? '#define DYNAMIC\n' : ''; - var newVS = - dataFromAttr + + var newMain = dynamicDefine + globalVarsHeader + 'attribute vec4 czm_modelMatrixRow0;\n' + 'attribute vec4 czm_modelMatrixRow1;\n' + 'attribute vec4 czm_modelMatrixRow2;\n' + + 'attribute float a_batchId;\n' + // TODO : what if the gltf has this attribute already? 'uniform mat4 czm_instanced_nodeLocal;\n' + 'mat4 czm_instanced_modelView;\n' + '#ifndef DYNAMIC\n' + @@ -360,39 +322,45 @@ define([ ' czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeLocal;\n' + ' #endif\n' + globalVarsMain + - ' gltf_main();\n' + - ' czm_setColorShow();\n' + + ' czm_old_main();\n' + '}'; - vertexShaderCached = newVS; - return newVS; + + vertexShaderCached = newMain; + return collection._content.modifyVertexShader(newMain); }; } - function getFragmentShaderCallback(qualifier) { + function getFragmentShaderCallback(collection) { return function(fs) { - var renamedSource = ShaderSource.replaceMain(fs, 'gltf_main'); + return collection._content.modifyFragmentShader(fs); + }; + } + + function getFragmentShaderNonInstancedCallback() { + return function(fs) { + var renamedSource = ShaderSource.replaceMain(fs, 'czm_old_main'); var newMain = - qualifier + ' vec4 czm_color;\n' + + 'uniform vec4 czm_color;\n' + 'void main()\n' + '{\n' + - ' gltf_main();\n' + + ' czm_old_main();\n' + ' gl_FragColor.rgb *= czm_color.rgb;\n' + '}'; return renamedSource + '\n' + newMain; }; } - - function getPickVertexShaderCallback() { + + function getPickVertexShaderCallback(collection) { return function (vs) { // Use the vertex shader that was generated earlier - return '#define RENDER_FOR_PICK\n' + vertexShaderCached; + return collection._content.modifyPickVertexShader(vertexShaderCached); }; } - function getPickFragmentShaderCallback() { + function getPickFragmentShaderCallback(collection) { return function(fs) { - return ShaderSource.createPickFragmentShaderSource(fs, 'varying'); + return collection._content.modifyPickFragmentShader(fs); }; } @@ -423,20 +391,18 @@ define([ } } - return uniformMap; + return collection._content.modifyUniformMap(uniformMap); }; } - function getPickUniformMapCallback() { + function getPickUniformMapCallback(collection) { return function(uniformMap) { uniformMap.czm_pickColor = undefined; - return uniformMap; + return collection._content.modifyPickUniformMap(uniformMap); }; } var instanceMatrix = new Matrix4(); - var LEFT_SHIFT16 = 65536.0; // 2^16 - var LEFT_SHIFT8 = 256.0; // 2^8 function updateVertexBuffer(collection, context) { if (!collection._instancingSupported) { @@ -453,7 +419,7 @@ define([ var center = dynamic ? Cartesian3.ZERO : collection._boundingSphere.center; if (createVertexBuffer) { - instanceBufferData = new Float32Array(instancesLength * 16); + instanceBufferData = new Float32Array(instancesLength * 13); } for (var i = 0; i < instancesLength; ++i) { @@ -466,25 +432,7 @@ define([ instanceMatrix = modelMatrix; } - // TODO : check if has color/pickColor first - var color = instance.color; - var pickColor = instance.getPickId(context).color; - var show = instance.show ? 1.0 : 0.0; - - var red = Color.floatToByte(color.red); - var green = Color.floatToByte(color.green); - var blue = Color.floatToByte(color.blue); - var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; - - red = Color.floatToByte(pickColor.red); - green = Color.floatToByte(pickColor.green); - blue = Color.floatToByte(pickColor.blue); - var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; - - var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT8 + Color.floatToByte(pickColor.alpha); - var compressed3 = show ? 1.0 : 0.0; - - var offset = i * 16; + var offset = i * 13; // First three rows of the model matrix instanceBufferData[offset + 0] = instanceMatrix[0]; @@ -499,12 +447,7 @@ define([ instanceBufferData[offset + 9] = instanceMatrix[6]; instanceBufferData[offset + 10] = instanceMatrix[10]; instanceBufferData[offset + 11] = instanceMatrix[14] - center.z; - - // Other instance data: color, pickColor, show - instanceBufferData[offset + 12] = compressed0; - instanceBufferData[offset + 13] = compressed1; - instanceBufferData[offset + 14] = compressed2; - instanceBufferData[offset + 15] = compressed3; + instanceBufferData[offset + 12] = instance._batchId; } if (createVertexBuffer) { @@ -560,22 +503,49 @@ define([ if (instancingSupported) { updateVertexBuffer(collection, context); - var attributeNames = ['czm_modelMatrixRow0', 'czm_modelMatrixRow1', 'czm_modelMatrixRow2', 'czm_instanceData']; var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT); - var instancedAttributes = {}; - - for (var i = 0; i < 4; ++i) { - instancedAttributes[attributeNames[i]] = { + var instancedAttributes = { + czm_modelMatrixRow0 : { index : 0, // updated in Model vertexBuffer : collection._vertexBuffer, componentsPerAttribute : 4, componentDatatype : ComponentDatatype.FLOAT, normalize : false, - offsetInBytes : componentSizeInBytes * 4 * i, - strideInBytes : componentSizeInBytes * 16, + offsetInBytes : 0, + strideInBytes : componentSizeInBytes * 13, instanceDivisor : 1 - }; - } + }, + czm_modelMatrixRow1 : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 4, + strideInBytes : componentSizeInBytes * 13, + instanceDivisor : 1 + }, + czm_modelMatrixRow2 : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 8, + strideInBytes : componentSizeInBytes * 13, + instanceDivisor : 1 + }, + a_batchId : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 1, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 12, + strideInBytes : componentSizeInBytes * 13, + instanceDivisor : 1 + } + }; // Instanced models will create different renderer resources, so change the cache key. var cacheKey = collection._cacheKey; @@ -589,15 +559,15 @@ define([ modelOptions.precreatedAttributes = instancedAttributes; modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); - modelOptions.fragmentShaderLoaded = getFragmentShaderCallback('varying'); + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection); modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context); - modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(); - modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(); - modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(); + modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(collection); + modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(collection); + modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); modelOptions.cacheKey = cacheKey; modelOptions.ignoreCommands = true; } else { - modelOptions.fragmentShaderLoaded = getFragmentShaderCallback('uniform'); + modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(); } if (defined(collection._url)) { @@ -770,13 +740,18 @@ define([ return; } - this._instancingSupported = context.instancedArrays; + var instancingSupported = context.instancedArrays; + this._instancingSupported = instancingSupported; if (this._state === LoadState.NEEDS_LOAD) { this._state = LoadState.LOADING; createModel(this, context); } + if (instancingSupported) { + this._content.update(context, frameState); + } + var model = this._model; model.update(context, frameState, emptyCommandList); @@ -803,6 +778,7 @@ define([ if (this._dynamic || this._vertexBufferDirty) { updateVertexBuffer(this, context); + this._vertexBufferDirty = false; } updateWireframe(this); @@ -813,7 +789,7 @@ define([ var commandsLength = commands.length; var i; - if (this._instancingSupported) { + if (instancingSupported) { for (i = 0; i < commandsLength; ++i) { commandList.push(commands[i]); } @@ -863,6 +839,7 @@ define([ */ ModelInstanceCollection.prototype.destroy = function() { this._model.destroy(); + this._content.destroy(); return destroyObject(this); }; From bab0ba9874efd03aed641d874337d05d19a6dbf7 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 5 Oct 2015 17:54:55 -0400 Subject: [PATCH 18/27] Added instanced model 3d tile type --- .../3D Models Instancing 3D Tiles.html | 88 +++ .../development/3D Models Instancing.html | 4 +- .../Batched3DModel3DTileContentProvider.js | 727 +----------------- Source/Scene/BatchedModel.js | 16 +- ...ionContent.js => Cesium3DTileBatchData.js} | 468 ++++++----- .../Cesium3DTileContentProviderFactory.js | 5 + .../Instanced3DModel3DTileContentProvider.js | 233 ++++++ Source/Scene/ModelInstance.js | 20 +- Source/Scene/ModelInstanceCollection.js | 135 ++-- 9 files changed, 696 insertions(+), 1000 deletions(-) create mode 100644 Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html rename Source/Scene/{CollectionContent.js => Cesium3DTileBatchData.js} (50%) create mode 100644 Source/Scene/Instanced3DModel3DTileContentProvider.js diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html b/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html new file mode 100644 index 000000000000..c5b7033a3a41 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html @@ -0,0 +1,88 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index 61f7e0721443..5d9a5dc5b18c 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -78,7 +78,7 @@ speedup : 0.5, loop : Cesium.ModelAnimationLoop.REPEAT }); - orientCamera(collection.boundingSphere.center, collection.boundingSphere.radius); + orientCamera(collection._boundingVolume.center, collection._boundingVolume.radius); }).otherwise(function(error){ window.alert(error); }); @@ -139,7 +139,7 @@ var roll = Math.random(); var scale = (Math.random() + 1.0)/2.0; var color = Cesium.Color.fromRandom(); - var show = (Math.random() > 0.5) ? true : false; + var show = (Math.random() > 0.5); var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(position, heading, pitch, roll); Cesium.Matrix4.multiplyByUniformScale(modelMatrix, scale, modelMatrix); diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index 2e66c3ae5fe2..c36f58b9eb44 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -1,10 +1,5 @@ /*global define*/ define([ - '../Core/Cartesian2', - '../Core/Cartesian4', - '../Core/clone', - '../Core/Color', - '../Core/combine', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -12,25 +7,12 @@ define([ '../Core/DeveloperError', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', - '../Core/PixelFormat', - '../Renderer/Context', - '../Renderer/ContextLimits', - '../Renderer/PixelDatatype', - '../Renderer/Sampler', - '../Renderer/ShaderSource', - '../Renderer/Texture', - '../Renderer/TextureMinificationFilter', - '../Renderer/TextureMagnificationFilter', + './Cesium3DTileBatchData', './Cesium3DTileContentState', './Model', './BatchedModel', '../ThirdParty/when' ], function( - Cartesian2, - Cartesian4, - clone, - Color, - combine, defaultValue, defined, defineProperties, @@ -38,15 +20,7 @@ define([ DeveloperError, getStringFromTypedArray, loadArrayBuffer, - PixelFormat, - Context, - ContextLimits, - PixelDatatype, - Sampler, - ShaderSource, - Texture, - TextureMinificationFilter, - TextureMagnificationFilter, + Cesium3DTileBatchData, Cesium3DTileContentState, Model, BatchedModel, @@ -77,41 +51,9 @@ define([ this.readyPromise = when.defer(); var batchSize = defaultValue(contentHeader.batchSize, 0); - this._batchSize = batchSize; // Number of models, e.g., buildings, batched into the glTF model. - this._batchValues = undefined; // Per-model show/color - this._batchValuesDirty = false; - this._batchTexture = undefined; - this._defaultTexture = undefined; - - this._pickTexture = undefined; - this._pickIds = []; - - this._batchTable = undefined; + this._batchSize = batchSize; + this._batchData = new Cesium3DTileBatchData(this, batchSize); this._models = undefined; - - // Dimensions for batch and pick textures - var textureDimensions; - var textureStep; - - if (batchSize > 0) { - // PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case - // when more than one row is needed (e.g., > 16K models in one tile) - var width = Math.min(batchSize, ContextLimits.maximumTextureSize); - var height = Math.ceil(batchSize / ContextLimits.maximumTextureSize); - var stepX = 1.0 / width; - var centerX = stepX * 0.5; - var stepY = 1.0 / height; - var centerY = stepY * 0.5; - - textureDimensions = new Cartesian2(width, height); - textureStep = new Cartesian4(stepX, centerX, stepY, centerY); - } - - this._textureDimensions = textureDimensions; - this._textureStep = textureStep; - - this._debugColor = Color.fromRandom({ alpha : 1.0 }); - this._debugColorizeTiles = false; }; defineProperties(Batched3DModel3DTileContentProvider.prototype, { @@ -130,273 +72,13 @@ define([ } }); - function getByteLength(content) { - var dimensions = content._textureDimensions; - return (dimensions.x * dimensions.y) * 4; - } - - function getBatchValues(content) { - if (!defined(content._batchValues)) { - // Default batch texture to RGBA = 255: white highlight (RGB) and show = true (A). - var byteLength = getByteLength(content); - var bytes = new Uint8Array(byteLength); - for (var i = 0; i < byteLength; ++i) { - bytes[i] = 255; - } - - content._batchValues = bytes; - } - - return content._batchValues; - } - - /** - * Use BatchedModel#show - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.setShow = function(batchId, value) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - - if (!defined(value)) { - throw new DeveloperError('value is required.'); - } - //>>includeEnd('debug'); - - if (value && !defined(this._batchValues)) { - // Avoid allocating since the default is show = true - return; - } - - var batchValues = getBatchValues(this); - var offset = (batchId * 4) + 3; // Store show/hide in alpha - var newValue = value ? 255 : 0; - - if (batchValues[offset] !== newValue) { - batchValues[offset] = newValue; - this._batchValuesDirty = true; - } - }; - - /** - * Use BatchedModel#show - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.getShow = function(batchId) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - //>>includeEnd('debug'); - - if (!defined(this._batchValues)) { - // Batched models default to true - return true; - } - - var offset = (batchId * 4) + 3; // Store show/hide in alpha - return this._batchValues[offset] === 255; - }; - - var scratchColor = new Array(4); - - /** - * Use BatchedModel#color - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.setColor = function(batchId, value) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - - if (!defined(value)) { - throw new DeveloperError('value is required.'); - } - //>>includeEnd('debug'); - - if (Color.equals(value, Color.WHITE) && !defined(this._batchValues)) { - // Avoid allocating since the default is white - return; - } - - var batchValues = getBatchValues(this); - var offset = batchId * 4; - var newValue = value.toBytes(scratchColor); - - if ((batchValues[offset] !== newValue[0]) || - (batchValues[offset + 1] !== newValue[1]) || - (batchValues[offset + 2] !== newValue[2])) { - batchValues[offset] = newValue[0]; - batchValues[offset + 1] = newValue[1]; - batchValues[offset + 2] = newValue[2]; - this._batchValuesDirty = true; - } - }; - - /** - * @private - */ - Batched3DModel3DTileContentProvider.prototype.setAllColor = function(value) { - //>>includeStart('debug', pragmas.debug); - if (!defined(value)) { - throw new DeveloperError('value is required.'); - } - //>>includeEnd('debug'); - - var batchSize = this._batchSize; - for (var i = 0; i < batchSize; ++i) { - // PERFORMANCE_IDEA: duplicate part of setColor here to factor things out of the loop - this.setColor(i, value); - } - }; - - /** - * Use BatchedModel#color - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.getColor = function(batchId, color) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - - if (!defined(color)) { - throw new DeveloperError('color is required.'); - } - //>>includeEnd('debug'); - - if (!defined(this._batchValues)) { - // Batched models default to WHITE - return Color.clone(Color.WHITE, color); - } - - var batchValues = this._batchValues; - var offset = batchId * 4; - return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], 255, color); - }; - - /////////////////////////////////////////////////////////////////////////// - - /** - * DOC_TBA - */ - Batched3DModel3DTileContentProvider.prototype.hasProperty = function(name) { - //>>includeStart('debug', pragmas.debug); - if (!defined(name)) { - throw new DeveloperError('name is required.'); - } - //>>includeEnd('debug'); - - var batchTable = this._batchTable; - return defined(batchTable) && defined(batchTable[name]); - }; - - /** - * DOC_TBA - * - * This may return different values from call to call if {@link BatchedModel#setProperty} - * is used to add new properties. - */ - Batched3DModel3DTileContentProvider.prototype.getPropertyNames = function() { - var names = []; - var batchTable = this._batchTable; - - if (!batchTable) { - return names; - } - - for (var name in batchTable) { - if (batchTable.hasOwnProperty(name)) { - names.push(name); - } - } - - return names; - }; - - /** - * Use BatchedModel#getProperty - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.getProperty = function(batchId, name) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - - if (!defined(name)) { - throw new DeveloperError('name is required.'); - } - //>>includeEnd('debug'); - - if (!defined(this._batchTable)) { - return undefined; - } - - var propertyValues = this._batchTable[name]; - - if (!defined(propertyValues)) { - return undefined; - } - - return clone(propertyValues[batchId], true); - }; - - /** - * Use BatchedModel#setProperty - * - * @private - */ - Batched3DModel3DTileContentProvider.prototype.setProperty = function(batchId, name, value) { - var batchSize = this._batchSize; - //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); - } - - if (!defined(name)) { - throw new DeveloperError('name is required.'); - } - //>>includeEnd('debug'); - - if (!defined(this._batchTable)) { - // Tile payload did not have a batch table. Create one for new user-defined properties. - this._batchTable = {}; - } - - var propertyValues = this._batchTable[name]; - - if (!defined(propertyValues)) { - // Property does not exist. Create it. - this._batchTable[name] = new Array(batchSize); - propertyValues = this._batchTable[name]; - } - - propertyValues[batchId] = clone(value, true); - }; - - /////////////////////////////////////////////////////////////////////////// - function createModels(content) { var tileset = content._tileset; var batchSize = content._batchSize; if (!defined(content._models) && (batchSize > 0)) { var models = new Array(batchSize); for (var i = 0; i < batchSize; ++i) { - models[i] = new BatchedModel(tileset, content, i); + models[i] = new BatchedModel(tileset, content._batchData, i); } content._models = models; } @@ -408,8 +90,8 @@ define([ Batched3DModel3DTileContentProvider.prototype.getModel = function(batchId) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); - if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { - throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + batchSize - + ').'); + if (!defined(batchId) || (batchId < 0) || (batchId >= batchSize)) { + throw new DeveloperError('batchId is required and between zero and batchSize - 1 (' + (batchSize - 1) + ').'); } //>>includeEnd('debug'); @@ -417,277 +99,7 @@ define([ return this._models[batchId]; }; - /////////////////////////////////////////////////////////////////////////// - - function getGlslComputeSt(content) { - // GLSL batchId is zero-based: [0, batchSize - 1] - if (content._textureDimensions.y === 1) { - return 'uniform vec4 tiles3d_textureStep; \n' + - 'vec2 computeSt(float batchId) \n' + - '{ \n' + - ' float stepX = tiles3d_textureStep.x; \n' + - ' float centerX = tiles3d_textureStep.y; \n' + - ' return vec2(centerX + (batchId * stepX), 0.5); \n' + - '} \n'; - } - - return 'uniform vec4 tiles3d_textureStep; \n' + - 'uniform vec2 tiles3d_textureDimensions; \n' + - 'vec2 computeSt(float batchId) \n' + - '{ \n' + - ' float stepX = tiles3d_textureStep.x; \n' + - ' float centerX = tiles3d_textureStep.y; \n' + - ' float stepY = tiles3d_textureStep.z; \n' + - ' float centerY = tiles3d_textureStep.w; \n' + - ' float xId = mod(batchId, tiles3d_textureDimensions.x); \n' + - ' float yId = floor(batchId / tiles3d_textureDimensions.x); \n' + - ' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' + - '} \n'; - } - - function getVertexShaderCallback(content) { - if (content._batchSize === 0) { - // Do not change vertex shader source; the model was not batched - return undefined; - } - - return function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, perform per patched model (e.g., building) show/hide in the vertex shader - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec3 tiles3d_modelColor; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' vec2 st = computeSt(a_batchId); \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + - ' float show = modelProperties.a; \n' + - ' gl_Position *= show; \n' + // Per batched model show/hide - ' tiles3d_modelColor = modelProperties.rgb; \n' + // Pass batched model color to fragment shader - '}'; - } else { - newMain = - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' tiles3d_modelSt = computeSt(a_batchId); \n' + - '}'; - } - - return renamedSource + '\n' + getGlslComputeSt(content) + newMain; - }; - } - - function getFragmentShaderCallback(content) { - if (content._batchSize === 0) { - // Do not change fragment shader source; the model was not batched - return undefined; - } - - return function(source) { -// TODO: generate entire shader at runtime? -// var diffuse = 'diffuse = u_diffuse;'; -// var diffuseTexture = 'diffuse = texture2D(u_diffuse, v_texcoord0);'; -// if (ContextLimits.maximumVertexTextureImageUnits > 0) { -// source = 'varying vec3 tiles3d_modelColor; \n' + source; -// source = source.replace(diffuse, 'diffuse.rgb = tiles3d_modelColor;'); -// source = source.replace(diffuseTexture, 'diffuse.rgb = texture2D(u_diffuse, v_texcoord0).rgb * tiles3d_modelColor;'); -// } else { -// source = -// 'uniform sampler2D tiles3d_batchTexture; \n' + -// 'varying vec2 tiles3d_modelSt; \n' + -// source; -// -// var readColor = -// 'vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + -// 'if (modelProperties.a == 0.0) { \n' + -// ' discard; \n' + -// '}'; -// -// source = source.replace(diffuse, readColor + 'diffuse.rgb = modelProperties.rgb;'); -// source = source.replace(diffuseTexture, readColor + 'diffuse.rgb = texture2D(u_diffuse, v_texcoord0).rgb * modelProperties.rgb;'); -// } -// -// return source; - -// TODO: support both "replace" and "highlight" color? Highlight is commented out below, replace is above - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, per patched model (e.g., building) show/hide already - // happened in the fragment shader - newMain = - 'varying vec3 tiles3d_modelColor; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' gl_FragColor.rgb *= tiles3d_modelColor; \n' + - '}'; - } else { - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + - ' if (modelProperties.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gltf_main(); \n' + - ' gl_FragColor.rgb *= modelProperties.rgb; \n' + - '}'; - } - - return renamedSource + '\n' + newMain; - }; - } - - function getUniformMapCallback(content) { - if (content._batchSize === 0) { - // Do not change the uniform map; the model was not batched - return undefined; - } - - return function(uniformMap) { - var batchUniformMap = { - tiles3d_batchTexture : function() { - // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. - return defaultValue(content._batchTexture, content._defaultTexture); - }, - tiles3d_textureDimensions : function() { - return content._textureDimensions; - }, - tiles3d_textureStep : function() { - return content._textureStep; - } - }; - - return combine(uniformMap, batchUniformMap); - }; - } - - /////////////////////////////////////////////////////////////////////////// - - function getPickVertexShaderCallback(content) { - if (content._batchSize === 0) { - // Do not change vertex shader source; the model was not batched - return undefined; - } - - return function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, perform per patched model (e.g., building) show/hide in the vertex shader - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' vec2 st = computeSt(a_batchId); \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + - ' float show = modelProperties.a; \n' + - ' gl_Position *= show; \n' + // Per batched model show/hide - ' tiles3d_modelSt = st; \n' + - '}'; - } else { - newMain = - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' tiles3d_modelSt = computeSt(a_batchId); \n' + - '}'; - } - - return renamedSource + '\n' + getGlslComputeSt(content) + newMain; - }; - } - - function getPickFragmentShaderCallback(content) { - if (content._batchSize === 0) { - // Do not change fragment shader source; the model was not batched - return undefined; - } - - return function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, per patched model (e.g., building) show/hide already - // happened in the fragment shader - newMain = - 'uniform sampler2D tiles3d_pickTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' if (gl_FragColor.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + - '}'; - } else { - newMain = - 'uniform sampler2D tiles3d_pickTexture; \n' + - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + - ' if (modelProperties.a == 0.0) { \n' + - ' discard; \n' + // Per batched model show/hide - ' } \n' + - ' gltf_main(); \n' + - ' if (gl_FragColor.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + - '}'; - } - - return renamedSource + '\n' + newMain; - }; - } - - function getPickUniformMapCallback(content) { - if (content._batchSize === 0) { - // Do not change the uniform map; the model was not batched - return undefined; - } - - return function(uniformMap) { - var batchUniformMap = { - tiles3d_batchTexture : function() { - return defaultValue(content._batchTexture, content._defaultTexture); - }, - tiles3d_textureDimensions : function() { - return content._textureDimensions; - }, - tiles3d_textureStep : function() { - return content._textureStep; - }, - tiles3d_pickTexture : function() { - return content._pickTexture; - } - }; - - return combine(uniformMap, batchUniformMap); - }; - } - - /////////////////////////////////////////////////////////////////////////// - -// TODO: move this and the copy in Model.js to an overload for getStringFromTypedArray + // TODO: move this and the copy in Model.js to an overload for getStringFromTypedArray function getSubarray(array, offset, length) { return array.subarray(offset, offset + length); } @@ -701,6 +113,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; + var batchData = this._batchData; this.state = Cesium3DTileContentState.LOADING; @@ -740,7 +153,7 @@ define([ // // We could also make another request for it, but that would make the property set/get // API async, and would double the number of numbers in some cases. - that._batchTable = JSON.parse(batchTableString); + batchData._batchTable = JSON.parse(batchTableString); } var gltfView = new Uint8Array(arrayBuffer, byteOffset, arrayBuffer.byteLength - byteOffset); @@ -751,12 +164,12 @@ define([ gltf : gltfView, cull : false, // The model is already culled by the 3D tiles releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory - vertexShaderLoaded : getVertexShaderCallback(that), - fragmentShaderLoaded : getFragmentShaderCallback(that), - uniformMapLoaded : getUniformMapCallback(that), - pickVertexShaderLoaded : getPickVertexShaderCallback(that), - pickFragmentShaderLoaded : getPickFragmentShaderCallback(that), - pickUniformMapLoaded : getPickUniformMapCallback(that), + vertexShaderLoaded : batchData.getVertexShaderCallback(), + fragmentShaderLoaded : batchData.getFragmentShaderCallback(), + uniformMapLoaded : batchData.getUniformMapCallback(), + pickVertexShaderLoaded : batchData.getPickVertexShaderCallback(), + pickFragmentShaderLoaded : batchData.getPickFragmentShaderCallback(), + pickUniformMapLoaded : batchData.getPickUniformMapCallback(), basePath : that._url }); @@ -771,87 +184,6 @@ define([ }).otherwise(failRequest); }; - function applyDebugSettings(owner, content) { - if (content.state === Cesium3DTileContentState.READY) { - if (owner.debugColorizeTiles && !content._debugColorizeTiles) { - content._debugColorizeTiles = true; - content.setAllColor(content._debugColor); - } else if (!owner.debugColorizeTiles && content._debugColorizeTiles) { - content._debugColorizeTiles = false; - content.setAllColor(Color.WHITE); - } - } - } - - function createTexture(content, context, bytes) { - var dimensions = content._textureDimensions; - return new Texture({ - context : context, - pixelFormat : PixelFormat.RGBA, - pixelDatatype : PixelDatatype.UNSIGNED_BYTE, - source : { - width : dimensions.x, - height : dimensions.y, - arrayBufferView : bytes - }, - sampler : new Sampler({ - minificationFilter : TextureMinificationFilter.NEAREST, - magnificationFilter : TextureMagnificationFilter.NEAREST - }) - }); - } - - function createPickTexture(content, context) { - var batchSize = content._batchSize; - if (!defined(content._pickTexture) && (batchSize > 0)) { - createModels(content); - var models = content._models; - - var pickIds = content._pickIds; - var byteLength = getByteLength(content); - var bytes = new Uint8Array(byteLength); - - // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating - // a continuous range of pickIds and then converting the base pickId + batchId - // to RGBA in the shader. The only consider is precision issues, which might - // not be an issue in WebGL 2. - for (var i = 0; i < batchSize; ++i) { - var pickId = context.createPickId(models[i]); - pickIds.push(pickId); - - var pickColor = pickId.color; - var offset = i * 4; - bytes[offset] = Color.floatToByte(pickColor.red); - bytes[offset + 1] = Color.floatToByte(pickColor.green); - bytes[offset + 2] = Color.floatToByte(pickColor.blue); - bytes[offset + 3] = Color.floatToByte(pickColor.alpha); - } - - content._pickTexture = createTexture(content, context, bytes); - } - } - - function createBatchTexture(content, context) { - if (!defined(content._batchTexture)) { - content._batchTexture = createTexture(content, context, content._batchValues); - content._batchValuesDirty = false; - } - } - - function updateBatchTexture(content, context) { - if (content._batchValuesDirty) { - var dimensions = content._textureDimensions; - // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained - // texture updates when less than, for example, 10%, of the values changed. Or - // even just optimize the common case when one model show/color changed. - content._batchTexture.copyFrom({ - width : dimensions.x, - height : dimensions.y, - arrayBufferView : content._batchValues - }); - } - } - /** * DOC_TBA * @@ -862,23 +194,7 @@ define([ // the content's resource loading. In the READY state, it will // actually generate commands. - applyDebugSettings(owner, this); - - this._defaultTexture = context.defaultTexture; - - if (frameState.passes.pick) { - // Create pick texture on-demand - createPickTexture(this, context); - } - - if (this._batchValuesDirty) { - // Create batch texture on-demand - createBatchTexture(this, context); - updateBatchTexture(this, context); // Apply per-model show/color updates - - this._batchValuesDirty = false; - } - + this._batchData.update(context, frameState); this._model.update(context, frameState, commandList); }; @@ -894,14 +210,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.destroy = function() { this._model = this._model && this._model.destroy(); - this._batchTexture = this._batchTexture && this._batchTexture.destroy(); - this._pickTexture = this._pickTexture && this._pickTexture.destroy(); - - var pickIds = this._pickIds; - var length = pickIds.length; - for (var i = 0; i < length; ++i) { - pickIds[i].destroy(); - } + this._batchData = this._batchData && this._batchData.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/BatchedModel.js b/Source/Scene/BatchedModel.js index 0eb6e0a7941d..709d53c9a06a 100644 --- a/Source/Scene/BatchedModel.js +++ b/Source/Scene/BatchedModel.js @@ -10,8 +10,8 @@ define([ /** * DOC_TBA */ - var BatchedModel = function(tileset, content, batchId) { - this._content = content; + var BatchedModel = function(tileset, batchData, batchId) { + this._batchData = batchData; this._batchId = batchId; this._color = undefined; // for calling getColor @@ -29,10 +29,10 @@ define([ */ show : { get : function() { - return this._content.getShow(this._batchId); + return this._batchData.getShow(this._batchId); }, set : function(value) { - this._content.setShow(this._batchId, value); + this._batchData.setShow(this._batchId, value); } }, @@ -44,10 +44,10 @@ define([ if (!this._color) { this._color = new Color(); } - return this._content.getColor(this._batchId, this._color); + return this._batchData.getColor(this._batchId, this._color); }, set : function(value) { - this._content.setColor(this._batchId, value); + this._batchData.setColor(this._batchId, value); } } }); @@ -56,14 +56,14 @@ define([ * DOC_TBA */ BatchedModel.prototype.getProperty = function(name) { - return this._content.getProperty(this._batchId, name); + return this._batchData.getProperty(this._batchId, name); }; /** * DOC_TBA */ BatchedModel.prototype.setProperty = function(name, value) { - this._content.setProperty(this._batchId, name, value); + this._batchData.setProperty(this._batchId, name, value); }; return BatchedModel; diff --git a/Source/Scene/CollectionContent.js b/Source/Scene/Cesium3DTileBatchData.js similarity index 50% rename from Source/Scene/CollectionContent.js rename to Source/Scene/Cesium3DTileBatchData.js index 541a558a3b43..3ded2b70369d 100644 --- a/Source/Scene/CollectionContent.js +++ b/Source/Scene/Cesium3DTileBatchData.js @@ -44,7 +44,7 @@ define([ * * @private */ - var CollectionData = function(size) { + var Cesium3DTileBatchData = function(owner, size) { this._batchSize = defaultValue(size, 0); this._batchValues = undefined; // Per-model show/color this._batchValuesDirty = false; @@ -53,10 +53,9 @@ define([ this._pickTexture = undefined; this._pickIds = []; - - this._models = undefined; - + this._batchTable = undefined; + this._owner = owner; // TODO : is owner the right name for ContentProvider? // Dimensions for batch and pick textures var textureDimensions; @@ -81,11 +80,11 @@ define([ this._textureStep = textureStep; }; - defineProperties(CollectionData.prototype, { + defineProperties(Cesium3DTileBatchData.prototype, { /** * DOC_TBA * - * @memberof CollectionData.prototype + * @memberof Cesium3DTileBatchData.prototype * * @type {Number} * @readonly @@ -97,27 +96,27 @@ define([ } }); - function getByteLength(content) { - var dimensions = content._textureDimensions; + function getByteLength(batchData) { + var dimensions = batchData._textureDimensions; return (dimensions.x * dimensions.y) * 4; } - function getBatchValues(content) { - if (!defined(content._batchValues)) { + function getBatchValues(batchData) { + if (!defined(batchData._batchValues)) { // Default batch texture to RGBA = 255: white highlight (RGB) and show = true (A). - var byteLength = getByteLength(content); + var byteLength = getByteLength(batchData); var bytes = new Uint8Array(byteLength); for (var i = 0; i < byteLength; ++i) { bytes[i] = 255; } - content._batchValues = bytes; + batchData._batchValues = bytes; } - return content._batchValues; + return batchData._batchValues; } - CollectionData.prototype.setShow = function(batchId, value) { + Cesium3DTileBatchData.prototype.setShow = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -144,7 +143,7 @@ define([ } }; - CollectionData.prototype.getShow = function(batchId) { + Cesium3DTileBatchData.prototype.getShow = function(batchId) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -163,7 +162,7 @@ define([ var scratchColor = new Array(4); - CollectionData.prototype.setColor = function(batchId, value) { + Cesium3DTileBatchData.prototype.setColor = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -194,7 +193,7 @@ define([ } }; - CollectionData.prototype.setAllColor = function(value) { + Cesium3DTileBatchData.prototype.setAllColor = function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError('value is required.'); @@ -208,7 +207,7 @@ define([ } }; - CollectionData.prototype.getColor = function(batchId, color) { + Cesium3DTileBatchData.prototype.getColor = function(batchId, color) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -230,9 +229,7 @@ define([ return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], 255, color); }; - /////////////////////////////////////////////////////////////////////////// - - CollectionData.prototype.hasProperty = function(name) { + Cesium3DTileBatchData.prototype.hasProperty = function(name) { //>>includeStart('debug', pragmas.debug); if (!defined(name)) { throw new DeveloperError('name is required.'); @@ -243,7 +240,7 @@ define([ return defined(batchTable) && defined(batchTable[name]); }; - CollectionData.prototype.getPropertyNames = function() { + Cesium3DTileBatchData.prototype.getPropertyNames = function() { var names = []; var batchTable = this._batchTable; @@ -260,7 +257,7 @@ define([ return names; }; - CollectionData.prototype.getProperty = function(batchId, name) { + Cesium3DTileBatchData.prototype.getProperty = function(batchId, name) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -285,7 +282,7 @@ define([ return clone(propertyValues[batchId], true); }; - CollectionData.prototype.setProperty = function(batchId, name, value) { + Cesium3DTileBatchData.prototype.setProperty = function(batchId, name, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -313,9 +310,9 @@ define([ propertyValues[batchId] = clone(value, true); }; - function getGlslComputeSt(content) { + function getGlslComputeSt(batchData) { // GLSL batchId is zero-based: [0, batchSize - 1] - if (content._textureDimensions.y === 1) { + if (batchData._textureDimensions.y === 1) { return 'uniform vec4 tiles3d_textureStep; \n' + 'vec2 computeSt(float batchId) \n' + '{ \n' + @@ -339,184 +336,250 @@ define([ '} \n'; } - CollectionData.prototype.modifyVertexShader = function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, perform per model (e.g., building) show/hide in the vertex shader - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec3 tiles3d_modelColor; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' vec2 st = computeSt(a_batchId); \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + - ' float show = modelProperties.a; \n' + - ' gl_Position *= show; \n' + // Per batched model show/hide - ' tiles3d_modelColor = modelProperties.rgb; \n' + // Pass batched model color to fragment shader - '}'; - } else { - newMain = - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' tiles3d_modelSt = computeSt(a_batchId); \n' + - '}'; + Cesium3DTileBatchData.prototype.getVertexShaderCallback = function() { + if (this._batchSize === 0) { + // Do not change vertex shader source; the model was not batched + return undefined; } - return renamedSource + '\n' + getGlslComputeSt(this) + newMain; - }; + var that = this; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per model (e.g., building) show/hide in the vertex shader + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec3 tiles3d_modelColor; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' vec2 st = computeSt(a_batchId); \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + + ' float show = modelProperties.a; \n' + + ' gl_Position *= show; \n' + // Per batched model show/hide + ' tiles3d_modelColor = modelProperties.rgb; \n' + // Pass batched model color to fragment shader + '}'; + } else { + newMain = + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' tiles3d_modelSt = computeSt(a_batchId); \n' + + '}'; + } - CollectionData.prototype.modifyFragmentShader = function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; + return renamedSource + '\n' + getGlslComputeSt(that) + newMain; + }; + }; - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, per patched model (e.g., building) show/hide already - // happened in the fragment shader - newMain = - 'varying vec3 tiles3d_modelColor; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' gl_FragColor.rgb *= tiles3d_modelColor; \n' + - '}'; - } else { - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + - ' if (modelProperties.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gltf_main(); \n' + - ' gl_FragColor.rgb *= modelProperties.rgb; \n' + - '}'; + Cesium3DTileBatchData.prototype.getFragmentShaderCallback = function() { + if (this._batchSize === 0) { + // Do not change fragment shader source; the model was not batched + return undefined; } - return renamedSource + '\n' + newMain; + return function(source) { + //TODO: generate entire shader at runtime? + //var diffuse = 'diffuse = u_diffuse;'; + //var diffuseTexture = 'diffuse = texture2D(u_diffuse, v_texcoord0);'; + //if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // source = 'varying vec3 tiles3d_modelColor; \n' + source; + // source = source.replace(diffuse, 'diffuse.rgb = tiles3d_modelColor;'); + // source = source.replace(diffuseTexture, 'diffuse.rgb = texture2D(u_diffuse, v_texcoord0).rgb * tiles3d_modelColor;'); + //} else { + // source = + // 'uniform sampler2D tiles3d_batchTexture; \n' + + // 'varying vec2 tiles3d_modelSt; \n' + + // source; + // + // var readColor = + // 'vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + + // 'if (modelProperties.a == 0.0) { \n' + + // ' discard; \n' + + // '}'; + // + // source = source.replace(diffuse, readColor + 'diffuse.rgb = modelProperties.rgb;'); + // source = source.replace(diffuseTexture, readColor + 'diffuse.rgb = texture2D(u_diffuse, v_texcoord0).rgb * modelProperties.rgb;'); + //} + // + //return source; + + // TODO: support both "replace" and "highlight" color? Highlight is below, replace is commented out above + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per patched model (e.g., building) show/hide already + // happened in the fragment shader + newMain = + 'varying vec3 tiles3d_modelColor; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' gl_FragColor.rgb *= tiles3d_modelColor; \n' + + '}'; + } else { + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + + ' if (modelProperties.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gltf_main(); \n' + + ' gl_FragColor.rgb *= modelProperties.rgb; \n' + + '}'; + } + + return renamedSource + '\n' + newMain; + }; }; - CollectionData.prototype.modifyUniformMap = function(uniformMap) { + Cesium3DTileBatchData.prototype.getUniformMapCallback = function() { + if (this._batchSize === 0) { + // Do not change the uniform map; the model was not batched + return undefined; + } + var that = this; - var batchUniformMap = { - tiles3d_batchTexture : function() { - // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. - return defaultValue(that._batchTexture, that._defaultTexture); - }, - tiles3d_textureDimensions : function() { - return that._textureDimensions; - }, - tiles3d_textureStep : function() { - return that._textureStep; - } + return function(uniformMap) { + var batchUniformMap = { + tiles3d_batchTexture : function() { + // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tiles3d_textureDimensions : function() { + return that._textureDimensions; + }, + tiles3d_textureStep : function() { + return that._textureStep; + } + }; + + return combine(uniformMap, batchUniformMap); }; - - return combine(uniformMap, batchUniformMap); }; - /////////////////////////////////////////////////////////////////////////// + Cesium3DTileBatchData.prototype.getPickVertexShaderCallback = function() { + if (this._batchSize === 0) { + // Do not change vertex shader source; the model was not batched + return undefined; + } - CollectionData.prototype.modifyPickVertexShader = function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; + var that = this; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per patched model (e.g., building) show/hide in the vertex shader + newMain = + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' vec2 st = computeSt(a_batchId); \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + + ' float show = modelProperties.a; \n' + + ' gl_Position *= show; \n' + // Per batched model show/hide + ' tiles3d_modelSt = st; \n' + + '}'; + } else { + newMain = + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' tiles3d_modelSt = computeSt(a_batchId); \n' + + '}'; + } - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, perform per batched model (e.g., building) show/hide in the vertex shader - newMain = - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' vec2 st = computeSt(a_batchId); \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, st); \n' + - ' float show = modelProperties.a; \n' + - ' gl_Position *= show; \n' + // Per batched model show/hide - ' tiles3d_modelSt = st; \n' + - '}'; - } else { - newMain = - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' tiles3d_modelSt = computeSt(a_batchId); \n' + - '}'; + return renamedSource + '\n' + getGlslComputeSt(that) + newMain; + }; + }; + + Cesium3DTileBatchData.prototype.getPickFragmentShaderCallback = function() { + if (this._batchSize === 0) { + // Do not change fragment shader source; the model was not batched + return undefined; } - return renamedSource + '\n' + getGlslComputeSt(this) + newMain; - }; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per patched model (e.g., building) show/hide already + // happened in the fragment shader + newMain = + 'uniform sampler2D tiles3d_pickTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' gltf_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + + '}'; + } else { + newMain = + 'uniform sampler2D tiles3d_pickTexture; \n' + + 'uniform sampler2D tiles3d_batchTexture; \n' + + 'varying vec2 tiles3d_modelSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + + ' if (modelProperties.a == 0.0) { \n' + + ' discard; \n' + // Per batched model show/hide + ' } \n' + + ' gltf_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + + '}'; + } - CollectionData.prototype.modifyPickFragmentShader = function(source) { - var renamedSource = ShaderSource.replaceMain(source, 'gltf_main'); - var newMain; - - if (ContextLimits.maximumVertexTextureImageUnits > 0) { - // When VTF is supported, per batched model (e.g., building) show/hide already - // happened in the fragment shader - newMain = - 'uniform sampler2D tiles3d_pickTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' gltf_main(); \n' + - ' if (gl_FragColor.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + - '}'; - } else { - newMain = - 'uniform sampler2D tiles3d_pickTexture; \n' + - 'uniform sampler2D tiles3d_batchTexture; \n' + - 'varying vec2 tiles3d_modelSt; \n' + - 'void main() \n' + - '{ \n' + - ' vec4 modelProperties = texture2D(tiles3d_batchTexture, tiles3d_modelSt); \n' + - ' if (modelProperties.a == 0.0) { \n' + - ' discard; \n' + // Per batched model show/hide - ' } \n' + - ' gltf_main(); \n' + - ' if (gl_FragColor.a == 0.0) { \n' + - ' discard; \n' + - ' } \n' + - ' gl_FragColor = texture2D(tiles3d_pickTexture, tiles3d_modelSt); \n' + - '}'; - } - - return renamedSource + '\n' + newMain; + return renamedSource + '\n' + newMain; + }; }; - CollectionData.prototype.modifyPickUniformMap = function(uniformMap) { + Cesium3DTileBatchData.prototype.getPickUniformMapCallback = function() { + if (this._batchSize === 0) { + // Do not change the uniform map; the model was not batched + return undefined; + } + var that = this; - var batchUniformMap = { - tiles3d_batchTexture : function() { - return defaultValue(that._batchTexture, that._defaultTexture); - }, - tiles3d_textureDimensions : function() { - return that._textureDimensions; - }, - tiles3d_textureStep : function() { - return that._textureStep; - }, - tiles3d_pickTexture : function() { - return that._pickTexture; - } + return function(uniformMap) { + var batchUniformMap = { + tiles3d_batchTexture : function() { + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tiles3d_textureDimensions : function() { + return that._textureDimensions; + }, + tiles3d_textureStep : function() { + return that._textureStep; + }, + tiles3d_pickTexture : function() { + return that._pickTexture; + } + }; + + return combine(uniformMap, batchUniformMap); }; - - return combine(uniformMap, batchUniformMap); }; - /////////////////////////////////////////////////////////////////////////// - - function createTexture(content, context, bytes) { - var dimensions = content._textureDimensions; + function createTexture(batchData, context, bytes) { + var dimensions = batchData._textureDimensions; return new Texture({ context : context, pixelFormat : PixelFormat.RGBA, @@ -533,12 +596,11 @@ define([ }); } - function createPickTexture(content, context) { - var batchSize = content._batchSize; - if (!defined(content._pickTexture) && (batchSize > 0)) { - - var pickIds = content._pickIds; - var byteLength = getByteLength(content); + function createPickTexture(batchData, context) { + var batchSize = batchData._batchSize; + if (!defined(batchData._pickTexture) && (batchSize > 0)) { + var pickIds = batchData._pickIds; + var byteLength = getByteLength(batchData); var bytes = new Uint8Array(byteLength); // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating @@ -546,7 +608,7 @@ define([ // to RGBA in the shader. The only consider is precision issues, which might // not be an issue in WebGL 2. for (var i = 0; i < batchSize; ++i) { - var pickId = context.createPickId({id : i}); // TODO : change this + var pickId = context.createPickId(batchData._owner.getModel(i)); pickIds.push(pickId); var pickColor = pickId.color; @@ -557,32 +619,32 @@ define([ bytes[offset + 3] = Color.floatToByte(pickColor.alpha); } - content._pickTexture = createTexture(content, context, bytes); + batchData._pickTexture = createTexture(batchData, context, bytes); } } - function createBatchTexture(content, context) { - if (!defined(content._batchTexture)) { - content._batchTexture = createTexture(content, context, content._batchValues); - content._batchValuesDirty = false; + function createBatchTexture(batchData, context) { + if (!defined(batchData._batchTexture)) { + batchData._batchTexture = createTexture(batchData, context, batchData._batchValues); + batchData._batchValuesDirty = false; } } - function updateBatchTexture(content, context) { - if (content._batchValuesDirty) { - var dimensions = content._textureDimensions; + function updateBatchTexture(batchData) { + if (batchData._batchValuesDirty) { + var dimensions = batchData._textureDimensions; // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained // texture updates when less than, for example, 10%, of the values changed. Or // even just optimize the common case when one model show/color changed. - content._batchTexture.copyFrom({ + batchData._batchTexture.copyFrom({ width : dimensions.x, height : dimensions.y, - arrayBufferView : content._batchValues + arrayBufferView : batchData._batchValues }); } } - CollectionData.prototype.update = function(context, frameState) { + Cesium3DTileBatchData.prototype.update = function(context, frameState) { this._defaultTexture = context.defaultTexture; if (frameState.passes.pick) { @@ -593,17 +655,17 @@ define([ if (this._batchValuesDirty) { // Create batch texture on-demand createBatchTexture(this, context); - updateBatchTexture(this, context); // Apply per-model show/color updates + updateBatchTexture(this); // Apply per-model show/color updates this._batchValuesDirty = false; } }; - CollectionData.prototype.isDestroyed = function() { + Cesium3DTileBatchData.prototype.isDestroyed = function() { return false; }; - CollectionData.prototype.destroy = function() { + Cesium3DTileBatchData.prototype.destroy = function() { this._batchTexture = this._batchTexture && this._batchTexture.destroy(); this._pickTexture = this._pickTexture && this._pickTexture.destroy(); @@ -616,5 +678,5 @@ define([ return destroyObject(this); }; - return CollectionData; + return Cesium3DTileBatchData; }); diff --git a/Source/Scene/Cesium3DTileContentProviderFactory.js b/Source/Scene/Cesium3DTileContentProviderFactory.js index 9de928a1bfbc..bf4f7ac47694 100644 --- a/Source/Scene/Cesium3DTileContentProviderFactory.js +++ b/Source/Scene/Cesium3DTileContentProviderFactory.js @@ -1,9 +1,11 @@ /*global define*/ define([ './Batched3DModel3DTileContentProvider', + './Instanced3DModel3DTileContentProvider', './Points3DTileContentProvider' ], function( Batched3DModel3DTileContentProvider, + Instanced3DModel3DTileContentProvider, Points3DTileContentProvider) { "use strict"; @@ -16,6 +18,9 @@ define([ }, pnts : function(tileset, url, contentHeader) { return new Points3DTileContentProvider(tileset, url, contentHeader); + }, + i3dm : function(tileset, url, contentHeader) { + return new Instanced3DModel3DTileContentProvider(tileset, url, contentHeader); } }; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js new file mode 100644 index 000000000000..0484bf6cb034 --- /dev/null +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -0,0 +1,233 @@ +/*global define*/ +define([ + '../Core/Cartesian3', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/getStringFromTypedArray', + '../Core/loadArrayBuffer', + '../Core/Matrix4', + '../Core/Quaternion', + './Cesium3DTileBatchData', + './Cesium3DTileContentState', + './ModelInstanceCollection', + '../ThirdParty/when' + ], function( + Cartesian3, + destroyObject, + DeveloperError, + getStringFromTypedArray, + loadArrayBuffer, + Matrix4, + Quaternion, + Cesium3DTileBatchData, + Cesium3DTileContentState, + ModelInstanceCollection, + when) { + "use strict"; + + /** + * DOC_TBA + */ + var Instanced3DModel3DTileContentProvider = function(tileset, url, contentHeader) { + this._modelInstanceCollection = undefined; + this._url = url; + this._tileset = tileset; + this._batchData = undefined; + + /** + * @readonly + */ + this.state = Cesium3DTileContentState.UNLOADED; + + /** + * @type {Promise} + */ + this.processingPromise = when.defer(); + + /** + * @type {Promise} + */ + this.readyPromise = when.defer(); + }; + + Instanced3DModel3DTileContentProvider.prototype.getModel = function(index) { + return this._modelInstanceCollection.getModel(index); + }; + + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var sizeOfFloat64 = Float64Array.BYTES_PER_ELEMENT; + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + + // Each vertex has a position, quaternion, and batchId + // Coordinates are in double precision, batchId is a short + var instanceSizeInBytes = sizeOfFloat64 * 7 + sizeOfUint16; + + function getSubarray(array, offset, length) { + return array.subarray(offset, offset + length); + } + + /** + * DOC_TBA + * + * Use Cesium3DTile#requestContent + */ + Instanced3DModel3DTileContentProvider.prototype.request = function() { + var that = this; + + this.state = Cesium3DTileContentState.LOADING; + + function failRequest(error) { + that.state = Cesium3DTileContentState.FAILED; + that.readyPromise.reject(error); + } + + loadArrayBuffer(this._url).then(function(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + var magic = getStringFromTypedArray(getSubarray(uint8Array, 0, Math.min(4, uint8Array.byteLength))); + if (magic !== 'i3dm') { + throw new DeveloperError('Invalid Instanced 3D Model. Expected magic=i3dm. Read magic=' + magic); + } + + var view = new DataView(arrayBuffer); + var byteOffset = 0; + + byteOffset += sizeOfUint32; // Skip magic number + + //>>includeStart('debug', pragmas.debug); + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new DeveloperError('Only Instanced 3D Model version 1 is supported. Version ' + version + ' is not.'); + } + //>>includeEnd('debug'); + byteOffset += sizeOfUint32; + + var batchTableLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var gltfLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var gltfFormat = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var instancesLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchData = new Cesium3DTileBatchData(that, instancesLength); + that._batchData = batchData; + if (batchTableLength > 0) { + var batchTableString = getStringFromTypedArray(getSubarray(uint8Array, byteOffset, batchTableLength)); + batchData._batchTable = JSON.parse(batchTableString); + byteOffset += batchTableLength; + } + + var gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfLength); + byteOffset += gltfLength; + + var instancesDataSizeInBytes = instancesLength * instanceSizeInBytes; + var instancesView = new DataView(arrayBuffer, byteOffset, instancesDataSizeInBytes); + byteOffset += instancesDataSizeInBytes; + + // TODO : what is the model's cache key if it doesn't have a url? Url of the tile? + // Create model instance collection + var collectionOptions = { + instances : [], + batchData : batchData, + boundingVolume : undefined, // TODO : what is the best way to get the tile's bounding volume? + pickPrimitive : that._tileset, + cull : false, + url : undefined, + headers : undefined, + gltf : undefined, + basePath : undefined + }; + + //>>includeStart('debug', pragmas.debug); + if((gltfFormat !== 0) && (gltfFormat !== 1)) { + throw new DeveloperError('glTF format must be 0 or 1.'); + } + //>>includeEnd('debug'); + + if (gltfFormat === 0) { + collectionOptions.url = getStringFromTypedArray(gltfView); + // TODO : how to get the correct headers + } else { + collectionOptions.gltf = gltfView; + collectionOptions.basePath = that._url; + } + + var position = new Cartesian3(); + var quaternion = new Quaternion(); + var scale = new Cartesian3(1.0, 1.0, 1.0); + + var instances = collectionOptions.instances; + byteOffset = 0; + + for (var i = 0; i < instancesLength; ++i) { + // Get position + position.x = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + position.y = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + position.z = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + + // Get rotation quaternion + quaternion.x = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + quaternion.y = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + quaternion.z = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + quaternion.w = instancesView.getFloat64(byteOffset, true); + byteOffset += sizeOfFloat64; + + // Get batchId + var batchId = instancesView.getUint16(byteOffset, true); + byteOffset += sizeOfUint16; + + var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(position, quaternion, scale); + instances.push({ + modelMatrix : modelMatrix, + batchId : batchId + }); + } + + var modelInstanceCollection = new ModelInstanceCollection(collectionOptions); + that._modelInstanceCollection = modelInstanceCollection; + that.state = Cesium3DTileContentState.PROCESSING; + that.processingPromise.resolve(that); + + when(modelInstanceCollection.readyPromise).then(function(modelInstanceCollection) { + that.state = Cesium3DTileContentState.READY; + that.readyPromise.resolve(that); + }).otherwise(failRequest); + }).otherwise(failRequest); + }; + + /** + * DOC_TBA + * + * Use Cesium3DTile#update + */ + Instanced3DModel3DTileContentProvider.prototype.update = function(owner, context, frameState, commandList) { + this._modelInstanceCollection.update(context, frameState, commandList); + }; + + /** + * DOC_TBA + */ + Instanced3DModel3DTileContentProvider.prototype.isDestroyed = function() { + return false; + }; + + /** + * DOC_TBA + */ + Instanced3DModel3DTileContentProvider.prototype.destroy = function() { + this._modelInstanceCollection = this._modelInstanceCollection && this._modelInstanceCollection.destroy(); + + return destroyObject(this); + }; + return Instanced3DModel3DTileContentProvider; +}); diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js index 678b41318803..e9ec184c52cf 100644 --- a/Source/Scene/ModelInstance.js +++ b/Source/Scene/ModelInstance.js @@ -18,17 +18,19 @@ define([ * * @private */ - var ModelInstance = function(options, collection, index, batchId) { + var ModelInstance = function(options, collection, index, pickPrimitive) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); this._collection = collection; - this._content = collection._content; + this._batchData = collection._batchData; this._index = index; - this._batchId = batchId; + this._batchId = defaultValue(options.batchId, index); this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this._color = undefined; // for calling getColor this.show = defaultValue(options.show, true); this.color = defaultValue(options.color, Color.WHITE); + + this.primitive = pickPrimitive; }; defineProperties(ModelInstance.prototype, { @@ -46,10 +48,10 @@ define([ show : { get : function() { - return this._content.getShow(this._batchId); + return this._batchData.getShow(this._batchId); }, set : function(value) { - this._content.setShow(this._batchId, value); + this._batchData.setShow(this._batchId, value); } }, @@ -58,20 +60,20 @@ define([ if (!defined(this._color)) { this._color = new Color(); } - return this._content.getColor(this._batchId, this._color); + return this._batchData.getColor(this._batchId, this._color); }, set : function(value) { - this._content.setColor(this._batchId, value); + this._batchData.setColor(this._batchId, value); } } }); ModelInstance.prototype.getProperty = function(name) { - return this._content.getProperty(this._batchId, name); + return this._batchData.getProperty(this._batchId, name); }; ModelInstance.prototype.setProperty = function(name, value) { - this._content.setProperty(this._batchId, name, value); + this._batchData.setProperty(this._batchId, name, value); }; return ModelInstance; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 038599f9402c..3ee8c74ed2ed 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -19,7 +19,7 @@ define([ '../Renderer/DrawCommand', '../Renderer/ShaderSource', '../ThirdParty/when', - './CollectionContent', + './Cesium3DTileBatchData', './Model', './ModelInstance', './SceneMode' @@ -43,7 +43,7 @@ define([ DrawCommand, ShaderSource, when, - CollectionContent, + Cesium3DTileBatchData, Model, ModelInstance, SceneMode) { @@ -97,21 +97,27 @@ define([ this._dynamic = defaultValue(options.dynamic, false); this._show = defaultValue(options.show, true); this._allowPicking = defaultValue(options.allowPicking, true); + this._cull = defaultValue(options.cull, true); this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; var instances = defaultValue(options.instances, []); var length = instances.length; - this._content = defined(options.content) ? options.content : new CollectionContent(length); - this._instances = new Array(length); + var batchData = options.batchData; + if (!defined(batchData)) { + batchData = new Cesium3DTileBatchData(this, length); + } + this._batchData = batchData; + this._instances = new Array(length); for (var i = 0; i < length; ++i) { - this._instances[i] = new ModelInstance(instances[i], this, i, i); + this._instances[i] = new ModelInstance(instances[i], this, i, this._pickPrimitive); } this._model = undefined; - this._instanceBufferData = undefined; + this._pickPrimitive = options.pickPrimitive; + this._vertexBufferValues = undefined; this._vertexBuffer = undefined; this._createVertexBuffer = true; this._vertexBufferDirty = false; @@ -121,9 +127,9 @@ define([ this._pickCommands = []; this._modelCommands = undefined; - this._boundingSphere = new BoundingSphere(); - this._boundingSphereModel = new Matrix4(); - this._boundingSphereModelView = new Matrix4(); + this._boundingVolume = options.boundingVolume; + this._boundingVolumeModel = new Matrix4(); + this._boundingVolumeModelView = new Matrix4(); // Passed on to Model this._url = options.url; @@ -167,11 +173,6 @@ define([ return this._show; } }, - boundingSphere : { - get : function() { - return this._boundingSphere; - } - }, allowPicking : { get : function() { return this._allowPicking; @@ -194,13 +195,23 @@ define([ } }); + ModelInstanceCollection.prototype.getModel = function(index) { + //>>includeStart('debug', pragmas.debug); + if (!defined(index) || (index < 0) || (index >= this.length)) { + throw new DeveloperError('index is required and between zero and length - 1 (' + (this.length - 1) + ').'); + } + //>>includeEnd('debug'); + + return this._instances[index]; + }; + ModelInstanceCollection.prototype._updateInstance = function(instance) { // TODO : update the whole buffer for now, but later do sub-commits based on the instance's index this._vertexBufferDirty = true; }; ModelInstanceCollection.prototype.add = function(instance) { - var i = new ModelInstance(instance, this, this.length, this.length); + var i = new ModelInstance(instance, this, this.length, this._pickPrimitive); this._instances.push(i); this._vertexBufferDirty = true; this._createVertexBuffer = true; @@ -326,13 +337,13 @@ define([ '}'; vertexShaderCached = newMain; - return collection._content.modifyVertexShader(newMain); + return collection._batchData.getVertexShaderCallback()(newMain); }; } function getFragmentShaderCallback(collection) { return function(fs) { - return collection._content.modifyFragmentShader(fs); + return collection._batchData.getFragmentShaderCallback()(fs); }; } @@ -354,19 +365,19 @@ define([ function getPickVertexShaderCallback(collection) { return function (vs) { // Use the vertex shader that was generated earlier - return collection._content.modifyPickVertexShader(vertexShaderCached); + return collection._batchData.getPickVertexShaderCallback()(vertexShaderCached); }; } function getPickFragmentShaderCallback(collection) { return function(fs) { - return collection._content.modifyPickFragmentShader(fs); + return collection._batchData.getPickFragmentShaderCallback()(fs); }; } function createBoundsModelViewFunction(collection, context) { return function() { - return Matrix4.multiplyTransformation(context.uniformState.view, collection._boundingSphereModel, collection._boundingSphereModelView); + return Matrix4.multiplyTransformation(context.uniformState.view, collection._boundingVolumeModel, collection._boundingVolumeModelView); }; } @@ -391,13 +402,13 @@ define([ } } - return collection._content.modifyUniformMap(uniformMap); + return collection._batchData.getUniformMapCallback()(uniformMap); }; } function getPickUniformMapCallback(collection) { return function(uniformMap) { - return collection._content.modifyPickUniformMap(uniformMap); + return collection._batchData.getPickUniformMapCallback()(uniformMap); }; } @@ -408,14 +419,14 @@ define([ return; } - var instanceBufferData = collection._instanceBufferData; + var instanceBufferData = collection._vertexBufferValues; var vertexBuffer = collection._vertexBuffer; var createVertexBuffer = collection._createVertexBuffer; var instancesLength = collection.length; var dynamic = collection._dynamic; var viewMatrix = context.uniformState.view; - var center = dynamic ? Cartesian3.ZERO : collection._boundingSphere.center; + var center = dynamic ? Cartesian3.ZERO : collection._boundingVolume.center; if (createVertexBuffer) { instanceBufferData = new Float32Array(instancesLength * 13); @@ -456,25 +467,29 @@ define([ usage : dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW }); collection._vertexBuffer = vertexBuffer; - collection._instanceBufferData = instanceBufferData; + collection._vertexBufferValues = instanceBufferData; collection._createVertexBuffer = false; } else { vertexBuffer.copyFromArrayView(instanceBufferData); } } - function updateBoundingSphere(collection) { - var points = []; - var instancesLength = collection.length; - for (var i = 0; i < instancesLength; ++i) { - var translation = new Cartesian3(); - Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); - points.push(translation); + function createBoundingVolume(collection) { + if (!defined(collection._boundingVolume)) { + var points = []; + var instancesLength = collection.length; + for (var i = 0; i < instancesLength; ++i) { + var translation = new Cartesian3(); + Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); + points.push(translation); + } + + var boundingSphere = new BoundingSphere(); + BoundingSphere.fromPoints(points, boundingSphere); + collection._boundingVolume = boundingSphere; } - var boundingSphere = collection._boundingSphere; - BoundingSphere.fromPoints(points, boundingSphere); - Matrix4.fromTranslation(boundingSphere.center, collection._boundingSphereModel); + Matrix4.fromTranslation(collection._boundingVolume.center, collection._boundingVolumeModel); } function createModel(collection, context) { @@ -497,7 +512,7 @@ define([ ignoreCommands : false }; - updateBoundingSphere(collection); + createBoundingVolume(collection); if (instancingSupported) { updateVertexBuffer(collection, context); @@ -598,23 +613,24 @@ define([ var instancesLength = collection.length; var allowPicking = collection.allowPicking; - var boundingSphere = collection._boundingSphere; - var boundingSphereModel = collection._boundingSphereModel; - boundingSphere.radius += collection._model.boundingSphere.radius; + var boundingVolume = collection._boundingVolume; + var boundingVolumeModel = collection._boundingVolumeModel; if (collection._instancingSupported) { for (i = 0; i < commandsLength; ++i) { command = clone(drawCommands[i]); command.instanceCount = instancesLength; - command.modelMatrix = boundingSphereModel; - command.boundingVolume = boundingSphere; + command.modelMatrix = boundingVolumeModel; + command.boundingVolume = boundingVolume; + command.cull = collection._cull; collection._drawCommands.push(command); if (allowPicking) { command = clone(pickCommands[i]); command.instanceCount = instancesLength; - command.modelMatrix = boundingSphereModel; - command.boundingVolume = boundingSphere; + command.modelMatrix = boundingVolumeModel; + command.boundingVolume = boundingVolume; + command.cull = collection._cull; collection._pickCommands.push(command); } } @@ -626,6 +642,7 @@ define([ command = clone(drawCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); + command.cull = collection._cull; command.uniformMap = clone(command.uniformMap); command.uniformMap.czm_color = createColorFunction(instances[j]); collection._drawCommands.push(command); @@ -634,6 +651,7 @@ define([ command = clone(pickCommands[i]); command.modelMatrix = new Matrix4(); command.boundingVolume = new BoundingSphere(); + command.cull = collection._cull; command.uniformMap = clone(command.uniformMap); command.uniformMap.czm_pickColor = createPickColorFunction(instances[j], context); collection._pickCommands.push(command); @@ -748,7 +766,7 @@ define([ } if (instancingSupported) { - this._content.update(context, frameState); + this._batchData.update(context, frameState); } var model = this._model; @@ -806,39 +824,18 @@ define([ }; /** - * Returns true if this object was destroyed; otherwise, false. - *

- * If this object was destroyed, it should not be used; calling any function other than - * isDestroyed will result in a {@link DeveloperError} exception. - * - * @returns {Boolean} true if this object was destroyed; otherwise, false. - * - * @see ModelInstanceCollection#destroy + * DOC_TBA */ ModelInstanceCollection.prototype.isDestroyed = function() { return false; }; /** - * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic - * release of WebGL resources, instead of relying on the garbage collector to destroy this object. - *

- * Once an object is destroyed, it should not be used; calling any function other than - * isDestroyed will result in a {@link DeveloperError} exception. Therefore, - * assign the return value (undefined) to the object as done in the example. - * - * @returns {undefined} - * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. - * - * @see ModelInstanceCollection#isDestroyed - * - * @example - * instanceCollection = instanceCollection && instanceCollection.destroy(); + * DOC_TBA */ ModelInstanceCollection.prototype.destroy = function() { - this._model.destroy(); - this._content.destroy(); + this._model = this._model && this._model.destroy(); + this._batchData = this._batchData && this._batchData.destroy(); return destroyObject(this); }; From d5ce3b23a2979f0602b6bfc7ad20f20b29a613a1 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 6 Oct 2015 13:36:44 -0400 Subject: [PATCH 19/27] Model instancing optimizations and cleanup --- .../3D Models Instancing 3D Tiles.html | 30 ++-- .../development/3D Models Instancing.html | 8 +- .../Batched3DModel3DTileContentProvider.js | 30 ++-- Source/Scene/BatchedModel.js | 16 +- Source/Scene/Cesium3DTile.js | 2 +- ...BatchData.js => Cesium3DTileBatchTable.js} | 101 ++++++------ Source/Scene/Cesium3DTileContentProvider.js | 2 +- .../Cesium3DTileContentProviderFactory.js | 12 +- Source/Scene/Cesium3DTileset.js | 1 + .../Instanced3DModel3DTileContentProvider.js | 76 +++++---- Source/Scene/ModelInstance.js | 17 +- Source/Scene/ModelInstanceCollection.js | 151 ++++++++++-------- Source/Scene/Points3DTileContentProvider.js | 2 +- 13 files changed, 226 insertions(+), 222 deletions(-) rename Source/Scene/{Cesium3DTileBatchData.js => Cesium3DTileBatchTable.js} (88%) diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html b/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html index c5b7033a3a41..a79b247f64b8 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html @@ -28,7 +28,7 @@ "use strict"; //Sandcastle_Begin -var globeVisible = false; +var globeVisible = true; var viewer = new Cesium.Viewer('cesiumContainer', { globe : globeVisible ? undefined : false, @@ -42,35 +42,23 @@ var controller = scene.screenSpaceCameraController; var r = Math.max(radius, camera.frustum.near); controller.minimumZoomDistance = r * 0.5; - var heading = Cesium.Math.toRadians(230.0); var pitch = Cesium.Math.toRadians(-20.0); camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0)); } function loadInstanced3DTile() { - // TODO : create real tileset later - var tileset; - var contentHeader = {}; - var url = 'http://localhost:8002/tilesets/Instances/instance3DTileTest.i3dm'; - var content = new Cesium.Instanced3DModel3DTileContentProvider(tileset, url, contentHeader); - content.request(); - content.processingPromise.then(function(content) { - var collection = content._modelInstanceCollection; - scene.primitives.add(collection); - collection.readyPromise.then(function(collection) { - // Play and loop all animations at half-speed - collection._model.activeAnimations.addAll({ - speedup : 0.5, - loop : Cesium.ModelAnimationLoop.REPEAT - }); - orientCamera(collection._boundingVolume.center, collection._boundingVolume.radius); - }).otherwise(function(error) { - window.alert(error); - }); + var city = scene.primitives.add(new Cesium.Cesium3DTileset({ + url : 'http://localhost:8002/tilesets/trees' + })); + + city.readyPromise.then(function() { + city.debugShowBox = true; }).otherwise(function(error) { window.alert(error); }); + + orientCamera(Cesium.Cartesian3.fromRadians(-1.2911323805815227, 0.7097150757974291), 10.0); } loadInstanced3DTile(); diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing.html b/Apps/Sandcastle/gallery/development/3D Models Instancing.html index 5d9a5dc5b18c..08660109c905 100644 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing.html +++ b/Apps/Sandcastle/gallery/development/3D Models Instancing.html @@ -74,7 +74,7 @@ collection.readyPromise.then(function(collection) { // Play and loop all animations at half-speed - collection._model.activeAnimations.addAll({ + collection.activeAnimations.addAll({ speedup : 0.5, loop : Cesium.ModelAnimationLoop.REPEAT }); @@ -96,14 +96,12 @@ Cesium.Matrix4.getTranslation(modelMatrix, translation); points.push(translation); - model = Cesium.Model.fromGltf({ + model = scene.primitives.add(Cesium.Model.fromGltf({ url : url, modelMatrix : modelMatrix, debugShowBoundingVolume : debugShowBoundingVolume, debugWireframe : debugWireframe - }); - - scene.primitives.add(model); + })); model.readyPromise.then(function(model) { // Play and loop all animations at half-speed diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index c36f58b9eb44..65c78138c526 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -7,7 +7,7 @@ define([ '../Core/DeveloperError', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', - './Cesium3DTileBatchData', + './Cesium3DTileBatchTable', './Cesium3DTileContentState', './Model', './BatchedModel', @@ -20,7 +20,7 @@ define([ DeveloperError, getStringFromTypedArray, loadArrayBuffer, - Cesium3DTileBatchData, + Cesium3DTileBatchTable, Cesium3DTileContentState, Model, BatchedModel, @@ -30,7 +30,7 @@ define([ /** * DOC_TBA */ - var Batched3DModel3DTileContentProvider = function(tileset, url, contentHeader) { + var Batched3DModel3DTileContentProvider = function(tileset, tile, url, contentHeader) { this._model = undefined; this._url = url; this._tileset = tileset; @@ -52,7 +52,7 @@ define([ var batchSize = defaultValue(contentHeader.batchSize, 0); this._batchSize = batchSize; - this._batchData = new Cesium3DTileBatchData(this, batchSize); + this._batchTable = new Cesium3DTileBatchTable(this, batchSize); this._models = undefined; }; @@ -78,7 +78,7 @@ define([ if (!defined(content._models) && (batchSize > 0)) { var models = new Array(batchSize); for (var i = 0; i < batchSize; ++i) { - models[i] = new BatchedModel(tileset, content._batchData, i); + models[i] = new BatchedModel(tileset, content._batchTable, i); } content._models = models; } @@ -113,7 +113,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var batchData = this._batchData; + var batchTable = this._batchTable; this.state = Cesium3DTileContentState.LOADING; @@ -153,7 +153,7 @@ define([ // // We could also make another request for it, but that would make the property set/get // API async, and would double the number of numbers in some cases. - batchData._batchTable = JSON.parse(batchTableString); + batchTable._batchTable = JSON.parse(batchTableString); } var gltfView = new Uint8Array(arrayBuffer, byteOffset, arrayBuffer.byteLength - byteOffset); @@ -164,12 +164,12 @@ define([ gltf : gltfView, cull : false, // The model is already culled by the 3D tiles releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory - vertexShaderLoaded : batchData.getVertexShaderCallback(), - fragmentShaderLoaded : batchData.getFragmentShaderCallback(), - uniformMapLoaded : batchData.getUniformMapCallback(), - pickVertexShaderLoaded : batchData.getPickVertexShaderCallback(), - pickFragmentShaderLoaded : batchData.getPickFragmentShaderCallback(), - pickUniformMapLoaded : batchData.getPickUniformMapCallback(), + vertexShaderLoaded : batchTable.getVertexShaderCallback(), + fragmentShaderLoaded : batchTable.getFragmentShaderCallback(), + uniformMapLoaded : batchTable.getUniformMapCallback(), + pickVertexShaderLoaded : batchTable.getPickVertexShaderCallback(), + pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(), + pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), basePath : that._url }); @@ -194,7 +194,7 @@ define([ // the content's resource loading. In the READY state, it will // actually generate commands. - this._batchData.update(context, frameState); + this._batchTable.update(context, frameState); this._model.update(context, frameState, commandList); }; @@ -210,7 +210,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.destroy = function() { this._model = this._model && this._model.destroy(); - this._batchData = this._batchData && this._batchData.destroy(); + this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/BatchedModel.js b/Source/Scene/BatchedModel.js index 709d53c9a06a..26d89b086c47 100644 --- a/Source/Scene/BatchedModel.js +++ b/Source/Scene/BatchedModel.js @@ -10,8 +10,8 @@ define([ /** * DOC_TBA */ - var BatchedModel = function(tileset, batchData, batchId) { - this._batchData = batchData; + var BatchedModel = function(tileset, batchTable, batchId) { + this._batchTable = batchTable; this._batchId = batchId; this._color = undefined; // for calling getColor @@ -29,10 +29,10 @@ define([ */ show : { get : function() { - return this._batchData.getShow(this._batchId); + return this._batchTable.getShow(this._batchId); }, set : function(value) { - this._batchData.setShow(this._batchId, value); + this._batchTable.setShow(this._batchId, value); } }, @@ -44,10 +44,10 @@ define([ if (!this._color) { this._color = new Color(); } - return this._batchData.getColor(this._batchId, this._color); + return this._batchTable.getColor(this._batchId, this._color); }, set : function(value) { - this._batchData.setColor(this._batchId, value); + this._batchTable.setColor(this._batchId, value); } } }); @@ -56,14 +56,14 @@ define([ * DOC_TBA */ BatchedModel.prototype.getProperty = function(name) { - return this._batchData.getProperty(this._batchId, name); + return this._batchTable.getProperty(this._batchId, name); }; /** * DOC_TBA */ BatchedModel.prototype.setProperty = function(name, value) { - this._batchData.setProperty(this._batchId, name, value); + this._batchTable.setProperty(this._batchId, name, value); }; return BatchedModel; diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 5bd7ad9b0611..61cbdacfa6f4 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -134,7 +134,7 @@ define([ var contentFactory = Cesium3DTileContentProviderFactory[contentHeader.type]; if (defined(contentFactory)) { - content = contentFactory(tileset, url, contentHeader); + content = contentFactory(tileset, this, url, contentHeader); } else { throw new DeveloperError('Unknown tile content type, ' + contentHeader.type + ', for ' + url); } diff --git a/Source/Scene/Cesium3DTileBatchData.js b/Source/Scene/Cesium3DTileBatchTable.js similarity index 88% rename from Source/Scene/Cesium3DTileBatchData.js rename to Source/Scene/Cesium3DTileBatchTable.js index 3ded2b70369d..48d83e39aaa7 100644 --- a/Source/Scene/Cesium3DTileBatchData.js +++ b/Source/Scene/Cesium3DTileBatchTable.js @@ -44,7 +44,7 @@ define([ * * @private */ - var Cesium3DTileBatchData = function(owner, size) { + var Cesium3DTileBatchTable = function(contentProvider, size) { this._batchSize = defaultValue(size, 0); this._batchValues = undefined; // Per-model show/color this._batchValuesDirty = false; @@ -55,7 +55,7 @@ define([ this._pickIds = []; this._batchTable = undefined; - this._owner = owner; // TODO : is owner the right name for ContentProvider? + this._contentProvider = contentProvider; // Dimensions for batch and pick textures var textureDimensions; @@ -80,11 +80,11 @@ define([ this._textureStep = textureStep; }; - defineProperties(Cesium3DTileBatchData.prototype, { + defineProperties(Cesium3DTileBatchTable.prototype, { /** * DOC_TBA * - * @memberof Cesium3DTileBatchData.prototype + * @memberof Cesium3DTileBatchTable.prototype * * @type {Number} * @readonly @@ -96,27 +96,27 @@ define([ } }); - function getByteLength(batchData) { - var dimensions = batchData._textureDimensions; + function getByteLength(batchTable) { + var dimensions = batchTable._textureDimensions; return (dimensions.x * dimensions.y) * 4; } - function getBatchValues(batchData) { - if (!defined(batchData._batchValues)) { + function getBatchValues(batchTable) { + if (!defined(batchTable._batchValues)) { // Default batch texture to RGBA = 255: white highlight (RGB) and show = true (A). - var byteLength = getByteLength(batchData); + var byteLength = getByteLength(batchTable); var bytes = new Uint8Array(byteLength); for (var i = 0; i < byteLength; ++i) { bytes[i] = 255; } - batchData._batchValues = bytes; + batchTable._batchValues = bytes; } - return batchData._batchValues; + return batchTable._batchValues; } - Cesium3DTileBatchData.prototype.setShow = function(batchId, value) { + Cesium3DTileBatchTable.prototype.setShow = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -143,7 +143,7 @@ define([ } }; - Cesium3DTileBatchData.prototype.getShow = function(batchId) { + Cesium3DTileBatchTable.prototype.getShow = function(batchId) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -162,7 +162,7 @@ define([ var scratchColor = new Array(4); - Cesium3DTileBatchData.prototype.setColor = function(batchId, value) { + Cesium3DTileBatchTable.prototype.setColor = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -193,7 +193,7 @@ define([ } }; - Cesium3DTileBatchData.prototype.setAllColor = function(value) { + Cesium3DTileBatchTable.prototype.setAllColor = function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError('value is required.'); @@ -207,7 +207,7 @@ define([ } }; - Cesium3DTileBatchData.prototype.getColor = function(batchId, color) { + Cesium3DTileBatchTable.prototype.getColor = function(batchId, color) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -229,7 +229,7 @@ define([ return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], 255, color); }; - Cesium3DTileBatchData.prototype.hasProperty = function(name) { + Cesium3DTileBatchTable.prototype.hasProperty = function(name) { //>>includeStart('debug', pragmas.debug); if (!defined(name)) { throw new DeveloperError('name is required.'); @@ -240,7 +240,7 @@ define([ return defined(batchTable) && defined(batchTable[name]); }; - Cesium3DTileBatchData.prototype.getPropertyNames = function() { + Cesium3DTileBatchTable.prototype.getPropertyNames = function() { var names = []; var batchTable = this._batchTable; @@ -257,7 +257,7 @@ define([ return names; }; - Cesium3DTileBatchData.prototype.getProperty = function(batchId, name) { + Cesium3DTileBatchTable.prototype.getProperty = function(batchId, name) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -282,7 +282,7 @@ define([ return clone(propertyValues[batchId], true); }; - Cesium3DTileBatchData.prototype.setProperty = function(batchId, name, value) { + Cesium3DTileBatchTable.prototype.setProperty = function(batchId, name, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -310,9 +310,9 @@ define([ propertyValues[batchId] = clone(value, true); }; - function getGlslComputeSt(batchData) { + function getGlslComputeSt(batchTable) { // GLSL batchId is zero-based: [0, batchSize - 1] - if (batchData._textureDimensions.y === 1) { + if (batchTable._textureDimensions.y === 1) { return 'uniform vec4 tiles3d_textureStep; \n' + 'vec2 computeSt(float batchId) \n' + '{ \n' + @@ -336,7 +336,7 @@ define([ '} \n'; } - Cesium3DTileBatchData.prototype.getVertexShaderCallback = function() { + Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function() { if (this._batchSize === 0) { // Do not change vertex shader source; the model was not batched return undefined; @@ -375,7 +375,7 @@ define([ }; }; - Cesium3DTileBatchData.prototype.getFragmentShaderCallback = function() { + Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function() { if (this._batchSize === 0) { // Do not change fragment shader source; the model was not batched return undefined; @@ -440,7 +440,7 @@ define([ }; }; - Cesium3DTileBatchData.prototype.getUniformMapCallback = function() { + Cesium3DTileBatchTable.prototype.getUniformMapCallback = function() { if (this._batchSize === 0) { // Do not change the uniform map; the model was not batched return undefined; @@ -465,7 +465,7 @@ define([ }; }; - Cesium3DTileBatchData.prototype.getPickVertexShaderCallback = function() { + Cesium3DTileBatchTable.prototype.getPickVertexShaderCallback = function() { if (this._batchSize === 0) { // Do not change vertex shader source; the model was not batched return undefined; @@ -504,7 +504,7 @@ define([ }; }; - Cesium3DTileBatchData.prototype.getPickFragmentShaderCallback = function() { + Cesium3DTileBatchTable.prototype.getPickFragmentShaderCallback = function() { if (this._batchSize === 0) { // Do not change fragment shader source; the model was not batched return undefined; @@ -551,7 +551,7 @@ define([ }; }; - Cesium3DTileBatchData.prototype.getPickUniformMapCallback = function() { + Cesium3DTileBatchTable.prototype.getPickUniformMapCallback = function() { if (this._batchSize === 0) { // Do not change the uniform map; the model was not batched return undefined; @@ -578,8 +578,8 @@ define([ }; }; - function createTexture(batchData, context, bytes) { - var dimensions = batchData._textureDimensions; + function createTexture(batchTable, context, bytes) { + var dimensions = batchTable._textureDimensions; return new Texture({ context : context, pixelFormat : PixelFormat.RGBA, @@ -596,19 +596,20 @@ define([ }); } - function createPickTexture(batchData, context) { - var batchSize = batchData._batchSize; - if (!defined(batchData._pickTexture) && (batchSize > 0)) { - var pickIds = batchData._pickIds; - var byteLength = getByteLength(batchData); + function createPickTexture(batchTable, context) { + var batchSize = batchTable._batchSize; + if (!defined(batchTable._pickTexture) && (batchSize > 0)) { + var pickIds = batchTable._pickIds; + var byteLength = getByteLength(batchTable); var bytes = new Uint8Array(byteLength); + var contentProvider = batchTable._contentProvider; // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating // a continuous range of pickIds and then converting the base pickId + batchId // to RGBA in the shader. The only consider is precision issues, which might // not be an issue in WebGL 2. for (var i = 0; i < batchSize; ++i) { - var pickId = context.createPickId(batchData._owner.getModel(i)); + var pickId = context.createPickId(contentProvider.getModel(i)); pickIds.push(pickId); var pickColor = pickId.color; @@ -619,32 +620,32 @@ define([ bytes[offset + 3] = Color.floatToByte(pickColor.alpha); } - batchData._pickTexture = createTexture(batchData, context, bytes); + batchTable._pickTexture = createTexture(batchTable, context, bytes); } } - function createBatchTexture(batchData, context) { - if (!defined(batchData._batchTexture)) { - batchData._batchTexture = createTexture(batchData, context, batchData._batchValues); - batchData._batchValuesDirty = false; + function createBatchTexture(batchTable, context) { + if (!defined(batchTable._batchTexture)) { + batchTable._batchTexture = createTexture(batchTable, context, batchTable._batchValues); + batchTable._batchValuesDirty = false; } } - function updateBatchTexture(batchData) { - if (batchData._batchValuesDirty) { - var dimensions = batchData._textureDimensions; + function updateBatchTexture(batchTable) { + if (batchTable._batchValuesDirty) { + var dimensions = batchTable._textureDimensions; // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained // texture updates when less than, for example, 10%, of the values changed. Or // even just optimize the common case when one model show/color changed. - batchData._batchTexture.copyFrom({ + batchTable._batchTexture.copyFrom({ width : dimensions.x, height : dimensions.y, - arrayBufferView : batchData._batchValues + arrayBufferView : batchTable._batchValues }); } } - Cesium3DTileBatchData.prototype.update = function(context, frameState) { + Cesium3DTileBatchTable.prototype.update = function(context, frameState) { this._defaultTexture = context.defaultTexture; if (frameState.passes.pick) { @@ -661,11 +662,11 @@ define([ } }; - Cesium3DTileBatchData.prototype.isDestroyed = function() { + Cesium3DTileBatchTable.prototype.isDestroyed = function() { return false; }; - Cesium3DTileBatchData.prototype.destroy = function() { + Cesium3DTileBatchTable.prototype.destroy = function() { this._batchTexture = this._batchTexture && this._batchTexture.destroy(); this._pickTexture = this._pickTexture && this._pickTexture.destroy(); @@ -678,5 +679,5 @@ define([ return destroyObject(this); }; - return Cesium3DTileBatchData; + return Cesium3DTileBatchTable; }); diff --git a/Source/Scene/Cesium3DTileContentProvider.js b/Source/Scene/Cesium3DTileContentProvider.js index a78733045dff..253654b577a9 100644 --- a/Source/Scene/Cesium3DTileContentProvider.js +++ b/Source/Scene/Cesium3DTileContentProvider.js @@ -10,7 +10,7 @@ define([ * * DOC_TBA */ - var Cesium3DTileContentProvider = function(url, contentHeader) { + var Cesium3DTileContentProvider = function(tileset, tile, url, contentHeader) { /** * @type {Cesium3DTileContentState} * @readonly diff --git a/Source/Scene/Cesium3DTileContentProviderFactory.js b/Source/Scene/Cesium3DTileContentProviderFactory.js index bf4f7ac47694..404042d6837f 100644 --- a/Source/Scene/Cesium3DTileContentProviderFactory.js +++ b/Source/Scene/Cesium3DTileContentProviderFactory.js @@ -13,14 +13,14 @@ define([ * @private */ var Cesium3DTileContentProviderFactory = { - b3dm : function(tileset, url, contentHeader) { - return new Batched3DModel3DTileContentProvider(tileset, url, contentHeader); + b3dm : function(tileset, tile, url, contentHeader) { + return new Batched3DModel3DTileContentProvider(tileset, tile, url, contentHeader); }, - pnts : function(tileset, url, contentHeader) { - return new Points3DTileContentProvider(tileset, url, contentHeader); + pnts : function(tileset, tile, url, contentHeader) { + return new Points3DTileContentProvider(tileset, tile, url, contentHeader); }, - i3dm : function(tileset, url, contentHeader) { - return new Instanced3DModel3DTileContentProvider(tileset, url, contentHeader); + i3dm : function(tileset, tile, url, contentHeader) { + return new Instanced3DModel3DTileContentProvider(tileset, tile, url, contentHeader); } }; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 6951310b592b..cc5fc93d9e6a 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -63,6 +63,7 @@ define([ var baseUrl = appendForwardSlash(url); this._url = url; + this._baseUrl = baseUrl; this._root = undefined; this._properties = undefined; // // Metadata for per-model/point/etc properties this._geometricError = undefined; // Geometric error when the tree is not rendered at all diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index 0484bf6cb034..1a81e6c58d31 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -1,38 +1,45 @@ /*global define*/ define([ '../Core/Cartesian3', + '../Core/defaultValue', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/Ellipsoid', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', '../Core/Matrix4', - '../Core/Quaternion', - './Cesium3DTileBatchData', + '../Core/Transforms', + './Cesium3DTileBatchTable', './Cesium3DTileContentState', './ModelInstanceCollection', + '../ThirdParty/Uri', '../ThirdParty/when' ], function( Cartesian3, + defaultValue, destroyObject, DeveloperError, + Ellipsoid, getStringFromTypedArray, loadArrayBuffer, Matrix4, - Quaternion, - Cesium3DTileBatchData, + Transforms, + Cesium3DTileBatchTable, Cesium3DTileContentState, ModelInstanceCollection, + Uri, when) { "use strict"; /** * DOC_TBA */ - var Instanced3DModel3DTileContentProvider = function(tileset, url, contentHeader) { + var Instanced3DModel3DTileContentProvider = function(tileset, tile, url, contentHeader) { this._modelInstanceCollection = undefined; this._url = url; this._tileset = tileset; - this._batchData = undefined; + this._batchTable = undefined; + this._boundingVolume = defaultValue(tile._contentsOrientedBoundingBox, tile._orientedBoundingBox); /** * @readonly @@ -54,13 +61,13 @@ define([ return this._modelInstanceCollection.getModel(index); }; + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; var sizeOfFloat64 = Float64Array.BYTES_PER_ELEMENT; - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; - // Each vertex has a position, quaternion, and batchId + // Each vertex has a latitude, longitude, and batchId // Coordinates are in double precision, batchId is a short - var instanceSizeInBytes = sizeOfFloat64 * 7 + sizeOfUint16; + var instanceSizeInBytes = sizeOfFloat64 * 2 + sizeOfUint16; function getSubarray(array, offset, length) { return array.subarray(offset, offset + length); @@ -113,11 +120,11 @@ define([ var instancesLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; - var batchData = new Cesium3DTileBatchData(that, instancesLength); - that._batchData = batchData; + var batchTable = new Cesium3DTileBatchTable(that, instancesLength); + that._batchTable = batchTable; if (batchTableLength > 0) { var batchTableString = getStringFromTypedArray(getSubarray(uint8Array, byteOffset, batchTableLength)); - batchData._batchTable = JSON.parse(batchTableString); + batchTable._batchTable = JSON.parse(batchTableString); byteOffset += batchTableLength; } @@ -128,12 +135,11 @@ define([ var instancesView = new DataView(arrayBuffer, byteOffset, instancesDataSizeInBytes); byteOffset += instancesDataSizeInBytes; - // TODO : what is the model's cache key if it doesn't have a url? Url of the tile? // Create model instance collection var collectionOptions = { - instances : [], - batchData : batchData, - boundingVolume : undefined, // TODO : what is the best way to get the tile's bounding volume? + instances : new Array(instancesLength), + batchTable : batchTable, + boundingVolume : that._boundingVolume, pickPrimitive : that._tileset, cull : false, url : undefined, @@ -144,53 +150,45 @@ define([ //>>includeStart('debug', pragmas.debug); if((gltfFormat !== 0) && (gltfFormat !== 1)) { - throw new DeveloperError('glTF format must be 0 or 1.'); + throw new DeveloperError('Only glTF format 0 (url) or 1 (embedded) are supported. Format ' + gltfFormat + ' is not'); } //>>includeEnd('debug'); if (gltfFormat === 0) { - collectionOptions.url = getStringFromTypedArray(gltfView); + var gltfUrl = getStringFromTypedArray(gltfView); + var url = (new Uri(gltfUrl).isAbsolute()) ? gltfUrl : that._tileset._baseUrl + gltfUrl; + collectionOptions.url = url; // TODO : how to get the correct headers } else { collectionOptions.gltf = gltfView; collectionOptions.basePath = that._url; + collectionOptions.cacheKey = that._url; } + var ellipsoid = Ellipsoid.WGS84; var position = new Cartesian3(); - var quaternion = new Quaternion(); - var scale = new Cartesian3(1.0, 1.0, 1.0); - var instances = collectionOptions.instances; byteOffset = 0; for (var i = 0; i < instancesLength; ++i) { - // Get position - position.x = instancesView.getFloat64(byteOffset, true); - byteOffset += sizeOfFloat64; - position.y = instancesView.getFloat64(byteOffset, true); - byteOffset += sizeOfFloat64; - position.z = instancesView.getFloat64(byteOffset, true); + // Get longitude and latitude + var longitude = instancesView.getFloat64(byteOffset, true); byteOffset += sizeOfFloat64; - - // Get rotation quaternion - quaternion.x = instancesView.getFloat64(byteOffset, true); - byteOffset += sizeOfFloat64; - quaternion.y = instancesView.getFloat64(byteOffset, true); - byteOffset += sizeOfFloat64; - quaternion.z = instancesView.getFloat64(byteOffset, true); - byteOffset += sizeOfFloat64; - quaternion.w = instancesView.getFloat64(byteOffset, true); + var latitude = instancesView.getFloat64(byteOffset, true); byteOffset += sizeOfFloat64; + var height = 0.0; // Get batchId var batchId = instancesView.getUint16(byteOffset, true); byteOffset += sizeOfUint16; - var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(position, quaternion, scale); - instances.push({ + Cartesian3.fromRadians(longitude, latitude, height, ellipsoid, position); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(position); + + instances[i] = { modelMatrix : modelMatrix, batchId : batchId - }); + }; } var modelInstanceCollection = new ModelInstanceCollection(collectionOptions); diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js index e9ec184c52cf..d60f889aeb33 100644 --- a/Source/Scene/ModelInstance.js +++ b/Source/Scene/ModelInstance.js @@ -21,12 +21,13 @@ define([ var ModelInstance = function(options, collection, index, pickPrimitive) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); this._collection = collection; - this._batchData = collection._batchData; this._index = index; - this._batchId = defaultValue(options.batchId, index); this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this._color = undefined; // for calling getColor + this._batchTable = collection._batchTable; + this._batchId = defaultValue(options.batchId, index); + this.show = defaultValue(options.show, true); this.color = defaultValue(options.color, Color.WHITE); @@ -48,10 +49,10 @@ define([ show : { get : function() { - return this._batchData.getShow(this._batchId); + return this._batchTable.getShow(this._batchId); }, set : function(value) { - this._batchData.setShow(this._batchId, value); + this._batchTable.setShow(this._batchId, value); } }, @@ -60,20 +61,20 @@ define([ if (!defined(this._color)) { this._color = new Color(); } - return this._batchData.getColor(this._batchId, this._color); + return this._batchTable.getColor(this._batchId, this._color); }, set : function(value) { - this._batchData.setColor(this._batchId, value); + this._batchTable.setColor(this._batchId, value); } } }); ModelInstance.prototype.getProperty = function(name) { - return this._batchData.getProperty(this._batchId, name); + return this._batchTable.getProperty(this._batchId, name); }; ModelInstance.prototype.setProperty = function(name, value) { - this._batchData.setProperty(this._batchId, name, value); + this._batchTable.setProperty(this._batchId, name, value); }; return ModelInstance; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 3ee8c74ed2ed..5526818a9a9f 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -11,6 +11,7 @@ define([ '../Core/defineProperties', '../Core/DeveloperError', '../Core/destroyObject', + '../Core/Math', '../Core/Matrix4', '../Core/PrimitiveType', '../Core/RuntimeError', @@ -19,7 +20,7 @@ define([ '../Renderer/DrawCommand', '../Renderer/ShaderSource', '../ThirdParty/when', - './Cesium3DTileBatchData', + './Cesium3DTileBatchTable', './Model', './ModelInstance', './SceneMode' @@ -35,6 +36,7 @@ define([ defineProperties, DeveloperError, destroyObject, + CesiumMath, Matrix4, PrimitiveType, RuntimeError, @@ -43,7 +45,7 @@ define([ DrawCommand, ShaderSource, when, - Cesium3DTileBatchData, + Cesium3DTileBatchTable, Model, ModelInstance, SceneMode) { @@ -104,11 +106,12 @@ define([ var instances = defaultValue(options.instances, []); var length = instances.length; - var batchData = options.batchData; - if (!defined(batchData)) { - batchData = new Cesium3DTileBatchData(this, length); + var batchTable = options.batchTable; + if (!defined(batchTable)) { + batchTable = new Cesium3DTileBatchTable(this, length); } - this._batchData = batchData; + this._batchTable = batchTable; + this._pickPrimitive = options.pickPrimitive; this._instances = new Array(length); for (var i = 0; i < length; ++i) { @@ -116,13 +119,15 @@ define([ } this._model = undefined; - this._pickPrimitive = options.pickPrimitive; this._vertexBufferValues = undefined; this._vertexBuffer = undefined; this._createVertexBuffer = true; this._vertexBufferDirty = false; this._instancedUniformsByProgram = undefined; + // Set to true if nodes in the model have off-center transforms. When false, the vertex shader can be optimized. + this._offCenter = undefined; + this._drawCommands = []; this._pickCommands = []; this._modelCommands = undefined; @@ -139,30 +144,9 @@ define([ this._cacheKey = options.cacheKey; this._asynchronous = options.asynchronous; - /** - * This property is for debugging only; it is not for production use nor is it optimized. - *

- * Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds - * to one draw command. A glTF mesh has an array of primitives, often of length one. - *

- * - * @type {Boolean} - * - * @default false - */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); this._debugShowBoundingVolume = false; - /** - * This property is for debugging only; it is not for production use nor is it optimized. - *

- * Draws the model in wireframe. - *

- * - * @type {Boolean} - * - * @default false - */ this.debugWireframe = defaultValue(options.debugWireframe, false); this._debugWireframe = false; }; @@ -183,6 +167,11 @@ define([ return this._instances.length; } }, + activeAnimations : { + get : function() { + return this._model.activeAnimations; + } + }, ready : { get : function() { return this._ready; @@ -206,7 +195,7 @@ define([ }; ModelInstanceCollection.prototype._updateInstance = function(instance) { - // TODO : update the whole buffer for now, but later do sub-commits based on the instance's index + // PERFORMANCE_IDEA: do sub-commits based on the instance's index instead of updating the whole buffer this._vertexBufferDirty = true; }; @@ -218,6 +207,25 @@ define([ return i; }; + // TODO : maybe this whole step should be a pre-process as part of the 3d tile toolchain + function isOffCenter(collection) { + if (defined(collection._offCenter)) { + return collection._offCenter; + } + + var nodes = collection._model._runtime.nodes; + for (var name in nodes) { + if (nodes.hasOwnProperty(name)) { + if(!Matrix4.equalsEpsilon(nodes[name].publicNode._matrix, Matrix4.IDENTITY, CesiumMath.EPSILON2)) { + collection._offCenter = true; + return true; + } + } + } + collection._offCenter = false; + return false; + } + function getInstancedUniforms(collection, programName) { if (defined(collection._instancedUniformsByProgram)) { return collection._instancedUniformsByProgram[programName]; @@ -276,11 +284,11 @@ define([ return function(vs, programName) { var instancedUniforms = getInstancedUniforms(collection, programName); var dynamic = collection._dynamic; + var offCenter = isOffCenter(collection); var regex; var renamedSource = ShaderSource.replaceMain(vs, 'czm_old_main'); - // TODO : can I add all these global vars and just let the shader compiler optimize them out var globalVarsHeader = ''; var globalVarsMain = ''; for (var uniform in instancedUniforms) { @@ -309,41 +317,53 @@ define([ } } - var dynamicDefine = dynamic ? '#define DYNAMIC\n' : ''; + var uniforms = ''; + if (!dynamic) { + uniforms += 'uniform mat4 czm_instanced_collectionModelView;\n'; + } + if (offCenter) { + uniforms += 'uniform mat4 czm_instanced_nodeTransform;\n'; + } + + // When dynamic, czm_instanced_model is the modelView matrix. + // Otherwise, czm_instanced_model is the model's local offset from the bounding volume. + // czm_instanced_nodeTransform is the local offset of the node within the model + var modelView = ''; + if (dynamic && offCenter) { + modelView = 'czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeTransform;\n'; + } else if (dynamic && !offCenter) { + modelView = 'czm_instanced_modelView = czm_instanced_model;\n'; + } else if (!dynamic && offCenter) { + modelView = 'czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeTransform;\n'; + } else if (!dynamic && !offCenter) { + modelView = 'czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model;\n'; + } var newMain = - dynamicDefine + + uniforms + globalVarsHeader + + 'mat4 czm_instanced_modelView;\n' + 'attribute vec4 czm_modelMatrixRow0;\n' + 'attribute vec4 czm_modelMatrixRow1;\n' + 'attribute vec4 czm_modelMatrixRow2;\n' + - 'attribute float a_batchId;\n' + // TODO : what if the gltf has this attribute already? - 'uniform mat4 czm_instanced_nodeLocal;\n' + - 'mat4 czm_instanced_modelView;\n' + - '#ifndef DYNAMIC\n' + - 'uniform mat4 czm_instanced_collectionModelView;\n' + - '#endif\n' + + 'attribute float a_batchId;\n' + renamedSource + 'void main()\n' + '{\n' + ' mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n' + - ' #ifdef DYNAMIC\n' + // czm_instanced_model is the modelView matrix - ' czm_instanced_modelView = czm_instanced_model * czm_instanced_nodeLocal;\n' + - ' #else\n' + // czm_instanced_model is the model's local offset from the bounding volume - ' czm_instanced_modelView = czm_instanced_collectionModelView * czm_instanced_model * czm_instanced_nodeLocal;\n' + - ' #endif\n' + + modelView + globalVarsMain + ' czm_old_main();\n' + '}'; vertexShaderCached = newMain; - return collection._batchData.getVertexShaderCallback()(newMain); + return collection._batchTable.getVertexShaderCallback()(newMain); }; } function getFragmentShaderCallback(collection) { return function(fs) { - return collection._batchData.getFragmentShaderCallback()(fs); + return collection._batchTable.getFragmentShaderCallback()(fs); }; } @@ -365,13 +385,13 @@ define([ function getPickVertexShaderCallback(collection) { return function (vs) { // Use the vertex shader that was generated earlier - return collection._batchData.getPickVertexShaderCallback()(vertexShaderCached); + return collection._batchTable.getPickVertexShaderCallback()(vertexShaderCached); }; } function getPickFragmentShaderCallback(collection) { return function(fs) { - return collection._batchData.getPickFragmentShaderCallback()(fs); + return collection._batchTable.getPickFragmentShaderCallback()(fs); }; } @@ -381,7 +401,7 @@ define([ }; } - function createNodeLocalFunction(node) { + function createNodeTransformFunction(node) { return function() { return node.computedMatrix; }; @@ -389,26 +409,31 @@ define([ function getUniformMapCallback(collection, context) { return function(uniformMap, programName, node) { - uniformMap = combine(uniformMap, { - czm_instanced_collectionModelView : (collection._dynamic ? undefined : createBoundsModelViewFunction(collection, context)), - czm_instanced_nodeLocal : createNodeLocalFunction(node) - }); + uniformMap = clone(uniformMap); + + if (!collection._dynamic) { + uniformMap.czm_instanced_collectionModelView = createBoundsModelViewFunction(collection, context); + } + + if (isOffCenter(collection)) { + uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node); + } // Remove instanced uniforms from the uniform map var instancedUniforms = getInstancedUniforms(collection, programName); for (var uniform in instancedUniforms) { if (instancedUniforms.hasOwnProperty(uniform)) { - uniformMap[uniform] = undefined; + delete uniformMap[uniform]; } } - return collection._batchData.getUniformMapCallback()(uniformMap); + return collection._batchTable.getUniformMapCallback()(uniformMap); }; } function getPickUniformMapCallback(collection) { return function(uniformMap) { - return collection._batchData.getPickUniformMapCallback()(uniformMap); + return collection._batchTable.getPickUniformMapCallback()(uniformMap); }; } @@ -476,12 +501,12 @@ define([ function createBoundingVolume(collection) { if (!defined(collection._boundingVolume)) { - var points = []; var instancesLength = collection.length; + var points = new Array(instancesLength); for (var i = 0; i < instancesLength; ++i) { var translation = new Cartesian3(); Matrix4.getTranslation(collection._instances[i].modelMatrix, translation); - points.push(translation); + points[i] = translation; } var boundingSphere = new BoundingSphere(); @@ -566,8 +591,6 @@ define([ var url = collection._url; if (defined(url)) { cacheKey = defaultValue(cacheKey, Model._getDefaultCacheKey(url)); - } - if (defined(cacheKey)) { cacheKey += '#instanced'; } @@ -766,7 +789,7 @@ define([ } if (instancingSupported) { - this._batchData.update(context, frameState); + this._batchTable.update(context, frameState); } var model = this._model; @@ -823,19 +846,13 @@ define([ } }; - /** - * DOC_TBA - */ ModelInstanceCollection.prototype.isDestroyed = function() { return false; }; - /** - * DOC_TBA - */ ModelInstanceCollection.prototype.destroy = function() { this._model = this._model && this._model.destroy(); - this._batchData = this._batchData && this._batchData.destroy(); + this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index f350ed783370..972e9cc3ccf3 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -32,7 +32,7 @@ define([ /** * @private */ - var Points3DTileContentProvider = function(tileset, url, contentHeader) { + var Points3DTileContentProvider = function(tileset, tile, url, contentHeader) { this._primitive = undefined; this._url = url; From 88efe56cedd92d538d59bb1b805c52c71941cd23 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Wed, 14 Oct 2015 12:09:46 -0400 Subject: [PATCH 20/27] Tweak comment --- Source/Scene/Instanced3DModel3DTileContentProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index 1a81e6c58d31..93c63e76ea59 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -65,7 +65,7 @@ define([ var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; var sizeOfFloat64 = Float64Array.BYTES_PER_ELEMENT; - // Each vertex has a latitude, longitude, and batchId + // Each vertex has a longitude, latitude, and batchId // Coordinates are in double precision, batchId is a short var instanceSizeInBytes = sizeOfFloat64 * 2 + sizeOfUint16; From be59252da8e2d778d8a4814b019e66043c133f46 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 20 Oct 2015 10:31:54 -0400 Subject: [PATCH 21/27] Removed Instancing 3D Tiles demo, using Cities instead --- Apps/Sandcastle/gallery/Cities.html | 10 ++- .../3D Models Instancing 3D Tiles.html | 76 ------------------- 2 files changed, 6 insertions(+), 80 deletions(-) delete mode 100644 Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html diff --git a/Apps/Sandcastle/gallery/Cities.html b/Apps/Sandcastle/gallery/Cities.html index 0879e758ab78..8551facc2a75 100644 --- a/Apps/Sandcastle/gallery/Cities.html +++ b/Apps/Sandcastle/gallery/Cities.html @@ -32,8 +32,9 @@ var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; var city = scene.primitives.add(new Cesium.Cesium3DTileset({ -// url : 'http://localhost:8002/tilesets/Cambridge', - url : 'http://localhost:8002/tilesets/London_Canary_Wharf', + //url : 'http://localhost:8002/tilesets/Cambridge', + //url : 'http://localhost:8002/tilesets/London_Canary_Wharf', + url : 'http://localhost:8002/tilesets/trees', // Defaults: maximumScreenSpaceError : 16, @@ -46,8 +47,9 @@ })); viewer.camera.setView({ -// position : Cesium.Cartesian3.fromDegrees(-71.1106, 42.3736, 5000.0) // Cambridge - position : Cesium.Cartesian3.fromDegrees(0.0183, 51.5036, 5000.0) // Canary Wharf + //position : Cesium.Cartesian3.fromDegrees(-71.1106, 42.3736, 5000.0) // Cambridge + //position : Cesium.Cartesian3.fromDegrees(0.0183, 51.5036, 5000.0) // Canary Wharf + position : Cesium.Cartesian3.fromRadians(-1.2911323805815227, 0.7097150757974291, 5000.0) // NYC Trees }); scene.debugShowFramesPerSecond = true; diff --git a/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html b/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html deleted file mode 100644 index a79b247f64b8..000000000000 --- a/Apps/Sandcastle/gallery/development/3D Models Instancing 3D Tiles.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -
-

Loading...

-
- - - From bc10897278e442e548fff8894bf68c089cbf78cc Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 20 Oct 2015 17:56:00 -0400 Subject: [PATCH 22/27] Prevent instance collections from sharing the same Model --- Apps/Sandcastle/gallery/Cities.html | 9 +++++++-- .../Instanced3DModel3DTileContentProvider.js | 1 - Source/Scene/Model.js | 11 +--------- Source/Scene/ModelInstanceCollection.js | 20 ++++++------------- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/Apps/Sandcastle/gallery/Cities.html b/Apps/Sandcastle/gallery/Cities.html index 8551facc2a75..ddafc66fe4a5 100644 --- a/Apps/Sandcastle/gallery/Cities.html +++ b/Apps/Sandcastle/gallery/Cities.html @@ -34,7 +34,10 @@ var city = scene.primitives.add(new Cesium.Cesium3DTileset({ //url : 'http://localhost:8002/tilesets/Cambridge', //url : 'http://localhost:8002/tilesets/London_Canary_Wharf', - url : 'http://localhost:8002/tilesets/trees', + //url : 'http://localhost:8002/tilesets/trees', + //url : 'http://localhost:8002/tilesets/Philly', + //url : 'http://localhost:8002/tilesets/Seattle', + url : 'http://localhost:8002/tilesets/SeattleTrees', // Defaults: maximumScreenSpaceError : 16, @@ -49,7 +52,9 @@ viewer.camera.setView({ //position : Cesium.Cartesian3.fromDegrees(-71.1106, 42.3736, 5000.0) // Cambridge //position : Cesium.Cartesian3.fromDegrees(0.0183, 51.5036, 5000.0) // Canary Wharf - position : Cesium.Cartesian3.fromRadians(-1.2911323805815227, 0.7097150757974291, 5000.0) // NYC Trees + //position : Cesium.Cartesian3.fromRadians(-1.2911323805815227, 0.7097150757974291, 5000.0) // NYC Trees + //position : Cesium.Cartesian3.fromDegrees(-75.1667, 39.9500, 5000.0) // Philly trees + position : Cesium.Cartesian3.fromDegrees(-122.3331, 47.6097, 5000.0) // Seattle }); scene.debugShowFramesPerSecond = true; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index 93c63e76ea59..4a2e08d8f9ea 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -162,7 +162,6 @@ define([ } else { collectionOptions.gltf = gltfView; collectionOptions.basePath = that._url; - collectionOptions.cacheKey = that._url; } var ellipsoid = Ellipsoid.WGS84; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 3f79c05ba6c5..080d3c90b820 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -841,15 +841,6 @@ define([ return JSON.parse(json); } - /** - * Get the default cache key from the provided url - * - * @private - */ - Model._getDefaultCacheKey = function(url) { - return getAbsoluteURL(url); - }; - /** *

* Creates a model from a glTF asset. When the model is ready to render, i.e., when the external binary, image, @@ -922,7 +913,7 @@ define([ var url = options.url; // If no cache key is provided, use the absolute URL, since two URLs with // different relative paths could point to the same model. - var cacheKey = defaultValue(options.cacheKey, Model._getDefaultCacheKey(url)); + var cacheKey = defaultValue(options.cacheKey, getAbsoluteURL(url)); options = clone(options); options.basePath = getBasePath(url); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 5526818a9a9f..b6a6516aab79 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -141,7 +141,6 @@ define([ this._headers = options.headers; this._gltf = options.gltf; this._basePath = options.basePath; - this._cacheKey = options.cacheKey; this._asynchronous = options.asynchronous; this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); @@ -524,9 +523,10 @@ define([ headers : collection._headers, gltf : collection._gltf, basePath : collection._basePath, - cacheKey : collection._cacheKey, + cacheKey : undefined, asynchronous : collection._asynchronous, allowPicking : collection._allowPicking, + releaseGltfJson : false, precreatedAttributes : undefined, vertexShaderLoaded : undefined, fragmentShaderLoaded : undefined, @@ -586,14 +586,6 @@ define([ } }; - // Instanced models will create different renderer resources, so change the cache key. - var cacheKey = collection._cacheKey; - var url = collection._url; - if (defined(url)) { - cacheKey = defaultValue(cacheKey, Model._getDefaultCacheKey(url)); - cacheKey += '#instanced'; - } - modelOptions.precreatedAttributes = instancedAttributes; modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection); @@ -601,8 +593,11 @@ define([ modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(collection); modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(collection); modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); - modelOptions.cacheKey = cacheKey; modelOptions.ignoreCommands = true; + modelOptions.releaseGltfJson = true; + + // Collections cannot share the same models, so create a unique cache key + modelOptions.cacheKey = 'ModelInstanceCollection' + Math.random(); } else { modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(); } @@ -637,13 +632,11 @@ define([ var allowPicking = collection.allowPicking; var boundingVolume = collection._boundingVolume; - var boundingVolumeModel = collection._boundingVolumeModel; if (collection._instancingSupported) { for (i = 0; i < commandsLength; ++i) { command = clone(drawCommands[i]); command.instanceCount = instancesLength; - command.modelMatrix = boundingVolumeModel; command.boundingVolume = boundingVolume; command.cull = collection._cull; collection._drawCommands.push(command); @@ -651,7 +644,6 @@ define([ if (allowPicking) { command = clone(pickCommands[i]); command.instanceCount = instancesLength; - command.modelMatrix = boundingVolumeModel; command.boundingVolume = boundingVolume; command.cull = collection._cull; collection._pickCommands.push(command); From 948ad727f11008cc1b4fc28fe8eef3e01334c5a6 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 21 Oct 2015 15:55:29 -0400 Subject: [PATCH 23/27] Reverted previous commit, now share the same Model but create unique vertex arrays --- Source/Scene/Model.js | 15 +++++++++++++++ Source/Scene/ModelInstanceCollection.js | 7 +++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 080d3c90b820..0d7c8093b35f 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -2848,6 +2848,11 @@ define([ resources.textures = cachedResources.textures; resources.samplers = cachedResources.samplers; resources.renderStates = cachedResources.renderStates; + + // Vertex arrays are unique to this model, create instead of using the cache. + if (defined(model._precreatedAttributes)) { + createVertexArrays(model, context); + } } else { createDecompressedViews(model, context); createBuffers(model, context, frameState); // using glTF bufferViews @@ -3302,6 +3307,11 @@ define([ cachedResources.renderStates = resources.renderStates; cachedResources.ready = true; + // Vertex arrays are unique to this model, do not store in cache. + if (defined(this._precreatedAttributes)) { + cachedResources.vertexArrays = {}; + } + if (this.releaseGltfJson) { releaseCachedGltf(this); } @@ -3422,6 +3432,11 @@ define([ * model = model && model.destroy(); */ Model.prototype.destroy = function() { + // Vertex arrays are unique to this model, destroy here. + if (defined(this._precreatedAttributes)) { + destroy(this._rendererResources.vertexArrays); + } + this._rendererResources = undefined; this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release(); diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index b6a6516aab79..31615f062d37 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -526,7 +526,6 @@ define([ cacheKey : undefined, asynchronous : collection._asynchronous, allowPicking : collection._allowPicking, - releaseGltfJson : false, precreatedAttributes : undefined, vertexShaderLoaded : undefined, fragmentShaderLoaded : undefined, @@ -594,10 +593,10 @@ define([ modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(collection); modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); modelOptions.ignoreCommands = true; - modelOptions.releaseGltfJson = true; - // Collections cannot share the same models, so create a unique cache key - modelOptions.cacheKey = 'ModelInstanceCollection' + Math.random(); + if (defined(collection._url)) { + modelOptions.cacheKey = collection._url + '#instanced'; + } } else { modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(); } From 69b858db243a01aa693f8971f5964214364d21c9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 22 Oct 2015 23:19:30 -0400 Subject: [PATCH 24/27] Fixed memory leak in non-instanced code path --- Source/Scene/ModelInstanceCollection.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 31615f062d37..521496ce79af 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -533,7 +533,7 @@ define([ pickVertexShaderLoaded : undefined, pickFragmentShaderLoaded : undefined, pickUniformMapLoaded : undefined, - ignoreCommands : false + ignoreCommands : true }; createBoundingVolume(collection); @@ -592,7 +592,6 @@ define([ modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(collection); modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(collection); modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); - modelOptions.ignoreCommands = true; if (defined(collection._url)) { modelOptions.cacheKey = collection._url + '#instanced'; From 9ae609d97e8e8bb1c2bc15f32d8540fd680d0b4e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 22 Oct 2015 23:53:42 -0400 Subject: [PATCH 25/27] Renamed Cesium3dTileBatchTable to Cesium3DTileBatchTableResources to avoid ambiguity --- Apps/Sandcastle/gallery/Cities.html | 4 +- .../Batched3DModel3DTileContentProvider.js | 28 ++--- Source/Scene/BatchedModel.js | 16 +-- ....js => Cesium3DTileBatchTableResources.js} | 114 ++++++++++-------- .../Instanced3DModel3DTileContentProvider.js | 14 +-- Source/Scene/ModelInstance.js | 14 +-- Source/Scene/ModelInstanceCollection.js | 28 ++--- 7 files changed, 117 insertions(+), 101 deletions(-) rename Source/Scene/{Cesium3DTileBatchTable.js => Cesium3DTileBatchTableResources.js} (85%) diff --git a/Apps/Sandcastle/gallery/Cities.html b/Apps/Sandcastle/gallery/Cities.html index ddafc66fe4a5..bc806555e55c 100644 --- a/Apps/Sandcastle/gallery/Cities.html +++ b/Apps/Sandcastle/gallery/Cities.html @@ -36,8 +36,8 @@ //url : 'http://localhost:8002/tilesets/London_Canary_Wharf', //url : 'http://localhost:8002/tilesets/trees', //url : 'http://localhost:8002/tilesets/Philly', - //url : 'http://localhost:8002/tilesets/Seattle', - url : 'http://localhost:8002/tilesets/SeattleTrees', + url : 'http://localhost:8002/tilesets/Seattle', + //url : 'http://localhost:8002/tilesets/SeattleTrees', // Defaults: maximumScreenSpaceError : 16, diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index 65c78138c526..789dac85aebe 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -7,7 +7,7 @@ define([ '../Core/DeveloperError', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', - './Cesium3DTileBatchTable', + './Cesium3DTileBatchTableResources', './Cesium3DTileContentState', './Model', './BatchedModel', @@ -20,7 +20,7 @@ define([ DeveloperError, getStringFromTypedArray, loadArrayBuffer, - Cesium3DTileBatchTable, + Cesium3DTileBatchTableResources, Cesium3DTileContentState, Model, BatchedModel, @@ -52,7 +52,7 @@ define([ var batchSize = defaultValue(contentHeader.batchSize, 0); this._batchSize = batchSize; - this._batchTable = new Cesium3DTileBatchTable(this, batchSize); + this._batchTableResources = new Cesium3DTileBatchTableResources(this, batchSize); this._models = undefined; }; @@ -78,7 +78,7 @@ define([ if (!defined(content._models) && (batchSize > 0)) { var models = new Array(batchSize); for (var i = 0; i < batchSize; ++i) { - models[i] = new BatchedModel(tileset, content._batchTable, i); + models[i] = new BatchedModel(tileset, content._batchTableResources, i); } content._models = models; } @@ -113,7 +113,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var batchTable = this._batchTable; + var batchTableResources = this._batchTableResources; this.state = Cesium3DTileContentState.LOADING; @@ -153,7 +153,7 @@ define([ // // We could also make another request for it, but that would make the property set/get // API async, and would double the number of numbers in some cases. - batchTable._batchTable = JSON.parse(batchTableString); + batchTableResources.batchTable = JSON.parse(batchTableString); } var gltfView = new Uint8Array(arrayBuffer, byteOffset, arrayBuffer.byteLength - byteOffset); @@ -164,12 +164,12 @@ define([ gltf : gltfView, cull : false, // The model is already culled by the 3D tiles releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory - vertexShaderLoaded : batchTable.getVertexShaderCallback(), - fragmentShaderLoaded : batchTable.getFragmentShaderCallback(), - uniformMapLoaded : batchTable.getUniformMapCallback(), - pickVertexShaderLoaded : batchTable.getPickVertexShaderCallback(), - pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(), - pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), + vertexShaderLoaded : batchTableResources.getVertexShaderCallback(), + fragmentShaderLoaded : batchTableResources.getFragmentShaderCallback(), + uniformMapLoaded : batchTableResources.getUniformMapCallback(), + pickVertexShaderLoaded : batchTableResources.getPickVertexShaderCallback(), + pickFragmentShaderLoaded : batchTableResources.getPickFragmentShaderCallback(), + pickUniformMapLoaded : batchTableResources.getPickUniformMapCallback(), basePath : that._url }); @@ -194,7 +194,7 @@ define([ // the content's resource loading. In the READY state, it will // actually generate commands. - this._batchTable.update(context, frameState); + this._batchTableResources.update(context, frameState); this._model.update(context, frameState, commandList); }; @@ -210,7 +210,7 @@ define([ */ Batched3DModel3DTileContentProvider.prototype.destroy = function() { this._model = this._model && this._model.destroy(); - this._batchTable = this._batchTable && this._batchTable.destroy(); + this._batchTableResources = this._batchTableResources && this._batchTableResources.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/BatchedModel.js b/Source/Scene/BatchedModel.js index 26d89b086c47..dc80a82e2a18 100644 --- a/Source/Scene/BatchedModel.js +++ b/Source/Scene/BatchedModel.js @@ -10,8 +10,8 @@ define([ /** * DOC_TBA */ - var BatchedModel = function(tileset, batchTable, batchId) { - this._batchTable = batchTable; + var BatchedModel = function(tileset, batchTableResources, batchId) { + this._batchTableResources = batchTableResources; this._batchId = batchId; this._color = undefined; // for calling getColor @@ -29,10 +29,10 @@ define([ */ show : { get : function() { - return this._batchTable.getShow(this._batchId); + return this._batchTableResources.getShow(this._batchId); }, set : function(value) { - this._batchTable.setShow(this._batchId, value); + this._batchTableResources.setShow(this._batchId, value); } }, @@ -44,10 +44,10 @@ define([ if (!this._color) { this._color = new Color(); } - return this._batchTable.getColor(this._batchId, this._color); + return this._batchTableResources.getColor(this._batchId, this._color); }, set : function(value) { - this._batchTable.setColor(this._batchId, value); + this._batchTableResources.setColor(this._batchId, value); } } }); @@ -56,14 +56,14 @@ define([ * DOC_TBA */ BatchedModel.prototype.getProperty = function(name) { - return this._batchTable.getProperty(this._batchId, name); + return this._batchTableResources.getProperty(this._batchId, name); }; /** * DOC_TBA */ BatchedModel.prototype.setProperty = function(name, value) { - this._batchTable.setProperty(this._batchId, name, value); + this._batchTableResources.setProperty(this._batchId, name, value); }; return BatchedModel; diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTableResources.js similarity index 85% rename from Source/Scene/Cesium3DTileBatchTable.js rename to Source/Scene/Cesium3DTileBatchTableResources.js index 48d83e39aaa7..fdf9b5e192c9 100644 --- a/Source/Scene/Cesium3DTileBatchTable.js +++ b/Source/Scene/Cesium3DTileBatchTableResources.js @@ -44,7 +44,7 @@ define([ * * @private */ - var Cesium3DTileBatchTable = function(contentProvider, size) { + var Cesium3DTileBatchTableResources = function(contentProvider, size) { this._batchSize = defaultValue(size, 0); this._batchValues = undefined; // Per-model show/color this._batchValuesDirty = false; @@ -80,11 +80,11 @@ define([ this._textureStep = textureStep; }; - defineProperties(Cesium3DTileBatchTable.prototype, { + defineProperties(Cesium3DTileBatchTableResources.prototype, { /** * DOC_TBA * - * @memberof Cesium3DTileBatchTable.prototype + * @memberof Cesium3DTileBatchTableResources.prototype * * @type {Number} * @readonly @@ -93,30 +93,46 @@ define([ get : function() { return this._batchSize; } + }, + + /** + * DOC_TBA + * + * @memberof Cesium3DTileBatchTableResources.prototype + * + * @type {Object} + */ + batchTable : { + get : function() { + return this._batchTable; + }, + set : function(batchTable) { + this._batchTable = batchTable; + } } }); - function getByteLength(batchTable) { - var dimensions = batchTable._textureDimensions; + function getByteLength(batchTableResources) { + var dimensions = batchTableResources._textureDimensions; return (dimensions.x * dimensions.y) * 4; } - function getBatchValues(batchTable) { - if (!defined(batchTable._batchValues)) { + function getBatchValues(batchTableResources) { + if (!defined(batchTableResources._batchValues)) { // Default batch texture to RGBA = 255: white highlight (RGB) and show = true (A). - var byteLength = getByteLength(batchTable); + var byteLength = getByteLength(batchTableResources); var bytes = new Uint8Array(byteLength); for (var i = 0; i < byteLength; ++i) { bytes[i] = 255; } - batchTable._batchValues = bytes; + batchTableResources._batchValues = bytes; } - return batchTable._batchValues; + return batchTableResources._batchValues; } - Cesium3DTileBatchTable.prototype.setShow = function(batchId, value) { + Cesium3DTileBatchTableResources.prototype.setShow = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -143,7 +159,7 @@ define([ } }; - Cesium3DTileBatchTable.prototype.getShow = function(batchId) { + Cesium3DTileBatchTableResources.prototype.getShow = function(batchId) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -162,7 +178,7 @@ define([ var scratchColor = new Array(4); - Cesium3DTileBatchTable.prototype.setColor = function(batchId, value) { + Cesium3DTileBatchTableResources.prototype.setColor = function(batchId, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -193,7 +209,7 @@ define([ } }; - Cesium3DTileBatchTable.prototype.setAllColor = function(value) { + Cesium3DTileBatchTableResources.prototype.setAllColor = function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError('value is required.'); @@ -207,7 +223,7 @@ define([ } }; - Cesium3DTileBatchTable.prototype.getColor = function(batchId, color) { + Cesium3DTileBatchTableResources.prototype.getColor = function(batchId, color) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -229,7 +245,7 @@ define([ return Color.fromBytes(batchValues[offset], batchValues[offset + 1], batchValues[offset + 2], 255, color); }; - Cesium3DTileBatchTable.prototype.hasProperty = function(name) { + Cesium3DTileBatchTableResources.prototype.hasProperty = function(name) { //>>includeStart('debug', pragmas.debug); if (!defined(name)) { throw new DeveloperError('name is required.'); @@ -240,7 +256,7 @@ define([ return defined(batchTable) && defined(batchTable[name]); }; - Cesium3DTileBatchTable.prototype.getPropertyNames = function() { + Cesium3DTileBatchTableResources.prototype.getPropertyNames = function() { var names = []; var batchTable = this._batchTable; @@ -257,7 +273,7 @@ define([ return names; }; - Cesium3DTileBatchTable.prototype.getProperty = function(batchId, name) { + Cesium3DTileBatchTableResources.prototype.getProperty = function(batchId, name) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -282,7 +298,7 @@ define([ return clone(propertyValues[batchId], true); }; - Cesium3DTileBatchTable.prototype.setProperty = function(batchId, name, value) { + Cesium3DTileBatchTableResources.prototype.setProperty = function(batchId, name, value) { var batchSize = this._batchSize; //>>includeStart('debug', pragmas.debug); if (!defined(batchId) || (batchId < 0) || (batchId > batchSize)) { @@ -310,9 +326,9 @@ define([ propertyValues[batchId] = clone(value, true); }; - function getGlslComputeSt(batchTable) { + function getGlslComputeSt(batchTableResources) { // GLSL batchId is zero-based: [0, batchSize - 1] - if (batchTable._textureDimensions.y === 1) { + if (batchTableResources._textureDimensions.y === 1) { return 'uniform vec4 tiles3d_textureStep; \n' + 'vec2 computeSt(float batchId) \n' + '{ \n' + @@ -336,7 +352,7 @@ define([ '} \n'; } - Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function() { + Cesium3DTileBatchTableResources.prototype.getVertexShaderCallback = function() { if (this._batchSize === 0) { // Do not change vertex shader source; the model was not batched return undefined; @@ -375,7 +391,7 @@ define([ }; }; - Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function() { + Cesium3DTileBatchTableResources.prototype.getFragmentShaderCallback = function() { if (this._batchSize === 0) { // Do not change fragment shader source; the model was not batched return undefined; @@ -440,7 +456,7 @@ define([ }; }; - Cesium3DTileBatchTable.prototype.getUniformMapCallback = function() { + Cesium3DTileBatchTableResources.prototype.getUniformMapCallback = function() { if (this._batchSize === 0) { // Do not change the uniform map; the model was not batched return undefined; @@ -465,7 +481,7 @@ define([ }; }; - Cesium3DTileBatchTable.prototype.getPickVertexShaderCallback = function() { + Cesium3DTileBatchTableResources.prototype.getPickVertexShaderCallback = function() { if (this._batchSize === 0) { // Do not change vertex shader source; the model was not batched return undefined; @@ -504,7 +520,7 @@ define([ }; }; - Cesium3DTileBatchTable.prototype.getPickFragmentShaderCallback = function() { + Cesium3DTileBatchTableResources.prototype.getPickFragmentShaderCallback = function() { if (this._batchSize === 0) { // Do not change fragment shader source; the model was not batched return undefined; @@ -551,7 +567,7 @@ define([ }; }; - Cesium3DTileBatchTable.prototype.getPickUniformMapCallback = function() { + Cesium3DTileBatchTableResources.prototype.getPickUniformMapCallback = function() { if (this._batchSize === 0) { // Do not change the uniform map; the model was not batched return undefined; @@ -578,8 +594,8 @@ define([ }; }; - function createTexture(batchTable, context, bytes) { - var dimensions = batchTable._textureDimensions; + function createTexture(batchTableResources, context, bytes) { + var dimensions = batchTableResources._textureDimensions; return new Texture({ context : context, pixelFormat : PixelFormat.RGBA, @@ -596,13 +612,13 @@ define([ }); } - function createPickTexture(batchTable, context) { - var batchSize = batchTable._batchSize; - if (!defined(batchTable._pickTexture) && (batchSize > 0)) { - var pickIds = batchTable._pickIds; - var byteLength = getByteLength(batchTable); + function createPickTexture(batchTableResources, context) { + var batchSize = batchTableResources._batchSize; + if (!defined(batchTableResources._pickTexture) && (batchSize > 0)) { + var pickIds = batchTableResources._pickIds; + var byteLength = getByteLength(batchTableResources); var bytes = new Uint8Array(byteLength); - var contentProvider = batchTable._contentProvider; + var contentProvider = batchTableResources._contentProvider; // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating // a continuous range of pickIds and then converting the base pickId + batchId @@ -620,32 +636,32 @@ define([ bytes[offset + 3] = Color.floatToByte(pickColor.alpha); } - batchTable._pickTexture = createTexture(batchTable, context, bytes); + batchTableResources._pickTexture = createTexture(batchTableResources, context, bytes); } } - function createBatchTexture(batchTable, context) { - if (!defined(batchTable._batchTexture)) { - batchTable._batchTexture = createTexture(batchTable, context, batchTable._batchValues); - batchTable._batchValuesDirty = false; + function createBatchTexture(batchTableResources, context) { + if (!defined(batchTableResources._batchTexture)) { + batchTableResources._batchTexture = createTexture(batchTableResources, context, batchTableResources._batchValues); + batchTableResources._batchValuesDirty = false; } } - function updateBatchTexture(batchTable) { - if (batchTable._batchValuesDirty) { - var dimensions = batchTable._textureDimensions; + function updateBatchTexture(batchTableResources) { + if (batchTableResources._batchValuesDirty) { + var dimensions = batchTableResources._textureDimensions; // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained // texture updates when less than, for example, 10%, of the values changed. Or // even just optimize the common case when one model show/color changed. - batchTable._batchTexture.copyFrom({ + batchTableResources._batchTexture.copyFrom({ width : dimensions.x, height : dimensions.y, - arrayBufferView : batchTable._batchValues + arrayBufferView : batchTableResources._batchValues }); } } - Cesium3DTileBatchTable.prototype.update = function(context, frameState) { + Cesium3DTileBatchTableResources.prototype.update = function(context, frameState) { this._defaultTexture = context.defaultTexture; if (frameState.passes.pick) { @@ -662,11 +678,11 @@ define([ } }; - Cesium3DTileBatchTable.prototype.isDestroyed = function() { + Cesium3DTileBatchTableResources.prototype.isDestroyed = function() { return false; }; - Cesium3DTileBatchTable.prototype.destroy = function() { + Cesium3DTileBatchTableResources.prototype.destroy = function() { this._batchTexture = this._batchTexture && this._batchTexture.destroy(); this._pickTexture = this._pickTexture && this._pickTexture.destroy(); @@ -679,5 +695,5 @@ define([ return destroyObject(this); }; - return Cesium3DTileBatchTable; + return Cesium3DTileBatchTableResources; }); diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index 4a2e08d8f9ea..db911b6b2074 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -9,7 +9,7 @@ define([ '../Core/loadArrayBuffer', '../Core/Matrix4', '../Core/Transforms', - './Cesium3DTileBatchTable', + './Cesium3DTileBatchTableResources', './Cesium3DTileContentState', './ModelInstanceCollection', '../ThirdParty/Uri', @@ -24,7 +24,7 @@ define([ loadArrayBuffer, Matrix4, Transforms, - Cesium3DTileBatchTable, + Cesium3DTileBatchTableResources, Cesium3DTileContentState, ModelInstanceCollection, Uri, @@ -38,7 +38,7 @@ define([ this._modelInstanceCollection = undefined; this._url = url; this._tileset = tileset; - this._batchTable = undefined; + this._batchTableResources = undefined; this._boundingVolume = defaultValue(tile._contentsOrientedBoundingBox, tile._orientedBoundingBox); /** @@ -120,11 +120,11 @@ define([ var instancesLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; - var batchTable = new Cesium3DTileBatchTable(that, instancesLength); - that._batchTable = batchTable; + var batchTableResources = new Cesium3DTileBatchTableResources(that, instancesLength); + that._batchTableResources = batchTableResources; if (batchTableLength > 0) { var batchTableString = getStringFromTypedArray(getSubarray(uint8Array, byteOffset, batchTableLength)); - batchTable._batchTable = JSON.parse(batchTableString); + batchTableResources.batchTable = JSON.parse(batchTableString); byteOffset += batchTableLength; } @@ -138,7 +138,7 @@ define([ // Create model instance collection var collectionOptions = { instances : new Array(instancesLength), - batchTable : batchTable, + batchTableResources : batchTableResources, boundingVolume : that._boundingVolume, pickPrimitive : that._tileset, cull : false, diff --git a/Source/Scene/ModelInstance.js b/Source/Scene/ModelInstance.js index d60f889aeb33..acac634cd022 100644 --- a/Source/Scene/ModelInstance.js +++ b/Source/Scene/ModelInstance.js @@ -25,7 +25,7 @@ define([ this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this._color = undefined; // for calling getColor - this._batchTable = collection._batchTable; + this._batchTableResources = collection._batchTableResources; this._batchId = defaultValue(options.batchId, index); this.show = defaultValue(options.show, true); @@ -49,10 +49,10 @@ define([ show : { get : function() { - return this._batchTable.getShow(this._batchId); + return this._batchTableResources.getShow(this._batchId); }, set : function(value) { - this._batchTable.setShow(this._batchId, value); + this._batchTableResources.setShow(this._batchId, value); } }, @@ -61,20 +61,20 @@ define([ if (!defined(this._color)) { this._color = new Color(); } - return this._batchTable.getColor(this._batchId, this._color); + return this._batchTableResources.getColor(this._batchId, this._color); }, set : function(value) { - this._batchTable.setColor(this._batchId, value); + this._batchTableResources.setColor(this._batchId, value); } } }); ModelInstance.prototype.getProperty = function(name) { - return this._batchTable.getProperty(this._batchId, name); + return this._batchTableResources.getProperty(this._batchId, name); }; ModelInstance.prototype.setProperty = function(name, value) { - this._batchTable.setProperty(this._batchId, name, value); + this._batchTableResources.setProperty(this._batchId, name, value); }; return ModelInstance; diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 521496ce79af..9c365ce92537 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -20,7 +20,7 @@ define([ '../Renderer/DrawCommand', '../Renderer/ShaderSource', '../ThirdParty/when', - './Cesium3DTileBatchTable', + './Cesium3DTileBatchTableResources', './Model', './ModelInstance', './SceneMode' @@ -45,7 +45,7 @@ define([ DrawCommand, ShaderSource, when, - Cesium3DTileBatchTable, + Cesium3DTileBatchTableResources, Model, ModelInstance, SceneMode) { @@ -106,11 +106,11 @@ define([ var instances = defaultValue(options.instances, []); var length = instances.length; - var batchTable = options.batchTable; - if (!defined(batchTable)) { - batchTable = new Cesium3DTileBatchTable(this, length); + var batchTableResources = options.batchTableResources; + if (!defined(batchTableResources)) { + batchTableResources = new Cesium3DTileBatchTableResources(this, length); } - this._batchTable = batchTable; + this._batchTableResources = batchTableResources; this._pickPrimitive = options.pickPrimitive; this._instances = new Array(length); @@ -356,13 +356,13 @@ define([ '}'; vertexShaderCached = newMain; - return collection._batchTable.getVertexShaderCallback()(newMain); + return collection._batchTableResources.getVertexShaderCallback()(newMain); }; } function getFragmentShaderCallback(collection) { return function(fs) { - return collection._batchTable.getFragmentShaderCallback()(fs); + return collection._batchTableResources.getFragmentShaderCallback()(fs); }; } @@ -384,13 +384,13 @@ define([ function getPickVertexShaderCallback(collection) { return function (vs) { // Use the vertex shader that was generated earlier - return collection._batchTable.getPickVertexShaderCallback()(vertexShaderCached); + return collection._batchTableResources.getPickVertexShaderCallback()(vertexShaderCached); }; } function getPickFragmentShaderCallback(collection) { return function(fs) { - return collection._batchTable.getPickFragmentShaderCallback()(fs); + return collection._batchTableResources.getPickFragmentShaderCallback()(fs); }; } @@ -426,13 +426,13 @@ define([ } } - return collection._batchTable.getUniformMapCallback()(uniformMap); + return collection._batchTableResources.getUniformMapCallback()(uniformMap); }; } function getPickUniformMapCallback(collection) { return function(uniformMap) { - return collection._batchTable.getPickUniformMapCallback()(uniformMap); + return collection._batchTableResources.getPickUniformMapCallback()(uniformMap); }; } @@ -779,7 +779,7 @@ define([ } if (instancingSupported) { - this._batchTable.update(context, frameState); + this._batchTableResources.update(context, frameState); } var model = this._model; @@ -842,7 +842,7 @@ define([ ModelInstanceCollection.prototype.destroy = function() { this._model = this._model && this._model.destroy(); - this._batchTable = this._batchTable && this._batchTable.destroy(); + this._batchTableResources = this._batchTableResources && this._batchTableResources.destroy(); return destroyObject(this); }; From a49b6fdba1ea2dd4ddd12a7905c7135c9263c0f4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 22 Oct 2015 23:58:53 -0400 Subject: [PATCH 26/27] Removed baseUrl from Cesium3DTileset --- Source/Scene/Cesium3DTileset.js | 9 ++++----- Source/Scene/Instanced3DModel3DTileContentProvider.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index cc5fc93d9e6a..1facd2d4b7d9 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -60,10 +60,9 @@ define([ } //>>includeEnd('debug'); - var baseUrl = appendForwardSlash(url); + url = appendForwardSlash(url); this._url = url; - this._baseUrl = baseUrl; this._root = undefined; this._properties = undefined; // // Metadata for per-model/point/etc properties this._geometricError = undefined; // Geometric error when the tree is not rendered at all @@ -147,10 +146,10 @@ define([ var that = this; - loadJson(baseUrl + 'tiles.json').then(function(tree) { + loadJson(url + 'tiles.json').then(function(tree) { that._properties = tree.properties; that._geometricError = tree.geometricError; - that._root = new Cesium3DTile(that, baseUrl, tree.root, undefined); + that._root = new Cesium3DTile(that, url, tree.root, undefined); var stack = []; stack.push({ @@ -166,7 +165,7 @@ define([ var length = children.length; for (var k = 0; k < length; ++k) { var childHeader = children[k]; - var childTile = new Cesium3DTile(that, baseUrl, childHeader, t.cesium3DTile); + var childTile = new Cesium3DTile(that, url, childHeader, t.cesium3DTile); t.cesium3DTile.children.push(childTile); stack.push({ diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index db911b6b2074..918ee88deebf 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -156,7 +156,7 @@ define([ if (gltfFormat === 0) { var gltfUrl = getStringFromTypedArray(gltfView); - var url = (new Uri(gltfUrl).isAbsolute()) ? gltfUrl : that._tileset._baseUrl + gltfUrl; + var url = (new Uri(gltfUrl).isAbsolute()) ? gltfUrl : that._tileset.url + gltfUrl; collectionOptions.url = url; // TODO : how to get the correct headers } else { From e19f70a17ad546cc91d3ee8e31ef40607a36a6f7 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 23 Oct 2015 10:00:12 -0400 Subject: [PATCH 27/27] Small change --- Source/Renderer/ShaderSource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js index f8abc251bd0d..6cefa42abeb0 100644 --- a/Source/Renderer/ShaderSource.js +++ b/Source/Renderer/ShaderSource.js @@ -330,7 +330,7 @@ define([ } ShaderSource.createPickVertexShaderSource = function(vertexShaderSource) { - var renamedVS = vertexShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); + var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_old_main'); var pickMain = 'attribute vec4 pickColor; \n' + 'varying vec4 czm_pickColor; \n' + 'void main() \n' + @@ -343,7 +343,7 @@ define([ }; ShaderSource.createPickFragmentShaderSource = function(fragmentShaderSource, pickColorQualifier) { - var renamedFS = fragmentShaderSource.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()'); + var renamedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_old_main'); var pickMain = pickColorQualifier + ' vec4 czm_pickColor; \n' + 'void main() \n' + '{ \n' +