@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,057 lines (911 loc) • 29.8 kB
JavaScript
import addAllToArray from "../../Core/addAllToArray.js";
import AttributeCompression from "../../Core/AttributeCompression.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 Matrix4 from "../../Core/Matrix4.js";
import Quaternion from "../../Core/Quaternion.js";
import Transforms from "../../Core/Transforms.js";
import Buffer from "../../Renderer/Buffer.js";
import BufferUsage from "../../Renderer/BufferUsage.js";
import ShaderDestination from "../../Renderer/ShaderDestination.js";
import InstancingStageCommon from "../../Shaders/Model/InstancingStageCommon.js";
import InstancingStageVS from "../../Shaders/Model/InstancingStageVS.js";
import LegacyInstancingStageVS from "../../Shaders/Model/LegacyInstancingStageVS.js";
import AttributeType from "../AttributeType.js";
import InstanceAttributeSemantic from "../InstanceAttributeSemantic.js";
import SceneMode from "../SceneMode.js";
import SceneTransforms from "../SceneTransforms.js";
import ModelUtility from "./ModelUtility.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 = {
name: "InstancingPipelineStage", // Helps with debugging
// Expose some methods for testing
_getInstanceTransformsAsMatrices: getInstanceTransformsAsMatrices,
_transformsToTypedArray: transformsToTypedArray,
};
/**
* 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> sets the 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 runtimeNode = renderResources.runtimeNode;
const use2D =
frameState.mode !== SceneMode.SCENE3D &&
!frameState.scene3DOnly &&
model._projectTo2D;
const keepTypedArray = model._enablePick && !frameState.context.webgl2;
const instancingVertexAttributes = [];
processTransformAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes,
use2D,
keepTypedArray,
);
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
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(
runtimeNode.instancingReferencePoint2D,
new Matrix4(),
);
uniformMap.u_modelView2D = function () {
return Matrix4.multiplyTransformation(
context.uniformState.view,
modelMatrix2D,
modelView2DScratch,
);
};
}
renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap);
renderResources.instanceCount = count;
addAllToArray(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.computeActualEllipsoidPosition(
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 runtimeNode = renderResources.runtimeNode;
const referencePoint = runtimeNode.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 runtimeNode = renderResources.runtimeNode;
const referencePoint = runtimeNode.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 runtimeNode = renderResources.runtimeNode;
const modelMatrix = renderResources.model.sceneGraph.computedModelMatrix;
const transformedPositionMin = Matrix4.multiplyByPoint(
modelMatrix,
runtimeNode.instancingTranslationMin,
scratchProjectedMin,
);
const projectedMin = SceneTransforms.computeActualEllipsoidPosition(
frameState,
transformedPositionMin,
transformedPositionMin,
);
const transformedPositionMax = Matrix4.multiplyByPoint(
modelMatrix,
runtimeNode.instancingTranslationMax,
scratchProjectedMax,
);
const projectedMax = SceneTransforms.computeActualEllipsoidPosition(
frameState,
transformedPositionMax,
transformedPositionMax,
);
runtimeNode.instancingReferencePoint2D = 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 = ModelUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.TRANSLATION,
);
const rotationAttribute = ModelUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.ROTATION,
);
const scaleAttribute = ModelUtility.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.typedArray
: new Float32Array(count * 3);
// Rotations get initialized to (0, 0, 0, 0).
// The w-component is set to 1 in the loop below.
let rotationTypedArray = hasRotation
? rotationAttribute.typedArray
: new Float32Array(count * 4);
// The rotation attribute may be normalized
if (hasRotation && rotationAttribute.normalized) {
rotationTypedArray = AttributeCompression.dequantize(
rotationTypedArray,
rotationAttribute.componentDatatype,
rotationAttribute.type,
count,
);
}
// Scales get initialized to (1, 1, 1).
let scaleTypedArray;
if (hasScale) {
scaleTypedArray = scaleAttribute.typedArray;
} 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;
}
const runtimeNode = renderResources.runtimeNode;
runtimeNode.instancingTranslationMin = instancingTranslationMin;
runtimeNode.instancingTranslationMax = instancingTranslationMax;
// Unload the typed arrays. These are just pointers to the arrays
// in the vertex buffer loader.
if (hasTranslation) {
translationAttribute.typedArray = undefined;
}
if (hasRotation) {
rotationAttribute.typedArray = undefined;
}
if (hasScale) {
scaleAttribute.typedArray = undefined;
}
return transforms;
}
function getInstanceTranslationsAsCartesian3s(
translationAttribute,
count,
renderResources,
) {
const instancingTranslations = new Array(count);
const translationTypedArray = translationAttribute.typedArray;
const instancingTranslationMin = new Cartesian3(
Number.MAX_VALUE,
Number.MAX_VALUE,
Number.MAX_VALUE,
);
const instancingTranslationMax = new Cartesian3(
-Number.MAX_VALUE,
-Number.MAX_VALUE,
-Number.MAX_VALUE,
);
for (let i = 0; i < count; i++) {
const translation = new Cartesian3(
translationTypedArray[i * 3],
translationTypedArray[i * 3 + 1],
translationTypedArray[i * 3 + 2],
);
instancingTranslations[i] = translation;
Cartesian3.minimumByComponent(
instancingTranslationMin,
translation,
instancingTranslationMin,
);
Cartesian3.maximumByComponent(
instancingTranslationMax,
translation,
instancingTranslationMax,
);
}
const runtimeNode = renderResources.runtimeNode;
runtimeNode.instancingTranslationMin = instancingTranslationMin;
runtimeNode.instancingTranslationMax = instancingTranslationMax;
// Unload the typed array. This is just a pointer to the array
// in the vertex buffer loader.
translationAttribute.typedArray = undefined;
return instancingTranslations;
}
function createVertexBuffer(typedArray, frameState) {
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: typedArray,
usage: BufferUsage.STATIC_DRAW,
});
// Destruction of resources allocated by the Model
// is handled by Model.destroy().
buffer.vertexArrayDestroyable = false;
return buffer;
}
function processTransformAttributes(
renderResources,
frameState,
instances,
instancingVertexAttributes,
use2D,
keepTypedArray,
) {
const rotationAttribute = ModelUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.ROTATION,
);
// Only use matrices for the transforms if the rotation attribute is defined.
if (defined(rotationAttribute)) {
processTransformMatrixAttributes(
renderResources,
instances,
instancingVertexAttributes,
frameState,
use2D,
keepTypedArray,
);
} else {
processTransformVec3Attributes(
renderResources,
instances,
instancingVertexAttributes,
frameState,
use2D,
);
}
}
function processTransformMatrixAttributes(
renderResources,
instances,
instancingVertexAttributes,
frameState,
use2D,
keepTypedArray,
) {
const shaderBuilder = renderResources.shaderBuilder;
const count = instances.attributes[0].count;
const model = renderResources.model;
const runtimeNode = renderResources.runtimeNode;
shaderBuilder.addDefine("HAS_INSTANCE_MATRICES");
const attributeString = "Transform";
let transforms;
let buffer = runtimeNode.instancingTransformsBuffer;
if (!defined(buffer)) {
// This function computes the transforms, sets the translation min / max,
// and unloads the typed arrays associated with the attributes.
transforms = getInstanceTransformsAsMatrices(
instances,
count,
renderResources,
);
const transformsTypedArray = transformsToTypedArray(transforms);
buffer = createVertexBuffer(transformsTypedArray, frameState);
model._modelResources.push(buffer);
if (keepTypedArray) {
runtimeNode.transformsTypedArray = transformsTypedArray;
}
runtimeNode.instancingTransformsBuffer = buffer;
}
processMatrixAttributes(
renderResources,
buffer,
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.
computeReferencePoint2D(renderResources, frameStateCV);
let buffer2D = runtimeNode.instancingTransformsBuffer2D;
if (!defined(buffer2D)) {
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.
buffer2D = createVertexBuffer(projectedTypedArray, frameState);
model._modelResources.push(buffer2D);
runtimeNode.instancingTransformsBuffer2D = buffer2D;
}
const attributeString2D = "Transform2D";
processMatrixAttributes(
renderResources,
buffer2D,
instancingVertexAttributes,
attributeString2D,
);
}
function processTransformVec3Attributes(
renderResources,
instances,
instancingVertexAttributes,
frameState,
use2D,
keepTypedArray,
) {
const shaderBuilder = renderResources.shaderBuilder;
const runtimeNode = renderResources.runtimeNode;
const translationAttribute = ModelUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.TRANSLATION,
);
const scaleAttribute = ModelUtility.getAttributeBySemantic(
instances,
InstanceAttributeSemantic.SCALE,
);
if (defined(scaleAttribute)) {
shaderBuilder.addDefine("HAS_INSTANCE_SCALE");
const attributeString = "Scale";
// Instanced scale attributes are loaded as buffers only.
processVec3Attribute(
renderResources,
scaleAttribute.buffer,
scaleAttribute.byteOffset,
scaleAttribute.byteStride,
instancingVertexAttributes,
attributeString,
);
}
if (!defined(translationAttribute)) {
return;
}
let instancingTranslations;
const typedArray = translationAttribute.typedArray;
if (defined(typedArray)) {
// This function computes and set the translation min / max, and unloads
// the typed array associated with the attribute.
// The translations are also returned in case they're used for 2D projection.
instancingTranslations = getInstanceTranslationsAsCartesian3s(
translationAttribute,
translationAttribute.count,
renderResources,
);
} else if (!defined(runtimeNode.instancingTranslationMin)) {
runtimeNode.instancingTranslationMin = translationAttribute.min;
runtimeNode.instancingTranslationMax = translationAttribute.max;
}
shaderBuilder.addDefine("HAS_INSTANCE_TRANSLATION");
const attributeString = "Translation";
processVec3Attribute(
renderResources,
translationAttribute.buffer,
translationAttribute.byteOffset,
translationAttribute.byteStride,
instancingVertexAttributes,
attributeString,
);
if (!use2D && !keepTypedArray) {
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.
computeReferencePoint2D(renderResources, frameStateCV);
let buffer2D = runtimeNode.instancingTranslationBuffer2D;
if (!defined(buffer2D)) {
const projectedTranslations = projectTranslationsTo2D(
instancingTranslations,
renderResources,
frameStateCV,
instancingTranslations,
);
const projectedTypedArray = translationsToTypedArray(projectedTranslations);
if (keepTypedArray) {
runtimeNode.transformsTypedArray = projectedTypedArray;
}
// This memory is counted during the statistics stage at the end
// of the pipeline.
buffer2D = createVertexBuffer(projectedTypedArray, frameState);
renderResources.model._modelResources.push(buffer2D);
runtimeNode.instancingTranslationBuffer2D = buffer2D;
}
if (!use2D) {
return;
}
const byteOffset = 0;
const byteStride = undefined;
const attributeString2D = "Translation2D";
processVec3Attribute(
renderResources,
buffer2D,
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`);
addAllToArray(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 shaderBuilder = renderResources.shaderBuilder;
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;
}
instancingVertexAttributes.push({
index: renderResources.attributeIndex++,
vertexBuffer: attribute.buffer,
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}`,
);
}
}
export default InstancingPipelineStage;