Skip to content

Commit

Permalink
fix(3dtiles): fix and document 3d tiles material overriding
Browse files Browse the repository at this point in the history
  • Loading branch information
jailln committed Nov 9, 2022
1 parent 04f8b40 commit 8ade709
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 12 deletions.
13 changes: 11 additions & 2 deletions src/Layer/C3DTilesLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ class C3DTilesLayer extends GeometryLayer {
* @param {C3TilesSource} config.source The source of 3d Tiles.
*
* name.
* @param {Number} [config.sseThreshold=16] The [Screen Space Error](https://github.com/CesiumGS/3d-tiles/blob/main/specification/README.md#geometric-error)
* threshold at which child nodes of the current node will be loaded and added to the scene.
* @param {Number} [config.cleanupDelay=1000] The time (in ms) after which a tile content (and its children) are
* removed from the scene.
* @param {Function} [config.onTileContentLoaded] Callback executed when the content of a tile is loaded.
* @param {Boolean|Material} [config.overrideMaterials='false'] option to override the materials of all the layer's
* objects. If true, a threejs [MeshBasicMaterial](https://threejs.org/docs/index.html?q=meshbasic#api/en/materials/MeshBasicMaterial)
* is set up. config.overrideMaterials can also be a threejs [Material](https://threejs.org/docs/index.html?q=material#api/en/materials/Material)
* in which case it will be used as the material for all objects of the layer.
* @param {C3DTExtensions} [config.registeredExtensions] 3D Tiles extensions managers registered for this tileset.
* @param {View} view The view
*/
constructor(id, config, view) {
Expand All @@ -40,8 +50,7 @@ class C3DTilesLayer extends GeometryLayer {
this.cleanupDelay = config.cleanupDelay || 1000;
this.onTileContentLoaded = config.onTileContentLoaded || (() => {});
this.protocol = '3d-tiles';
// custom cesium shaders are not functional;
this.overrideMaterials = config.overrideMaterials !== undefined ? config.overrideMaterials : true;
this.overrideMaterials = config.overrideMaterials ?? false;
this.name = config.name;
this.registeredExtensions = config.registeredExtensions || new C3DTExtensions();

Expand Down
21 changes: 11 additions & 10 deletions src/Parser/B3dmParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import shaderUtils from 'Renderer/Shader/ShaderUtils';
import utf8Decoder from 'Utils/Utf8Decoder';
import C3DTBatchTable from 'Core/3DTiles/C3DTBatchTable';
import ReferLayerProperties from 'Layer/ReferencingLayerProperties';
import { MeshBasicMaterial } from 'three';
import disposeThreeMaterial from 'Utils/ThreeUtils';

const matrixChangeUpVectorZtoY = (new THREE.Matrix4()).makeRotationX(Math.PI / 2);
// For gltf rotation
Expand Down Expand Up @@ -78,7 +80,10 @@ export default {
* @param {string} options.urlBase - the base url of the b3dm file (used to fetch textures for the embedded glTF model).
* @param {boolean=} [options.doNotPatchMaterial='false'] - disable patching material with logarithmic depth buffer support.
* @param {float} [options.opacity=1.0] - the b3dm opacity.
* @param {boolean|Material=} [options.overrideMaterials='false'] - override b3dm's embedded glTF materials. If overrideMaterials is a three.js material, it will be the material used to override.
* @param {boolean|Material=} [options.overrideMaterials='false'] - override b3dm's embedded glTF materials. If
* true, a threejs [MeshBasicMaterial](https://threejs.org/docs/index.html?q=meshbasic#api/en/materials/MeshBasicMaterial)
* is set up. config.overrideMaterials can also be a threejs [Material](https://threejs.org/docs/index.html?q=material#api/en/materials/Material)
* in which case it will be the material used to override.
* @return {Promise} - a promise that resolves with an object containig a THREE.Scene (gltf) and a batch table (batchTable).
*
*/
Expand Down Expand Up @@ -184,19 +189,15 @@ export default {
mesh.frustumCulled = false;
if (mesh.material) {
if (options.overrideMaterials) {
if (Array.isArray(mesh.material)) {
for (const material of mesh.material) {
material.dispose();
}
} else {
mesh.material.dispose();
}
const oldMat = mesh.material;
// Set up new material
if (typeof (options.overrideMaterials) === 'object' &&
options.overrideMaterials.isMaterial) {
options.overrideMaterials.isMaterial) {
mesh.material = options.overrideMaterials;
} else {
mesh.material.depthWrite = true;
mesh.material = new MeshBasicMaterial();
}
disposeThreeMaterial(oldMat);
} else if (Capabilities.isLogDepthBufferSupported()
&& mesh.material.isRawShaderMaterial
&& !options.doNotPatchMaterial) {
Expand Down
67 changes: 67 additions & 0 deletions src/Utils/ThreeUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Find threejs textures from a material
* @param {Material} material the threejs material holding textures
* @returns {Array} an array of textures in the material
*/
function findTextures(material) {
const textures = [];
if (material.alphaMap) {
textures.push(material.map);
}
if (material.aoMap) {
textures.push(material.map);
}
if (material.bumpMap) {
textures.push(material.bumpMap);
}
if (material.displacementMap) {
textures.push(material.bumpMap);
}
if (material.emissiveMap) {
textures.push(material.emissiveMap);
}
if (material.envMap) {
textures.push(material.envMap);
}
if (material.lightMap) {
textures.push(material.envMap);
}
if (material.map) {
textures.push(material.map);
}
if (material.metalnessMap) {
textures.push(material.map);
}
if (material.normalMap) {
textures.push(material.map);
}
if (material.roughnessMap) {
textures.push(material.map);
}
if (material.specularMap) {
textures.push(material.specularMap);
}
return textures;
}

/**
* Removes a material and its textures, memory will be freed.
* IMPORTANT NOTE: the material and the texture must not be referenced by other threejs objects, otherwise the memory
* won't be freed.
* @param {Material} material the material to remove
*/
export default function disposeThreeMaterial(material) {
const textures = findTextures(material);
// Remove material
if (Array.isArray(material)) {
for (const m of material) {
m.dispose();
}
} else {
material.dispose();
}
// Remove textures
for (let i = 0; i < textures.length; i++) {
textures[i].dispose();
}
}
62 changes: 62 additions & 0 deletions test/unit/threeutils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import assert from 'assert';
import * as THREE from 'three';
import disposeThreeMaterial from 'Utils/ThreeUtils';

describe('ThreeJS Utils', function () {
it('Should dispose material and textures', function () {
// Testing if material and textures are correctly disposed is not easy since dispose dispatches an event to
// indicate that some WebGLRenderer GPU resources should be remove. A way to test that is to check the
// WebGLRenderer.info value which lists used GPU resources but WebGLRenderer is not available for unit tests.
// Another way, that is implemented here, is to create a material and a all possible textures and to count the
// number of dispatched 'dispose' events
var disposeCounter = {
nb: 0,
};
const increment = function () {
this.nb++;
};

const material = new THREE.MeshStandardMaterial();
material.addEventListener('dispose', increment.bind(disposeCounter));

material.alphaMap = new THREE.Texture();
material.alphaMap.addEventListener('dispose', increment.bind(disposeCounter));

material.aoMap = new THREE.Texture();
material.aoMap.addEventListener('dispose', increment.bind(disposeCounter));

material.bumpMap = new THREE.Texture();
material.bumpMap.addEventListener('dispose', increment.bind(disposeCounter));

material.displacementMap = new THREE.Texture();
material.displacementMap.addEventListener('dispose', increment.bind(disposeCounter));

material.emissiveMap = new THREE.Texture();
material.emissiveMap.addEventListener('dispose', increment.bind(disposeCounter));

material.envMap = new THREE.Texture();
material.envMap.addEventListener('dispose', increment.bind(disposeCounter));

material.lightMap = new THREE.Texture();
material.lightMap.addEventListener('dispose', increment.bind(disposeCounter));

material.map = new THREE.Texture();
material.map.addEventListener('dispose', increment.bind(disposeCounter));

material.metalnessMap = new THREE.Texture();
material.metalnessMap.addEventListener('dispose', increment.bind(disposeCounter));

material.normalMap = new THREE.Texture();
material.normalMap.addEventListener('dispose', increment.bind(disposeCounter));

material.roughnessMap = new THREE.Texture();
material.roughnessMap.addEventListener('dispose', increment.bind(disposeCounter));

material.specularMap = new THREE.Texture();
material.specularMap.addEventListener('dispose', increment.bind(disposeCounter));

disposeThreeMaterial(material);

assert.equal(disposeCounter.nb, 13);
});
});

0 comments on commit 8ade709

Please sign in to comment.