diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index ad484109de43..7ce93a967d65 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -317,7 +317,7 @@ function Model(options) { } this._instanceFeatureIdLabel = instanceFeatureIdLabel; - this._runtimeInstancesLength = 0; + this._runtimeInstancesDirty = false; this._featureTables = []; this._featureTableId = undefined; @@ -2260,21 +2260,28 @@ function updateVerticalExaggeration(model, frameState) { } function updateRuntimeModelInstances(model) { - if ( - model.sceneGraph.modelInstances.length !== model._runtimeInstancesLength - ) { - model.resetDrawCommands(); - model._runtimeInstancesLength = model.sceneGraph.modelInstances.length; - } let instance; for (let i = 0; i < model.sceneGraph.modelInstances.length; i++) { instance = model.sceneGraph.modelInstances.get(i); + if (instance._dirty) { if (!model.sceneGraph.modelInstances._dirty) { model.sceneGraph.modelInstances._dirty = true; } instance._dirty = false; } + + if (instance._drawDirty) { + if (!model._runtimeInstancesDirty) { + model._runtimeInstancesDirty = true; + } + instance._drawDirty = false; + } + } + + if (model._runtimeInstancesDirty) { + model.resetDrawCommands(); + model._runtimeInstancesDirty = false; } } diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index 5130c78a5703..5995d9262610 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -5,6 +5,8 @@ import Matrix3 from "../../Core/Matrix3.js"; import Matrix4 from "../../Core/Matrix4.js"; import TranslationRotationScale from "../../Core/TranslationRotationScale.js"; import Quaternion from "../../Core/Quaternion.js"; +import Color from "../../Core/Color.js"; +import defined from "../../Core/defined.js"; const scratchTranslationRotationScale = new TranslationRotationScale(); const scratchRotation = new Matrix3(); @@ -21,7 +23,12 @@ class ModelInstance { * Constructs a {@link ModelInstance}, a copy of a {@link Model} mesh, for efficiently rendering a large number of copies the same model using GPU mesh instancing. * The position, orientation, and scale of the instance is determined by the specified {@link Matrix4}. * @constructor - * @param {Matrix4} transform Matrix4 describing the transform of the instance + * + * @param {object} options Object with the following properties: + * @param {Matrix4} options.transform Matrix4 describing the transform of the instance. + * @param {boolean} [options.show=true] Determines if the instance in the collection will be shown. + * @param {Color} [options.color] A color that blends with the instance rendered color + * * @example * const position = Cesium.Cartesian3.fromDegrees(-75.1652, 39.9526); * @@ -36,21 +43,27 @@ class ModelInstance { * Cesium.Ellipsoid.WGS84, * fixedFrameTransform, * ); - * const modelInstance = new Cesium.ModelInstance(instanceModelMatrix); + * const modelInstance = new Cesium.ModelInstance({ + * transform: instanceModelMatrix + * }); */ - constructor(transform) { + constructor(options) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("transform", transform); + Check.typeOf.object("options", options); + Check.typeOf.object("options.transform", options.transform); //>>includeEnd('debug'); - this._transform = transform; + this._transform = options.transform; this._center = new Cartesian3(); this._relativeTransform = new Matrix4(); this._relativeScaledTransform = new Matrix4(); this._pickId = undefined; + this.show = options.show ?? true; + this.color = options.color; - this._updateTransform(transform); + this._updateTransform(options.transform); this._dirty = false; + this._drawDirty = false; } /** @@ -117,15 +130,60 @@ class ModelInstance { return this._relativeScaledTransform; } - /** - * The Pick Id of the instance. - * @type {string|undefined} - * @readonly - */ get pickId() { return this._pickId; } + /** + * Whether or not to render the model instance. + * + * @type {boolean} + * + * @default true + */ + get show() { + return this._show; + } + set show(value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool("show", value); + //>>includeEnd('debug'); + + if (this._show === value) { + return; + } + + this._show = value; + this._dirty = true; + } + + /** + * The Color of the instance. + * @type {Color} + */ + get color() { + return this._color; + } + set color(value) { + //>>includeStart('debug', pragmas.debug); + if (defined(value)) { + Check.typeOf.object("color", value); + } + //>>includeEnd('debug'); + + if ( + this._color === value || + (defined(this._color) && + defined(value) && + Color.equals(this._color, value)) + ) { + return; + } + + this._color = value; + this._drawDirty = true; + } + _updateTransform(transform) { // Get center from the transform this._center = Matrix4.getTranslation(transform, this._center); diff --git a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js index ff95e5fa1319..4ae8c2a462ee 100644 --- a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js +++ b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js @@ -1,4 +1,3 @@ -import Check from "../../Core/Check.js"; import defined from "../../Core/defined.js"; import DeveloperError from "../../Core/DeveloperError.js"; import ModelInstance from "./ModelInstance.js"; @@ -36,7 +35,9 @@ import RuntimeError from "../../Core/RuntimeError.js"; * * // Add an instance at the specified transform to a collection * const collection = new Cesium.ModelInstanceCollection(); - * collection.add(instanceModelMatrix); + * collection.add({ + * transform: instanceModelMatrix + * }); * * // Add an instance to a model * const model = await Cesium.Model.fromGltfAsync({ @@ -44,7 +45,9 @@ import RuntimeError from "../../Core/RuntimeError.js"; * minimumPixelSize: 64, * }); * viewer.scene.primitives.add(model); - * model.instances.add(instanceModelMatrix); + * model.instances.add({ + * transform: instanceModelMatrix + * }); */ function ModelInstanceCollection(options) { this._instances = []; @@ -75,17 +78,25 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { } for (let i = 0; i < transforms.length; i++) { - const transform = transforms[i]; - const instance = new ModelInstance(transform); + const instance = new ModelInstance({ + transform: transforms[i], + }); this._instances.push(instance); } + + if (transforms.length > 0 && this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } }; /** * Creates and adds an instance with the specified transform to the collection. * The added instance is returned so it can be modified or removed from the collection later. * - * @param {Matrix4} transform A transform that represents an instance of a Model + * @param {object} options Object with the following properties: + * @param {Matrix4} options.transform Matrix4 describing the transform of the instance. + * @param {boolean} [options.show=true] Determines if the billboards in the collection will be shown. + * @param {Color} [options.color] Determines if the billboards in the collection will be shown. * @returns {ModelInstance} The model instance that was added to the collection. * * @performance Calling add is expected constant time. However, the collection's vertex buffer @@ -95,24 +106,27 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { * @example * // Example: Provide a transform to add a model instance to the collection * const collection = new ModelInstanceCollection() - * const instance = collection.add(transform) + * const instance = collection.add({ + * transform: instanceTransform + * }) * * @see ModelInstanceCollection#remove * @see ModelInstanceCollection#removeAll */ -ModelInstanceCollection.prototype.add = function (transform) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("transform", transform); - //>>includeEnd('debug'); - +ModelInstanceCollection.prototype.add = function (options) { if (this._model?._loader._hasMeshGpuInstancing) { throw new RuntimeError( "Models with the EXT_mesh_gpu_instancing extension cannot use the ModelInstanceCollection class.", ); } - const instance = new ModelInstance(transform, this); + const instance = new ModelInstance(options); this._instances.push(instance); + + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } + return instance; }; @@ -132,7 +146,9 @@ ModelInstanceCollection.prototype.add = function (transform) { * * * @example - * const instance = collection.add(transform); + * const instance = collection.add({ + * transform: transform + * }); * collection.remove(instance); // Returns true * * @see ModelInstanceCollection#add @@ -152,6 +168,10 @@ ModelInstanceCollection.prototype.remove = function (instance) { this._instances.splice(index, 1); + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } + return true; }; @@ -172,6 +192,10 @@ ModelInstanceCollection.prototype.remove = function (instance) { ModelInstanceCollection.prototype.removeAll = function () { const instances = this._instances; instances.length = 0; + + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } }; /** diff --git a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js index 5d898aa5ead9..6bc85e3e54fa 100644 --- a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js @@ -49,7 +49,10 @@ ModelInstancesUpdateStage.update = function ( */ function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { const modelInstances = sceneGraph.modelInstances._instances; - const buffer = runtimeNode.instancingTransformsBuffer; + + const showsTypedArray = + RuntimeModelInstancingPipelineStage._getShowsTypedArray(modelInstances); + runtimeNode.instanceShowsBuffer.copyFromArrayView(showsTypedArray); const transformsTypedArray = RuntimeModelInstancingPipelineStage._getTransformsTypedArray( @@ -61,8 +64,9 @@ function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { runtimeNode.instancingTransformsBuffer.copyFromArrayView( transformsTypedArray, ); - - runtimeNode.instancingTransformsBuffer = buffer; + const colorsTypedArray = + RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + runtimeNode.instanceColorsBuffer.copyFromArrayView(colorsTypedArray); const childrenLength = runtimeNode.children.length; diff --git a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js index d00911b2daa3..956611de8579 100644 --- a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js @@ -9,6 +9,8 @@ import BufferUsage from "../../Renderer/BufferUsage.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import InstancingStageCommon from "../../Shaders/Model/InstancingStageCommon.js"; import RuntimeModelInstancingPipelineStageVS from "../../Shaders/Model/RuntimeModelInstancingPipelineStageVS.js"; +import RuntimeModelInstancingPipelineStageFS from "../../Shaders/Model/RuntimeModelInstancingPipelineStageFS.js"; +import ColorBlendMode from "../ColorBlendMode.js"; const nodeTransformScratch = new Matrix4(); const relativeScaledTransformScratch = new Matrix4(); @@ -41,6 +43,9 @@ RuntimeModelInstancingPipelineStage.process = function ( frameState, ) { const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addVarying("float", "v_gex_show"); + shaderBuilder.addVarying("vec4", "v_gex_instanceColor"); + shaderBuilder.addDefine("HAS_INSTANCING"); shaderBuilder.addDefine("HAS_INSTANCE_MATRICES"); shaderBuilder.addDefine( @@ -48,9 +53,17 @@ RuntimeModelInstancingPipelineStage.process = function ( undefined, ShaderDestination.VERTEX, ); + shaderBuilder.addDefine( + "USE_API_INSTANCING", + undefined, + ShaderDestination.FRAGMENT, + ); + shaderBuilder.addVertexLines(InstancingStageCommon); shaderBuilder.addVertexLines(RuntimeModelInstancingPipelineStageVS); + shaderBuilder.addFragmentLines(RuntimeModelInstancingPipelineStageFS); + const model = renderResources.model; const sceneGraph = model.sceneGraph; @@ -75,6 +88,23 @@ RuntimeModelInstancingPipelineStage.process = function ( renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap); }; +RuntimeModelInstancingPipelineStage._getShowsTypedArray = function ( + modelInstances, +) { + const showsTypedArray = new Uint8Array(modelInstances.length); + + for (let i = 0; i < modelInstances.length; i++) { + const modelInstance = modelInstances[i]; + if (!defined(modelInstance)) { + continue; + } + + showsTypedArray[i] = modelInstance.show ? 255 : 0; + } + + return showsTypedArray; +}; + RuntimeModelInstancingPipelineStage._getTransformsTypedArray = function ( modelInstances, model, @@ -122,6 +152,34 @@ RuntimeModelInstancingPipelineStage._getTransformsTypedArray = function ( return transformsTypedArray; }; +RuntimeModelInstancingPipelineStage._getColorsTypedArray = function ( + modelInstances, +) { + const colorsTypedArray = new Uint8Array(modelInstances.length * 4); + + for (let i = 0; i < modelInstances.length; i++) { + const modelInstance = modelInstances[i]; + if (!defined(modelInstance)) { + continue; + } + + const color = modelInstance.color; + + if (!defined(color)) { + continue; + } + + const o = i * 4; + + colorsTypedArray[o + 0] = Math.round(color.red * 255); + colorsTypedArray[o + 1] = Math.round(color.green * 255); + colorsTypedArray[o + 2] = Math.round(color.blue * 255); + colorsTypedArray[o + 3] = Math.round(color.alpha * 255); + } + + return colorsTypedArray; +}; + RuntimeModelInstancingPipelineStage._createAttributes = function ( frameState, renderResources, @@ -131,6 +189,18 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( const usage = BufferUsage.STATIC_DRAW; // Create typed array and buffer + const showsTypedArray = + RuntimeModelInstancingPipelineStage._getShowsTypedArray( + modelInstances, + renderResources.model, + frameState, + ); + const showsBuffer = Buffer.createVertexBuffer({ + context, + usage, + typedArray: showsTypedArray, + }); + const transformsTypedArray = RuntimeModelInstancingPipelineStage._getTransformsTypedArray( modelInstances, @@ -143,14 +213,28 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( typedArray: transformsTypedArray, }); + const colorsTypedArray = + RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + const colorsBuffer = Buffer.createVertexBuffer({ + context, + usage, + typedArray: colorsTypedArray, + }); + + renderResources.runtimeNode.instanceShowsBuffer = showsBuffer; renderResources.runtimeNode.instancingTransformsBuffer = transformsBuffer; + renderResources.runtimeNode.instanceColorsBuffer = colorsBuffer; // Destruction of resources allocated by the Model // is handled by Model.destroy(). + showsBuffer.vertexArrayDestroyable = false; transformsBuffer.vertexArrayDestroyable = false; + colorsBuffer.vertexArrayDestroyable = false; // Add attribute declarations const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addAttribute("float", "a_gex_show"); + shaderBuilder.addAttribute("vec4", `a_instancingTransformRow0`); shaderBuilder.addAttribute("vec4", `a_instancingTransformRow1`); shaderBuilder.addAttribute("vec4", `a_instancingTransformRow2`); @@ -158,6 +242,8 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( shaderBuilder.addAttribute("vec3", `a_instancingPositionHigh`); shaderBuilder.addAttribute("vec3", `a_instancingPositionLow`); + shaderBuilder.addAttribute("vec4", "a_gex_instanceColor"); + // Create attributes const vertexSizeInFloats = 18; const componentByteSize = ComponentDatatype.getSizeInBytes( @@ -166,6 +252,16 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( const strideInBytes = componentByteSize * vertexSizeInFloats; const attributes = [ + { + index: renderResources.attributeIndex++, + vertexBuffer: showsBuffer, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.BYTE, + normalize: true, + offsetInBytes: 0, + strideInBytes: 1, + instanceDivisor: 1, + }, { index: renderResources.attributeIndex++, vertexBuffer: transformsBuffer, @@ -216,6 +312,16 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( strideInBytes: strideInBytes, instanceDivisor: 1, }, + { + index: renderResources.attributeIndex++, + vertexBuffer: colorsBuffer, + componentsPerAttribute: 4, + componentDatatype: ComponentDatatype.UNSIGNED_BYTE, + normalize: true, + offsetInBytes: 0, + strideInBytes: 4, + instanceDivisor: 1, + }, ]; return attributes; @@ -226,12 +332,19 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( sceneGraph, ) { const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addUniform( "mat4", "u_instance_nodeTransform", ShaderDestination.VERTEX, ); + shaderBuilder.addUniform( + "float", + "gex_instanceColorBlend", + ShaderDestination.FRAGMENT, + ); + const runtimeNode = renderResources.runtimeNode; const uniformMap = { @@ -251,6 +364,13 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( nodeTransformScratch, ); }, + + gex_instanceColorBlend: () => { + return ColorBlendMode.getColorBlend( + renderResources.model.colorBlendMode, + renderResources.model.colorBlendAmount, + ); + }, }; return uniformMap; diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 33f106ea02b9..8f53a31ac1c4 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -83,6 +83,10 @@ void main() primitiveOutlineStage(material); #endif + #ifdef USE_API_INSTANCING + RuntimeModelInstancingStage(material); + #endif + vec4 color = handleAlpha(material.diffuse, material.alpha); // When not picking metadata END diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl new file mode 100644 index 000000000000..061c8ad3a3a3 --- /dev/null +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl @@ -0,0 +1,18 @@ +void RuntimeModelInstancingStage(inout czm_modelMaterial material) +{ + if (v_gex_show == 0.0) { + discard; + } + + if (v_gex_instanceColor.r == 0.0 && + v_gex_instanceColor.g == 0.0 && + v_gex_instanceColor.b == 0.0 && + v_gex_instanceColor.a == 0.0) { + return; + } + + material.diffuse = mix(material.diffuse, v_gex_instanceColor.rgb, gex_instanceColorBlend); + float highlight = ceil(gex_instanceColorBlend); + material.diffuse *= mix(v_gex_instanceColor.rgb, vec3(1.0), highlight); + material.alpha *= v_gex_instanceColor.a; +} \ No newline at end of file diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl index 7e77449e8d47..dae69a019bd0 100644 --- a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl @@ -3,6 +3,8 @@ void RuntimeModelInstancingStage( out mat4 instanceModelView, out mat3 instanceModelViewInverseTranspose) { + v_gex_show = a_gex_show; + vec3 positionMC = attributes.positionMC; mat4 instancingTransform = getInstancingTransform(); @@ -15,4 +17,6 @@ void RuntimeModelInstancingStage( instanceModelView = czm_modelViewRelativeToEye; instanceModelViewInverseTranspose = mat3(czm_modelViewRelativeToEye * instanceModel); + + v_gex_instanceColor = a_gex_instanceColor; } diff --git a/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js b/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js index 046d9e2a1601..844c86172575 100644 --- a/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js @@ -49,15 +49,21 @@ describe("Scene/Model/ModelInstanceCollection", function () { }); it("can add an instance", function () { - const instance = collection.add(sampleTransform1); + const instance = collection.add({ + transform: sampleTransform1, + }); expect(collection.length).toEqual(1); expect(collection.get(0)).toBe(instance); }); it("can remove an instance", function () { - const sampleInstance1 = collection.add(sampleTransform1); - collection.add(sampleTransform2); + const sampleInstance1 = collection.add({ + transform: sampleTransform1, + }); + collection.add({ + transform: sampleTransform2, + }); const removed = collection.remove(sampleInstance1); @@ -67,8 +73,12 @@ describe("Scene/Model/ModelInstanceCollection", function () { }); it("can remove all instances", function () { - collection.add(sampleTransform1); - collection.add(sampleTransform2); + collection.add({ + transform: sampleTransform1, + }); + collection.add({ + transform: sampleTransform2, + }); expect(collection.length).toEqual(2); collection.removeAll(); expect(collection.length).toEqual(0); diff --git a/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js b/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js index 1b06a51d5721..a602f04ddfec 100644 --- a/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js @@ -7,6 +7,7 @@ import { Cartesian3, Ellipsoid, BoundingSphere, + Color, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; @@ -47,11 +48,15 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); expect(instance.transform).toEqual(instanceModelMatrix); expect(instance.center).toEqual(new Cartesian3()); - expect(instance.relativeTransform).toEqual(instanceModelMatrix); + expect(instance.transform).toEqual(instanceModelMatrix); + expect(instance.show).toEqual(true); + expect(instance.color).toEqual(undefined); }); it("creates an instance with translation", async function () { @@ -68,7 +73,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); expect(instance.transform).toEqual(instanceModelMatrix); const center = new Cartesian3(10, 10, 10); @@ -108,7 +115,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); const sampleModelMatrix = Matrix4.IDENTITY; const sampleRootNodeTransform = Matrix4.IDENTITY; @@ -224,7 +233,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); // values based on the Primitive for the "Wheels" Node in the CesiumMilkTruck const sampleModel = { @@ -309,7 +320,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); // values based on the Primitive for the "Wheels" Node in the CesiumMilkTruck const sampleModel = { @@ -385,6 +398,31 @@ describe( expect(primitiveBoundingSphere.center).toEqual(boundingSphereCenter); expect(primitiveBoundingSphere.radius).toEqual(boundingSphereRadius); }); + + it("creates an instance with show and color", async function () { + const position = new Cartesian3(0, 0, 0); + + const headingPositionRoll = new HeadingPitchRoll(); + const fixedFrameTransform = Transforms.localFrameToFixedFrameGenerator( + "north", + "west", + ); + const instanceModelMatrix = new Transforms.headingPitchRollToFixedFrame( + position, + headingPositionRoll, + Ellipsoid.WGS84, + fixedFrameTransform, + ); + + const instance = new ModelInstance({ + transform: instanceModelMatrix, + show: false, + color: Color.RED, + }); + + expect(instance.show).toEqual(false); + expect(instance.color).toEqual(Color.RED); + }); }, "WebGL", ); diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 5f4d193d3c80..15077a2ea2c3 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -79,8 +79,12 @@ describe( fixedFrameTransform, ); - const sampleInstance1 = new ModelInstance(instanceModelMatrix1); - const sampleInstance2 = new ModelInstance(instanceModelMatrix2); + const sampleInstance1 = new ModelInstance({ + transform: instanceModelMatrix1, + }); + const sampleInstance2 = new ModelInstance({ + transform: instanceModelMatrix2, + }); function mockRenderResources(node) { return { @@ -141,7 +145,7 @@ describe( scene.frameState, ); - expect(renderResources.attributes.length).toBe(5); + expect(renderResources.attributes.length).toBe(7); const shaderBuilder = renderResources.shaderBuilder; ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ @@ -152,18 +156,27 @@ describe( ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ "HAS_INSTANCE_MATRICES", "HAS_INSTANCING", + "USE_API_INSTANCING", + ]); + ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ + "float v_gex_show;", + "vec4 v_gex_instanceColor;", ]); ShaderBuilderTester.expectHasAttributes(shaderBuilder, undefined, [ + "in float a_gex_show;", "in vec4 a_instancingTransformRow0;", "in vec4 a_instancingTransformRow1;", "in vec4 a_instancingTransformRow2;", "in vec3 a_instancingPositionHigh;", "in vec3 a_instancingPositionLow;", + "in vec4 a_gex_instanceColor;", ]); - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, [ "uniform mat4 u_instance_nodeTransform;", ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float gex_instanceColorBlend;", + ]); expect(runtimeNode.instancingTransformsBuffer).toBeDefined(); // The resource will be counted by NodeStatisticsPipelineStage. @@ -283,8 +296,12 @@ describe( instanceModelMatrix4, ); - const sampleInstance3 = new ModelInstance(instanceModelMatrix3); - const sampleInstance4 = new ModelInstance(instanceModelMatrix4); + const sampleInstance3 = new ModelInstance({ + transform: instanceModelMatrix3, + }); + const sampleInstance4 = new ModelInstance({ + transform: instanceModelMatrix4, + }); // mock resources for ModelInstancesUpdateStage sceneGraph.modelInstances._instances = [sampleInstance3, sampleInstance4];