cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
990 lines (849 loc) • 28.7 kB
JavaScript
import AttributeType from "../AttributeType.js";
import Buffer from "../../Renderer/Buffer.js";
import BufferUsage from "../../Renderer/BufferUsage.js";
import Cartesian3 from "../../Core/Cartesian3.js";
import clone from "../../Core/clone.js";
import combine from "../../Core/combine.js";
import ComponentDatatype from "../../Core/ComponentDatatype.js";
import defined from "../../Core/defined.js";
import InstanceAttributeSemantic from "../InstanceAttributeSemantic.js";
import InstancingStageCommon from "../../Shaders/ModelExperimental/InstancingStageCommon.js";
import InstancingStageVS from "../../Shaders/ModelExperimental/InstancingStageVS.js";
import LegacyInstancingStageVS from "../../Shaders/ModelExperimental/LegacyInstancingStageVS.js";
import Matrix4 from "../../Core/Matrix4.js";
import ModelExperimentalUtility from "./ModelExperimentalUtility.js";
import Quaternion from "../../Core/Quaternion.js";
import SceneMode from "../SceneMode.js";
import SceneTransforms from "../SceneTransforms.js";
import ShaderDestination from "../../Renderer/ShaderDestination.js";
import Transforms from "../../Core/Transforms.js";
const modelViewScratch = new Matrix4();
const nodeTransformScratch = new Matrix4();
const modelView2DScratch = new Matrix4();
/**
* The instancing pipeline stage is responsible for handling GPU mesh instancing at the node
* level.
*
* @namespace InstancingPipelineStage
* @private
*/
const InstancingPipelineStage = {};
InstancingPipelineStage.name = "InstancingPipelineStage"; // Helps with debugging
/**
* Process a node. This modifies the following parts of the render resources:
* <ul>
* <li> creates buffers for the typed arrays of each attribute, if they do not yet exist
* <li> adds attribute declarations for the instancing vertex attributes in the vertex shader</li>
* <li> adds an instancing translation min and max to compute an accurate bounding volume</li>
* </ul>
*
* If the scene is in either 2D or CV mode, this stage also:
* <ul>
* <li> adds additional attributes for the transformation attributes projected to 2D
* <li> adds a flag to the shader to use the 2D instanced attributes
* <li> adds a uniform for the view model matrix in 2D
* </ul>
*
* @param {NodeRenderResources} renderResources The render resources for this node.
* @param {ModelComponents.Node} node The node.
* @param {FrameState} frameState The frame state.
*/
InstancingPipelineStage.process = function (renderResources, node, frameState) {
const instances = node.instances;
const count = instances.attributes[0].count;
const shaderBuilder = renderResources.shaderBuilder;
shaderBuilder.addDefine("HAS_INSTANCING");
shaderBuilder.addVertexLines([InstancingStageCommon]);
const model = renderResources.model;
const sceneGraph = model.sceneGraph;
const use2D =
frameState.mode !== SceneMode.SCENE3D &&
!frameState.scene3DOnly &&
model._projectTo2D;
const instancingVertexAttributes = [];
processTransformAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes,
use2D
);
processFeatureIdAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes
);
const uniformMap = {};
if (instances.transformInWorldSpace) {
shaderBuilder.addDefine(
"USE_LEGACY_INSTANCING",
undefined,
ShaderDestination.VERTEX
);
shaderBuilder.addUniform(
"mat4",
"u_instance_modifiedModelView",
ShaderDestination.VERTEX
);
shaderBuilder.addUniform(
"mat4",
"u_instance_nodeTransform",
ShaderDestination.VERTEX
);
// The i3dm format applies the instancing transforms in world space.
// Instancing matrices come from a vertex attribute rather than a
// uniform, and they are multiplied in the middle of the modelView matrix
// product. This means czm_modelView can't be used. Instead, we split the
// matrix into two parts, modifiedModelView and nodeTransform, and handle
// this in LegacyInstancingStageVS.glsl. Conceptually the product looks like
// this:
//
// modelView = u_modifiedModelView * a_instanceTransform * u_nodeTransform
uniformMap.u_instance_modifiedModelView = function () {
// Model matrix without the node hierarchy or axis correction
// (see u_instance_nodeTransform).
let modifiedModelMatrix = Matrix4.multiplyTransformation(
// For 3D Tiles, model.modelMatrix is the computed tile
// transform (which includes tileset.modelMatrix). This always applies
// for i3dm, since such models are always part of a tileset.
model.modelMatrix,
// For i3dm models, components.transform contains the RTC_CENTER
// translation.
sceneGraph.components.transform,
modelViewScratch
);
if (use2D) {
// If projectTo2D is enabled, the 2D view matrix
// will be accounted for in the u_modelView2D
// uniform.
//
// modifiedModelView = view3D * modifiedModel
return Matrix4.multiplyTransformation(
frameState.context.uniformState.view3D,
modifiedModelMatrix,
modelViewScratch
);
}
// For projection to 2D without projectTo2D enabled,
// project the model matrix to 2D.
if (frameState.mode !== SceneMode.SCENE3D) {
modifiedModelMatrix = Transforms.basisTo2D(
frameState.mapProjection,
modifiedModelMatrix,
modelViewScratch
);
}
// modifiedModelView = view * modifiedModel
return Matrix4.multiplyTransformation(
frameState.context.uniformState.view,
modifiedModelMatrix,
modelViewScratch
);
};
uniformMap.u_instance_nodeTransform = function () {
// nodeTransform = axisCorrection * nodeHierarchyTransform
return Matrix4.multiplyTransformation(
// glTF y-up to 3D Tiles z-up
sceneGraph.axisCorrectionMatrix,
// This transforms from the node's coordinate system to the root
// of the node hierarchy
renderResources.runtimeNode.computedTransform,
nodeTransformScratch
);
};
shaderBuilder.addVertexLines([LegacyInstancingStageVS]);
} else {
shaderBuilder.addVertexLines([InstancingStageVS]);
}
if (use2D) {
shaderBuilder.addDefine(
"USE_2D_INSTANCING",
undefined,
ShaderDestination.VERTEX
);
shaderBuilder.addUniform("mat4", "u_modelView2D", ShaderDestination.VERTEX);
const context = frameState.context;
const modelMatrix2D = Matrix4.fromTranslation(
renderResources.instancingReferencePoint2D,
new Matrix4()
);
uniformMap.u_modelView2D = function () {
return Matrix4.multiplyTransformation(
context.uniformState.view,
modelMatrix2D,
modelView2DScratch
);
};
}
renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap);
renderResources.instanceCount = count;
renderResources.attributes.push.apply(
renderResources.attributes,
instancingVertexAttributes
);
};
const projectedTransformScratch = new Matrix4();
const projectedPositionScratch = new Cartesian3();
function projectTransformTo2D(
transform,
modelMatrix,
nodeTransform,
frameState,
result
) {
let projectedTransform = Matrix4.multiplyTransformation(
modelMatrix,
transform,
projectedTransformScratch
);
projectedTransform = Matrix4.multiplyTransformation(
projectedTransform,
nodeTransform,
projectedTransformScratch
);
result = Transforms.basisTo2D(
frameState.mapProjection,
projectedTransform,
result
);
return result;
}
function projectPositionTo2D(
position,
modelMatrix,
nodeTransform,
frameState,
result
) {
const translationMatrix = Matrix4.fromTranslation(
position,
projectedTransformScratch
);
let projectedTransform = Matrix4.multiplyTransformation(
modelMatrix,
translationMatrix,
projectedTransformScratch
);
projectedTransform = Matrix4.multiplyTransformation(
projectedTransform,
nodeTransform,
projectedTransformScratch
);
const finalPosition = Matrix4.getTranslation(
projectedTransform,
projectedPositionScratch
);
result = SceneTransforms.computeActualWgs84Position(
frameState,
finalPosition,
result
);
return result;
}
function getModelMatrixAndNodeTransform(
renderResources,
modelMatrix,
nodeComputedTransform
) {
const model = renderResources.model;
const sceneGraph = model.sceneGraph;
const instances = renderResources.runtimeNode.node.instances;
if (instances.transformInWorldSpace) {
// Replicate the multiplication order in LegacyInstancingStageVS.
modelMatrix = Matrix4.multiplyTransformation(
model.modelMatrix,
sceneGraph.components.transform,
modelMatrix
);
nodeComputedTransform = Matrix4.multiplyTransformation(
sceneGraph.axisCorrectionMatrix,
renderResources.runtimeNode.computedTransform,
nodeComputedTransform
);
} else {
// The node transform should be pre-multiplied with the instancing transform.
modelMatrix = Matrix4.clone(sceneGraph.computedModelMatrix, modelMatrix);
modelMatrix = Matrix4.multiplyTransformation(
modelMatrix,
renderResources.runtimeNode.computedTransform,
modelMatrix
);
nodeComputedTransform = Matrix4.clone(
Matrix4.IDENTITY,
nodeComputedTransform
);
}
}
const modelMatrixScratch = new Matrix4();
const nodeComputedTransformScratch = new Matrix4();
const transformScratch = new Matrix4();
const positionScratch = new Cartesian3();
function projectTransformsTo2D(
transforms,
renderResources,
frameState,
result
) {
const modelMatrix = modelMatrixScratch;
const nodeComputedTransform = nodeComputedTransformScratch;
getModelMatrixAndNodeTransform(
renderResources,
modelMatrix,
nodeComputedTransform
);
const referencePoint = renderResources.instancingReferencePoint2D;
const count = transforms.length;
for (let i = 0; i < count; i++) {
const transform = transforms[i];
const projectedTransform = projectTransformTo2D(
transform,
modelMatrix,
nodeComputedTransform,
frameState,
transformScratch
);
const position = Matrix4.getTranslation(
projectedTransform,
positionScratch
);
const finalTranslation = Cartesian3.subtract(
position,
referencePoint,
position
);
result[i] = Matrix4.setTranslation(
projectedTransform,
finalTranslation,
result[i]
);
}
return result;
}
function projectTranslationsTo2D(
translations,
renderResources,
frameState,
result
) {
const modelMatrix = modelMatrixScratch;
const nodeComputedTransform = nodeComputedTransformScratch;
getModelMatrixAndNodeTransform(
renderResources,
modelMatrix,
nodeComputedTransform
);
const referencePoint = renderResources.instancingReferencePoint2D;
const count = translations.length;
for (let i = 0; i < count; i++) {
const translation = translations[i];
const projectedPosition = projectPositionTo2D(
translation,
modelMatrix,
nodeComputedTransform,
frameState,
translation
);
result[i] = Cartesian3.subtract(
projectedPosition,
referencePoint,
result[i]
);
}
return result;
}
const scratchProjectedMin = new Cartesian3();
const scratchProjectedMax = new Cartesian3();
function computeReferencePoint2D(renderResources, frameState) {
// Compute the reference point by averaging the instancing translation
// min / max values after they are projected to 2D.
const modelMatrix = renderResources.model.sceneGraph.computedModelMatrix;
const transformedPositionMin = Matrix4.multiplyByPoint(
modelMatrix,
renderResources.instancingTranslationMin,
scratchProjectedMin
);
const projectedMin = SceneTransforms.computeActualWgs84Position(
frameState,
transformedPositionMin,
transformedPositionMin
);
const transformedPositionMax = Matrix4.multiplyByPoint(
modelMatrix,
renderResources.instancingTranslationMax,
scratchProjectedMax
);
const projectedMax = SceneTransforms.computeActualWgs84Position(
frameState,
transformedPositionMax,
transformedPositionMax
);
return Cartesian3.lerp(projectedMin, projectedMax, 0.5, new Cartesian3());
}
function transformsToTypedArray(transforms) {
const elements = 12;
const count = transforms.length;
const transformsTypedArray = new Float32Array(count * elements);
for (let i = 0; i < count; i++) {
const transform = transforms[i];
const offset = elements * i;
transformsTypedArray[offset + 0] = transform[0];
transformsTypedArray[offset + 1] = transform[4];
transformsTypedArray[offset + 2] = transform[8];
transformsTypedArray[offset + 3] = transform[12];
transformsTypedArray[offset + 4] = transform[1];
transformsTypedArray[offset + 5] = transform[5];
transformsTypedArray[offset + 6] = transform[9];
transformsTypedArray[offset + 7] = transform[13];
transformsTypedArray[offset + 8] = transform[2];
transformsTypedArray[offset + 9] = transform[6];
transformsTypedArray[offset + 10] = transform[10];
transformsTypedArray[offset + 11] = transform[14];
}
return transformsTypedArray;
}
function translationsToTypedArray(translations) {
const elements = 3;
const count = translations.length;
const transationsTypedArray = new Float32Array(count * elements);
for (let i = 0; i < count; i++) {
const translation = translations[i];
const offset = elements * i;
transationsTypedArray[offset + 0] = translation[0];
transationsTypedArray[offset + 1] = translation[4];
transationsTypedArray[offset + 2] = translation[8];
}
return transationsTypedArray;
}
const translationScratch = new Cartesian3();
const rotationScratch = new Quaternion();
const scaleScratch = new Cartesian3();
function getInstanceTransformsAsMatrices(instances, count, renderResources) {
const transforms = new Array(count);
const translationAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.TRANSLATION
);
const rotationAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.ROTATION
);
const scaleAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.SCALE
);
const instancingTranslationMax = new Cartesian3(
-Number.MAX_VALUE,
-Number.MAX_VALUE,
-Number.MAX_VALUE
);
const instancingTranslationMin = new Cartesian3(
Number.MAX_VALUE,
Number.MAX_VALUE,
Number.MAX_VALUE
);
const hasTranslation = defined(translationAttribute);
const hasRotation = defined(rotationAttribute);
const hasScale = defined(scaleAttribute);
// Translations get initialized to (0, 0, 0).
const translationTypedArray = hasTranslation
? translationAttribute.packedTypedArray
: new Float32Array(count * 3);
// Rotations get initialized to (0, 0, 0, 0). The w-component is set to 1 in the loop below.
const rotationTypedArray = hasRotation
? rotationAttribute.packedTypedArray
: new Float32Array(count * 4);
// Scales get initialized to (1, 1, 1).
let scaleTypedArray;
if (hasScale) {
scaleTypedArray = scaleAttribute.packedTypedArray;
} else {
scaleTypedArray = new Float32Array(count * 3);
scaleTypedArray.fill(1);
}
for (let i = 0; i < count; i++) {
const translation = new Cartesian3(
translationTypedArray[i * 3],
translationTypedArray[i * 3 + 1],
translationTypedArray[i * 3 + 2],
translationScratch
);
Cartesian3.maximumByComponent(
instancingTranslationMax,
translation,
instancingTranslationMax
);
Cartesian3.minimumByComponent(
instancingTranslationMin,
translation,
instancingTranslationMin
);
const rotation = new Quaternion(
rotationTypedArray[i * 4],
rotationTypedArray[i * 4 + 1],
rotationTypedArray[i * 4 + 2],
hasRotation ? rotationTypedArray[i * 4 + 3] : 1,
rotationScratch
);
const scale = new Cartesian3(
scaleTypedArray[i * 3],
scaleTypedArray[i * 3 + 1],
scaleTypedArray[i * 3 + 2],
scaleScratch
);
const transform = Matrix4.fromTranslationQuaternionRotationScale(
translation,
rotation,
scale,
new Matrix4()
);
transforms[i] = transform;
}
renderResources.instancingTranslationMax = instancingTranslationMax;
renderResources.instancingTranslationMin = instancingTranslationMin;
return transforms;
}
function getInstanceTranslationsAsCartesian3s(translationAttribute, count) {
const translations = new Array(count);
const translationTypedArray = translationAttribute.packedTypedArray;
for (let i = 0; i < count; i++) {
translations[i] = new Cartesian3(
translationTypedArray[i * 3],
translationTypedArray[i * 3 + 1],
translationTypedArray[i * 3 + 2]
);
}
return translations;
}
function createVertexBuffer(typedArray, frameState) {
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: typedArray,
usage: BufferUsage.STATIC_DRAW,
});
// Destruction of resources allocated by the ModelExperimental
// is handled by ModelExperimental.destroy().
buffer.vertexArrayDestroyable = false;
return buffer;
}
function processTransformAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes,
use2D
) {
const translationAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.TRANSLATION
);
let translationMax;
let translationMin;
if (defined(translationAttribute)) {
translationMax = translationAttribute.max;
translationMin = translationAttribute.min;
}
const rotationAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.ROTATION
);
const shaderBuilder = renderResources.shaderBuilder;
const count = instances.attributes[0].count;
const useMatrices =
defined(rotationAttribute) ||
!defined(translationMax) ||
!defined(translationMin);
const statistics = renderResources.model.statistics;
// Packed typed arrays are omitted from statistics because they don't
// necessarily correspond to the size of the GPU buffer containing
// their data. It's also difficult to track which typed arrays have
// already been counted.
const hasCpuCopy = false;
let transforms;
if (useMatrices) {
shaderBuilder.addDefine("HAS_INSTANCE_MATRICES");
const attributeString = "Transform";
transforms = getInstanceTransformsAsMatrices(
instances,
count,
renderResources
);
const transformsTypedArray = transformsToTypedArray(transforms);
const buffer = createVertexBuffer(transformsTypedArray, frameState);
renderResources.model._pipelineResources.push(buffer);
processMatrixAttributes(
renderResources,
buffer,
instancingVertexAttributes,
attributeString
);
// Count the buffer here since it had to be allocated
// in this stage.
statistics.addBuffer(buffer, hasCpuCopy);
} else {
if (defined(translationAttribute)) {
shaderBuilder.addDefine("HAS_INSTANCE_TRANSLATION");
const translationMax = translationAttribute.max;
const translationMin = translationAttribute.min;
renderResources.instancingTranslationMax = translationMax;
renderResources.instancingTranslationMin = translationMin;
let buffer = translationAttribute.buffer;
let byteOffset = translationAttribute.byteOffset;
let byteStride = translationAttribute.byteStride;
if (!defined(buffer)) {
buffer = createVertexBuffer(
translationAttribute.packedTypedArray,
frameState
);
renderResources.model._pipelineResources.push(buffer);
byteOffset = 0;
byteStride = undefined;
// Count the buffer here if it had to be allocated
// in this stage. Otherwise, it will be counted in
// NodeStatisticsPipelineStage.
statistics.addBuffer(buffer, hasCpuCopy);
}
const attributeString = "Translation";
processVec3Attribute(
renderResources,
buffer,
byteOffset,
byteStride,
instancingVertexAttributes,
attributeString
);
}
const scaleAttribute = ModelExperimentalUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.SCALE
);
if (defined(scaleAttribute)) {
shaderBuilder.addDefine("HAS_INSTANCE_SCALE");
let buffer = scaleAttribute.buffer;
let byteOffset = scaleAttribute.byteOffset;
let byteStride = scaleAttribute.byteStride;
if (!defined(buffer)) {
buffer = createVertexBuffer(
scaleAttribute.packedTypedArray,
frameState
);
renderResources.model._pipelineResources.push(buffer);
byteOffset = 0;
byteStride = undefined;
// Count the buffer here if it had to be allocated
// in this stage. Otherwise, it will be counted in
// NodeStatisticsPipelineStage.
statistics.addBuffer(buffer, hasCpuCopy);
}
const attributeString = "Scale";
processVec3Attribute(
renderResources,
buffer,
byteOffset,
byteStride,
instancingVertexAttributes,
attributeString
);
}
}
if (!use2D) {
return;
}
// Force the scene mode to be CV. In 2D, projected positions will have
// an x-coordinate of 0, which eliminates the height data that is
// necessary for rendering in CV mode.
const frameStateCV = clone(frameState);
frameStateCV.mode = SceneMode.COLUMBUS_VIEW;
// To prevent jitter, the positions are defined relative to a common
// reference point. For convenience, this is the center of the instanced
// translation bounds projected to 2D.
const referencePoint = computeReferencePoint2D(renderResources, frameStateCV);
renderResources.instancingReferencePoint2D = referencePoint;
const runtimeNode = renderResources.runtimeNode;
if (useMatrices) {
let buffer = runtimeNode.instancingTransformsBuffer2D;
if (!defined(buffer)) {
const projectedTransforms = projectTransformsTo2D(
transforms,
renderResources,
frameStateCV,
transforms
);
const projectedTypedArray = transformsToTypedArray(projectedTransforms);
// This memory is counted during the statistics stage at the end
// of the pipeline.
buffer = createVertexBuffer(projectedTypedArray, frameState);
renderResources.model._modelResources.push(buffer);
runtimeNode.instancingTransformsBuffer2D = buffer;
}
const attributeString2D = "Transform2D";
processMatrixAttributes(
renderResources,
buffer,
instancingVertexAttributes,
attributeString2D
);
} else {
let buffer = runtimeNode.instancingTranslationBuffer2D;
if (!defined(buffer)) {
const translations = getInstanceTranslationsAsCartesian3s(
translationAttribute,
count
);
const projectedTranslations = projectTranslationsTo2D(
translations,
renderResources,
frameStateCV,
translations
);
const projectedTypedArray = translationsToTypedArray(
projectedTranslations
);
// This memory is counted during the statistics stage at the end
// of the pipeline.
buffer = createVertexBuffer(projectedTypedArray, frameState);
renderResources.model._modelResources.push(buffer);
runtimeNode.instancingTranslationBuffer2D = buffer;
}
const byteOffset = 0;
const byteStride = undefined;
const attributeString2D = "Translation2D";
processVec3Attribute(
renderResources,
buffer,
byteOffset,
byteStride,
instancingVertexAttributes,
attributeString2D
);
}
}
function processMatrixAttributes(
renderResources,
buffer,
instancingVertexAttributes,
attributeString
) {
const vertexSizeInFloats = 12;
const componentByteSize = ComponentDatatype.getSizeInBytes(
ComponentDatatype.FLOAT
);
const strideInBytes = componentByteSize * vertexSizeInFloats;
const matrixAttributes = [
{
index: renderResources.attributeIndex++,
vertexBuffer: buffer,
componentsPerAttribute: 4,
componentDatatype: ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: 0,
strideInBytes: strideInBytes,
instanceDivisor: 1,
},
{
index: renderResources.attributeIndex++,
vertexBuffer: buffer,
componentsPerAttribute: 4,
componentDatatype: ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: componentByteSize * 4,
strideInBytes: strideInBytes,
instanceDivisor: 1,
},
{
index: renderResources.attributeIndex++,
vertexBuffer: buffer,
componentsPerAttribute: 4,
componentDatatype: ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: componentByteSize * 8,
strideInBytes: strideInBytes,
instanceDivisor: 1,
},
];
const shaderBuilder = renderResources.shaderBuilder;
shaderBuilder.addAttribute("vec4", `a_instancing${attributeString}Row0`);
shaderBuilder.addAttribute("vec4", `a_instancing${attributeString}Row1`);
shaderBuilder.addAttribute("vec4", `a_instancing${attributeString}Row2`);
instancingVertexAttributes.push.apply(
instancingVertexAttributes,
matrixAttributes
);
}
function processVec3Attribute(
renderResources,
buffer,
byteOffset,
byteStride,
instancingVertexAttributes,
attributeString
) {
instancingVertexAttributes.push({
index: renderResources.attributeIndex++,
vertexBuffer: buffer,
componentsPerAttribute: 3,
componentDatatype: ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: byteOffset,
strideInBytes: byteStride,
instanceDivisor: 1,
});
const shaderBuilder = renderResources.shaderBuilder;
shaderBuilder.addAttribute("vec3", `a_instance${attributeString}`);
}
function processFeatureIdAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes
) {
const attributes = instances.attributes;
const model = renderResources.model;
const shaderBuilder = renderResources.shaderBuilder;
// Load Feature ID vertex attributes. These are loaded as typed arrays in GltfLoader
// because we want to expose the instance feature ID when picking.
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes[i];
if (attribute.semantic !== InstanceAttributeSemantic.FEATURE_ID) {
continue;
}
if (
attribute.setIndex >= renderResources.featureIdVertexAttributeSetIndex
) {
renderResources.featureIdVertexAttributeSetIndex = attribute.setIndex + 1;
}
const vertexBuffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: attribute.packedTypedArray,
usage: BufferUsage.STATIC_DRAW,
});
vertexBuffer.vertexArrayDestroyable = false;
model._pipelineResources.push(vertexBuffer);
// Packed typed arrays are omitted from statistics because they don't
// necessarily correspond to the size of the GPU buffer containing
// their data. It's also difficult to track which typed arrays have
// already been counted.
const hasCpuCopy = false;
model.statistics.addBuffer(vertexBuffer, hasCpuCopy);
instancingVertexAttributes.push({
index: renderResources.attributeIndex++,
vertexBuffer: vertexBuffer,
componentsPerAttribute: AttributeType.getNumberOfComponents(
attribute.type
),
componentDatatype: attribute.componentDatatype,
normalize: false,
offsetInBytes: attribute.byteOffset,
strideInBytes: attribute.byteStride,
instanceDivisor: 1,
});
shaderBuilder.addAttribute(
"float",
`a_instanceFeatureId_${attribute.setIndex}`
);
}
}
// Exposed for testing
InstancingPipelineStage._getInstanceTransformsAsMatrices = getInstanceTransformsAsMatrices;
InstancingPipelineStage._transformsToTypedArray = transformsToTypedArray;
export default InstancingPipelineStage;