gltfio: various improvements to skinning normalization.

- Allow JavaScript and Java / Kotlin clients to configure this setting.
- Opt in by default (this was disabled in #2621)
- Optimize large assets like Bistro by iterating through prims only when
  the asset actually has skins.

Fixes #2714.
This commit is contained in:
Philip Rideout
2020-06-23 12:01:03 -07:00
parent 455bed917b
commit 8f4ffbc782
5 changed files with 34 additions and 24 deletions

View File

@@ -70,6 +70,9 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener {
val progress
get() = resourceLoader.asyncGetLoadProgress()
var normalizeSkinningWeights = true
var recomputeBoundingBoxes = false
val scene: Scene
val view: View
val camera: Camera
@@ -101,7 +104,7 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener {
view.camera = camera
assetLoader = AssetLoader(engine, MaterialProvider(engine), EntityManager.get())
resourceLoader = ResourceLoader(engine, false, false)
resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes)
// Always add a direct light source since it is required for shadowing.
// We highly recommend adding an indirect light as well.

View File

@@ -296,7 +296,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
}
asset->mResourcesLoaded = true;
mPool->addAsset(asset);
auto gltf = (cgltf_data*) asset->mSourceAsset;
const cgltf_data* gltf = asset->mSourceAsset;
cgltf_options options {};
// For emscripten and Android builds we have a custom implementation of cgltf_load_buffers which
@@ -361,7 +361,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
#else
// Read data from the file system and base64 URIs.
cgltf_result result = cgltf_load_buffers(&options, gltf, pImpl->mGltfPath.c_str());
cgltf_result result = cgltf_load_buffers(&options, (cgltf_data*) gltf, pImpl->mGltfPath.c_str());
if (result != cgltf_result_success) {
slog.e << "Unable to load resources." << io::endl;
return false;
@@ -371,7 +371,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
SYSTRACE_NAME_END();
#ifndef NDEBUG
if (cgltf_validate(gltf) != cgltf_result_success) {
if (cgltf_validate((cgltf_data*) gltf) != cgltf_result_success) {
slog.e << "Failed cgltf validation." << io::endl;
return false;
}
@@ -381,13 +381,19 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
// tangent generation.
decodeDracoMeshes(asset);
// To be robust against the glTF conformance suite, we optionally ensure that skinning weights
// sum to 1.0 at every vertex. Note that if the same weights buffer is shared in multiple
// places, this will needlessly repeat the work. In the future we would like to remove this
// feature, and instead simply require correct models. See also:
// https://github.com/KhronosGroup/glTF-Sample-Models/issues/215
if (pImpl->mNormalizeSkinningWeights) {
normalizeSkinningWeights(asset);
// Normalize skinning weights, then "import" each skin into the asset by building a mapping of
// skins to their affected entities.
if (gltf->skins_count > 0) {
if (pImpl->mNormalizeSkinningWeights) {
normalizeSkinningWeights(asset);
}
if (asset->mInstances.empty()) {
importSkins(gltf, asset->mNodeMap, asset->mSkins);
} else {
for (FFilamentInstance* instance : asset->mInstances) {
importSkins(gltf, instance->nodeMap, instance->skins);
}
}
}
if (pImpl->mRecomputeBoundingBoxes) {
@@ -425,16 +431,6 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
slot.indexBuffer->setBuffer(engine, std::move(bd));
}
// For each skin, build a list of renderables that it affects.
const cgltf_data* const_gltf = gltf;
if (asset->mInstances.empty()) {
importSkins(const_gltf, asset->mNodeMap, asset->mSkins);
} else {
for (FFilamentInstance* instance : asset->mInstances) {
importSkins(const_gltf, instance->nodeMap, instance->skins);
}
}
// Apply sparse data modifications to base arrays, then upload the result.
applySparseData(asset);

View File

@@ -620,6 +620,7 @@ int main(int argc, char** argv) {
configuration.engine = app.engine;
configuration.gltfPath = gltfPath.c_str();
configuration.recomputeBoundingBoxes = app.recomputeAabb;
configuration.normalizeSkinningWeights = true;
if (!app.resourceLoader) {
app.resourceLoader = new gltfio::ResourceLoader(configuration);
}

View File

@@ -396,12 +396,20 @@ Filament.loadClassExtensions = function() {
//
// The optional asyncInterval argument allows clients to control how decoding is amortized
// over time. It represents the number of milliseconds between each texture decoding task.
//
// The optional config argument is an object with boolean fields `normalizeSkinningWeights` and
// `recomputeBoundingBoxes`.
Filament.gltfio$FilamentAsset.prototype.loadResources = function(onDone, onFetched, basePath,
asyncInterval) {
asyncInterval, config) {
const asset = this;
const engine = this.getEngine();
const names = this.getResourceUris();
const interval = asyncInterval || 30;
const defaults = {
normalizeSkinningWeights: true,
recomputeBoundingBoxes: false
};
config = Object.assign(defaults, config || {});
basePath = basePath || document.location;
onFetched = onFetched || ((name) => {});
@@ -420,7 +428,9 @@ Filament.loadClassExtensions = function() {
}
// Construct a resource loader and start decoding after all textures are fetched.
const resourceLoader = new Filament.gltfio$ResourceLoader(engine, false, false);
const resourceLoader = new Filament.gltfio$ResourceLoader(engine,
config.normalizeSkinningWeights,
config.recomputeBoundingBoxes);
const onComplete = () => {
resourceLoader.asyncBeginLoad(asset);

View File

@@ -465,7 +465,7 @@ export class gltfio$AssetLoader {
export class gltfio$FilamentAsset {
public loadResources(onDone: () => void|null, onFetched: (s: string) => void|null,
basePath: string|null, asyncInterval: number|null): void;
basePath: string|null, asyncInterval: number|null, options?: object): void;
public getEntities(): Entity[];
public getEntitiesByName(name: string): Entity[];
public getEntityByName(name: string): Entity;