Skip to content

Commit

Permalink
Optimized culling solution for skinned meshes (#2435)
Browse files Browse the repository at this point in the history
* ModelComponent can specify oversize bounding box and disable expensive skinned bounds update

* es5 compatibility
  • Loading branch information
mvaligursky authored Oct 7, 2020
1 parent f1692a6 commit d369b82
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 14 deletions.
26 changes: 26 additions & 0 deletions src/framework/components/model/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import { Component } from '../component.js';
* @property {boolean} lightmapped If true, this model will be lightmapped after using lightmapper.bake().
* @property {number} lightmapSizeMultiplier Lightmap resolution multiplier.
* @property {boolean} isStatic Mark model as non-movable (optimization).
* @property {pc.BoundingBox} aabb If set, the bounding box is used as a bounding box for visibility culling of attached mesh instances. This is an optimization,
* allowing oversized bounding box to be specified for skinned characters in order to avoid per frame bounding box computations based on bone positions.
* @property {pc.MeshInstance[]} meshInstances An array of meshInstances contained in the component's model. If model is not set or loaded for component it will return null.
* @property {number} batchGroupId Assign model to a specific batch group (see {@link pc.BatchGroup}). Default value is -1 (no group).
* @property {number[]} layers An array of layer IDs ({@link pc.Layer#id}) to which this model should belong.
Expand Down Expand Up @@ -78,6 +80,9 @@ function ModelComponent(system, entity) {
this._layers = [LAYERID_WORLD]; // assign to the default world layer
this._batchGroupId = -1;

// bounding box which can be set to override bounding box based on mesh
this._aabb = null;

this._area = null;

this._assetOld = 0;
Expand Down Expand Up @@ -494,6 +499,26 @@ Object.defineProperty(ModelComponent.prototype, "meshInstances", {
}
});

Object.defineProperties(ModelComponent.prototype, {

'aabb': {
get: function () {
return this._aabb;
},
set: function (value) {
this._aabb = value;

// set it on meshInstances
var mi = this._model.meshInstances;
if (mi) {
for (var i = 0; i < mi.length; i++) {
mi[i].setOverrideAabb(this._aabb);
}
}
}
}
});

Object.defineProperty(ModelComponent.prototype, "type", {
get: function () {
return this._type;
Expand Down Expand Up @@ -683,6 +708,7 @@ Object.defineProperty(ModelComponent.prototype, "model", {
meshInstances[i].castShadow = this._castShadows;
meshInstances[i].receiveShadow = this._receiveShadows;
meshInstances[i].isStatic = this._isStatic;
meshInstances[i].setOverrideAabb(this._aabb);
}

this.lightmapped = this._lightmapped; // update meshInstances
Expand Down
4 changes: 2 additions & 2 deletions src/scene/batching.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ SkinBatchInstance.prototype = Object.create(SkinBatchInstance.prototype);
SkinBatchInstance.prototype.constructor = SkinBatchInstance;

Object.assign(SkinBatchInstance.prototype, {
updateMatrices: function (rootNode) {
updateMatrices: function (rootNode, skinUpdateIndex) {
},

updateMatrixPalette: function () {
updateMatrixPalette: function (rootNode, skinUpdateIndex) {
var pe;
var mp = this.matrixPalette;
var base;
Expand Down
17 changes: 11 additions & 6 deletions src/scene/forward-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ var skipRenderCamera = null;
var _skipRenderCounter = 0;
var skipRenderAfter = 0;

var _skinUpdateIndex = 0;

// The 8 points of the camera frustum transformed to light space
var frustumPoints = [];
for (var fp = 0; fp < 8; fp++) {
Expand Down Expand Up @@ -1180,19 +1182,22 @@ Object.assign(ForwardRenderer.prototype, {
},

updateCpuSkinMatrices: function (drawCalls) {

_skinUpdateIndex++;

var drawCallsCount = drawCalls.length;
if (drawCallsCount === 0) return;

// #ifdef PROFILER
var skinTime = now();
// #endif

var i, skin;
var i, si;
for (i = 0; i < drawCallsCount; i++) {
skin = drawCalls[i].skinInstance;
if (skin) {
skin.updateMatrices(drawCalls[i].node);
skin._dirty = true;
si = drawCalls[i].skinInstance;
if (si) {
si.updateMatrices(drawCalls[i].node, _skinUpdateIndex);
si._dirty = true;
}
}

Expand All @@ -1213,7 +1218,7 @@ Object.assign(ForwardRenderer.prototype, {
skin = drawCalls[i].skinInstance;
if (skin) {
if (skin._dirty) {
skin.updateMatrixPalette();
skin.updateMatrixPalette(drawCalls[i].node, _skinUpdateIndex);
skin._dirty = false;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/scene/mesh-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ Object.defineProperty(MeshInstance.prototype, 'skinInstance', {
for (var i = 0; i < this._shader.length; i++) {
this._shader[i] = null;
}

this._setupSkinUpdate();
}
});

Expand Down Expand Up @@ -512,6 +514,23 @@ Object.assign(MeshInstance.prototype, {
parameter.scopeId.setValue(parameter.data);
}
}
},

setOverrideAabb: function (aabb) {
this._updateAabb = !aabb;
if (aabb) {
this.aabb.copy(aabb);
}

this._setupSkinUpdate();
},

_setupSkinUpdate: function () {

// set if bones need to be updated before culling
if (this._skinInstance) {
this._skinInstance._updateBeforeCull = this._updateAabb;
}
}
});

Expand Down
35 changes: 29 additions & 6 deletions src/scene/skin-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ var _invMatrix = new Mat4();
function SkinInstance(skin) {
this._dirty = true;

// sequencial index of when the bone update was performed the last time
this._skinUpdateIndex = -1;

// true if bones need to be updated before the frustum culling (bones are needed to update bounds of the MeshInstance)
this._updateBeforeCull = true;

if (skin) {
this.initSkin(skin);
}
Expand Down Expand Up @@ -77,16 +83,33 @@ Object.assign(SkinInstance.prototype, {
}
},

updateMatrices: function (rootNode) {
_updateMatrices: function (rootNode, skinUpdateIndex) {

// if not already up to date
if (this._skinUpdateIndex !== skinUpdateIndex) {
this._skinUpdateIndex = skinUpdateIndex;

_invMatrix.copy(rootNode.getWorldTransform()).invert();
for (var i = this.bones.length - 1; i >= 0; i--) {
this.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform()); // world space -> rootNode space
this.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]); // rootNode space -> bind space
_invMatrix.copy(rootNode.getWorldTransform()).invert();
for (var i = this.bones.length - 1; i >= 0; i--) {
this.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform()); // world space -> rootNode space
this.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]); // rootNode space -> bind space
}
}
},

updateMatrixPalette: function () {
updateMatrices: function (rootNode, skinUpdateIndex) {

if (this._updateBeforeCull) {
this._updateMatrices(rootNode, skinUpdateIndex);
}
},

updateMatrixPalette: function (rootNode, skinUpdateIndex) {

// make sure matrices are up to date
this._updateMatrices(rootNode, skinUpdateIndex);

// copy matrices to palette
var pe;
var mp = this.matrixPalette;
var base;
Expand Down

0 comments on commit d369b82

Please sign in to comment.