gltfio: add UbershaderLoader, an implemention of MaterialProvider.
This commit is contained in:
@@ -22,7 +22,9 @@ set(SRCS
|
||||
src/FilamentAsset.cpp
|
||||
src/GltfEnums.h
|
||||
src/MaterialGenerator.cpp
|
||||
src/MaterialProvider.cpp
|
||||
src/ResourceLoader.cpp
|
||||
src/UbershaderLoader.cpp
|
||||
src/Wireframe.cpp
|
||||
src/Wireframe.h
|
||||
src/math.h
|
||||
|
||||
@@ -26,6 +26,21 @@
|
||||
|
||||
namespace gltfio {
|
||||
|
||||
/**
|
||||
* AssetLoader can be configured to generate materials using filamat, or to use a small set of
|
||||
* prebuilt ubershader materials.
|
||||
*/
|
||||
enum MaterialSource {
|
||||
GENERATE_SHADERS,
|
||||
LOAD_UBERSHADERS,
|
||||
};
|
||||
|
||||
struct AssetConfiguration {
|
||||
class filament::Engine* engine;
|
||||
utils::NameComponentManager* names = nullptr;
|
||||
MaterialSource materials = GENERATE_SHADERS;
|
||||
};
|
||||
|
||||
/**
|
||||
* AssetLoader consumes a blob of glTF 2.0 content (either JSON or GLB) and produces an "asset",
|
||||
* which is a bundle of Filament entities, material instances, textures, vertex buffers, and index
|
||||
@@ -37,15 +52,11 @@ namespace gltfio {
|
||||
*
|
||||
* AssetLoader also owns a cache of Material objects that may be re-used across multiple loads.
|
||||
*
|
||||
* TODO: currently this uses filamat to generate materials on the fly, but we may wish to allow
|
||||
* clients to load a small set of precompiled ubershader materials to avoid the footprint of the
|
||||
* filamat library on resource-constrained platforms.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* auto engine = Engine::create();
|
||||
* auto loader = AssetLoader::create(engine);
|
||||
* auto loader = AssetLoader::create({engine});
|
||||
*
|
||||
* // Parse the glTF content and create Filament entities.
|
||||
* std::vector<uint8_t> content(...);
|
||||
@@ -84,12 +95,14 @@ class AssetLoader {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Creates an asset loader for the given engine.
|
||||
* Creates an asset loader for the given configuration, which specifies the Filament engine.
|
||||
*
|
||||
* The engine is held weakly, used only for the creation and destruction of Filament objects.
|
||||
* The optional name component manager can be used to assign names to renderables.
|
||||
* The material source specifies whether to use filamat to generate materials on the fly, or to
|
||||
* load a small set of precompiled ubershader materials.
|
||||
*/
|
||||
static AssetLoader* create(filament::Engine* engine, utils::NameComponentManager* = nullptr);
|
||||
static AssetLoader* create(const AssetConfiguration& config);
|
||||
|
||||
/**
|
||||
* Frees the loader.
|
||||
|
||||
@@ -135,6 +135,7 @@ struct BufferBinding {
|
||||
|
||||
bool convertBytesToShorts; // the resource loader must convert the buffer from u8 to u16
|
||||
bool generateTrivialIndices; // the resource loader must generate indices like: 0, 1, 2, ...
|
||||
bool generateDummyData; // the resource loader should generate a sequence of 1.0 values
|
||||
};
|
||||
|
||||
/** Describes a binding from a Texture to a MaterialInstance. */
|
||||
|
||||
@@ -56,24 +56,7 @@ struct alignas(4) MaterialKey {
|
||||
|
||||
static_assert(sizeof(MaterialKey) == 12, "MaterialKey has unexpected padding.");
|
||||
|
||||
inline bool operator==(const MaterialKey& k1, const MaterialKey& k2) {
|
||||
return
|
||||
(k1.doubleSided == k2.doubleSided) &&
|
||||
(k1.unlit == k2.unlit) &&
|
||||
(k1.hasVertexColors == k2.hasVertexColors) &&
|
||||
(k1.hasBaseColorTexture == k2.hasBaseColorTexture) &&
|
||||
(k1.hasMetallicRoughnessTexture == k2.hasMetallicRoughnessTexture) &&
|
||||
(k1.hasNormalTexture == k2.hasNormalTexture) &&
|
||||
(k1.hasOcclusionTexture == k2.hasOcclusionTexture) &&
|
||||
(k1.hasEmissiveTexture == k2.hasEmissiveTexture) &&
|
||||
(k1.alphaMode == k2.alphaMode) &&
|
||||
(k1.baseColorUV == k2.baseColorUV) &&
|
||||
(k1.metallicRoughnessUV == k2.metallicRoughnessUV) &&
|
||||
(k1.emissiveUV == k2.emissiveUV) &&
|
||||
(k1.aoUV == k2.aoUV) &&
|
||||
(k1.normalUV == k2.normalUV) &&
|
||||
(k1.alphaMaskThreshold == k2.alphaMaskThreshold);
|
||||
}
|
||||
bool operator==(const MaterialKey& k1, const MaterialKey& k2);
|
||||
|
||||
// Define a mapping from a uv set index in the source asset to one of Filament's uv sets.
|
||||
enum UvSet : uint8_t { UNUSED, UV0, UV1 };
|
||||
@@ -85,17 +68,13 @@ using UvMap = std::array<UvSet, 8>;
|
||||
* - The "MaterialGenerator" implementation generates materials at run time (which can be slow)
|
||||
* and requires the filamat library, but produces streamlined shaders.
|
||||
*
|
||||
* - The "Ubermaterial" implementation uses a small number of pre-built materials with complex
|
||||
* - The "UbershaderLoader" implementation uses a small number of pre-built materials with complex
|
||||
* fragment shaders, but does not require any run time work or usage of filamat.
|
||||
*/
|
||||
class MaterialProvider {
|
||||
public:
|
||||
static MaterialProvider* createMaterialGenerator(filament::Engine* engine);
|
||||
|
||||
static MaterialProvider* createUbermaterial(filament::Engine* engine) {
|
||||
/* TODO: ubermaterial not yet implemented */
|
||||
return nullptr;
|
||||
}
|
||||
static MaterialProvider* createUbershaderLoader(filament::Engine* engine);
|
||||
|
||||
virtual ~MaterialProvider() {}
|
||||
|
||||
@@ -111,6 +90,12 @@ public:
|
||||
virtual void destroyMaterials() = 0;
|
||||
};
|
||||
|
||||
namespace details {
|
||||
void constrainMaterial(MaterialKey* key, UvMap* uvmap);
|
||||
void processShaderString(std::string* shader, const UvMap& uvmap,
|
||||
const MaterialKey& config);
|
||||
}
|
||||
|
||||
} // namespace gltfio
|
||||
|
||||
#endif // GLTFIO_MATERIALPROVIDER_H
|
||||
|
||||
@@ -3,6 +3,9 @@ material {
|
||||
requires : [ uv0, uv1, color ],
|
||||
shadingModel : ${SHADINGMODEL},
|
||||
blending : ${BLENDING},
|
||||
depthWrite : true,
|
||||
doubleSided : true,
|
||||
flipUV : false,
|
||||
parameters : [
|
||||
// Base Color
|
||||
{ type : int, name : baseColorIndex },
|
||||
|
||||
@@ -98,14 +98,23 @@ static uint32_t computeBindingOffset(const cgltf_accessor* accessor) {
|
||||
return uint32_t(accessor->offset + accessor->buffer_view->offset);
|
||||
};
|
||||
|
||||
static MaterialProvider* createMaterialProvider(const AssetConfiguration& config) {
|
||||
if (config.materials == GENERATE_SHADERS) {
|
||||
return MaterialProvider::createMaterialGenerator(config.engine);
|
||||
} else {
|
||||
return MaterialProvider::createUbershaderLoader(config.engine);
|
||||
}
|
||||
}
|
||||
|
||||
struct FAssetLoader : public AssetLoader {
|
||||
FAssetLoader(Engine* engine, NameComponentManager* names) :
|
||||
FAssetLoader(const AssetConfiguration& config) :
|
||||
mEntityManager(EntityManager::get()),
|
||||
mRenderableManager(engine->getRenderableManager()),
|
||||
mNameManager(names),
|
||||
mTransformManager(engine->getTransformManager()),
|
||||
mMaterials(MaterialProvider::createMaterialGenerator(engine)),
|
||||
mEngine(engine) {}
|
||||
mRenderableManager(config.engine->getRenderableManager()),
|
||||
mNameManager(config.names),
|
||||
mTransformManager(config.engine->getTransformManager()),
|
||||
mMaterials(createMaterialProvider(config)),
|
||||
mEngine(config.engine),
|
||||
mMaterialSource(config.materials) {}
|
||||
|
||||
FFilamentAsset* createAssetFromJson(const uint8_t* bytes, uint32_t nbytes);
|
||||
FilamentAsset* createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes);
|
||||
@@ -147,6 +156,7 @@ struct FAssetLoader : public AssetLoader {
|
||||
TransformManager& mTransformManager;
|
||||
MaterialProvider* mMaterials;
|
||||
Engine* mEngine;
|
||||
MaterialSource mMaterialSource;
|
||||
|
||||
// The loader owns a few transient mappings used only for the current asset being loaded.
|
||||
FFilamentAsset* mResult;
|
||||
@@ -406,14 +416,12 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
}
|
||||
mResult->mIndexBuffers.push_back(indices);
|
||||
|
||||
// We do not necessarily upload all glTF attribute buffers to the GPU. For example, we do not
|
||||
// upload tangent vectors in their source format or more than two UV sets. However the buffer
|
||||
// count that gets passed to the Builder should be equal to the glTF attribute count because we
|
||||
// do not remap the slots.
|
||||
VertexBuffer::Builder vbb;
|
||||
vbb.bufferCount(inPrim->attributes_count);
|
||||
|
||||
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
|
||||
int slot = 0;
|
||||
bool hasUv0 = false, hasUv1 = false, hasVertexColor = false;
|
||||
uint32_t vertexCount = 0;
|
||||
for (; slot < inPrim->attributes_count; slot++) {
|
||||
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
|
||||
const cgltf_accessor* inputAccessor = inputAttribute.data;
|
||||
|
||||
@@ -430,6 +438,10 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inputAttribute.type == cgltf_attribute_type_color) {
|
||||
hasVertexColor = true;
|
||||
}
|
||||
|
||||
// Translate the cgltf attribute enum into a Filament enum and ignore all uv sets
|
||||
// that do not have entries in the mapping table.
|
||||
VertexAttribute semantic;
|
||||
@@ -442,9 +454,11 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
switch (uvset) {
|
||||
case UV0:
|
||||
semantic = VertexAttribute::UV0;
|
||||
hasUv0 = true;
|
||||
break;
|
||||
case UV1:
|
||||
semantic = VertexAttribute::UV1;
|
||||
hasUv1 = true;
|
||||
break;
|
||||
case UNUSED:
|
||||
// It is perfectly acceptable to drop unused texture coordinate sets. In fact
|
||||
@@ -453,8 +467,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
}
|
||||
}
|
||||
|
||||
// This will needlessly set the same vertex count multiple times, which should be fine.
|
||||
vbb.vertexCount(inputAccessor->count);
|
||||
vertexCount = inputAccessor->count;
|
||||
|
||||
// The positions accessor is required to have min/max properties, use them to expand
|
||||
// the bounding box for this primitive.
|
||||
@@ -486,6 +499,34 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
}
|
||||
}
|
||||
|
||||
vbb.vertexCount(vertexCount);
|
||||
|
||||
// If an ubershader is used, then we provide a single dummy buffer for all unfulfilled vertex
|
||||
// requirements. The color data should be a sequence of normalized UBYTE4, so dummy UVs are
|
||||
// USHORT2 to make the sizes match.
|
||||
int dummySlot = -1;
|
||||
if (mMaterialSource == LOAD_UBERSHADERS) {
|
||||
bool needsDummyData = false;
|
||||
if (!hasUv0) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2);
|
||||
}
|
||||
if (!hasUv1) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2);
|
||||
}
|
||||
if (!hasVertexColor) {
|
||||
needsDummyData = true;
|
||||
vbb.attribute(VertexAttribute::COLOR, slot, VertexBuffer::AttributeType::UBYTE4);
|
||||
vbb.normalized(VertexAttribute::COLOR);
|
||||
}
|
||||
if (needsDummyData) {
|
||||
dummySlot = slot++;
|
||||
}
|
||||
}
|
||||
|
||||
vbb.bufferCount(slot);
|
||||
|
||||
VertexBuffer* vertices = mResult->mPrimMap[inPrim] = vbb.build(*mEngine);
|
||||
mResult->mVertexBuffers.push_back(vertices);
|
||||
|
||||
@@ -501,7 +542,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
uvmap[inputAttribute.index] == UNUSED) {
|
||||
continue;
|
||||
}
|
||||
mResult->mBufferBindings.emplace_back(BufferBinding {
|
||||
mResult->mBufferBindings.push_back({
|
||||
.uri = bv->buffer->uri,
|
||||
.totalSize = uint32_t(bv->buffer->size),
|
||||
.bufferIndex = uint8_t(slot),
|
||||
@@ -511,7 +552,24 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
|
||||
.vertexBuffer = vertices,
|
||||
.indexBuffer = nullptr,
|
||||
.convertBytesToShorts = false,
|
||||
.generateTrivialIndices = false
|
||||
.generateTrivialIndices = false,
|
||||
.generateDummyData = false
|
||||
});
|
||||
}
|
||||
|
||||
if (dummySlot > 0) {
|
||||
mResult->mBufferBindings.push_back({
|
||||
.uri = "",
|
||||
.totalSize = uint32_t(sizeof(ubyte4) * vertexCount),
|
||||
.bufferIndex = uint8_t(dummySlot),
|
||||
.offset = 0,
|
||||
.size = uint32_t(sizeof(ubyte4) * vertexCount),
|
||||
.data = nullptr,
|
||||
.vertexBuffer = vertices,
|
||||
.indexBuffer = nullptr,
|
||||
.convertBytesToShorts = false,
|
||||
.generateTrivialIndices = false,
|
||||
.generateDummyData = true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -711,8 +769,8 @@ bool FAssetLoader::primitiveHasVertexColor(const cgltf_primitive* inPrim) const
|
||||
return false;
|
||||
}
|
||||
|
||||
AssetLoader* AssetLoader::create(Engine* engine, NameComponentManager* names) {
|
||||
return new FAssetLoader(engine, names);
|
||||
AssetLoader* AssetLoader::create(const AssetConfiguration& config) {
|
||||
return new FAssetLoader(config);
|
||||
}
|
||||
|
||||
void AssetLoader::destroy(AssetLoader** loader) {
|
||||
|
||||
@@ -74,11 +74,11 @@ void MaterialGenerator::destroyMaterials() {
|
||||
mCache.clear();
|
||||
}
|
||||
|
||||
static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap) {
|
||||
static std::string shaderFromKey(const MaterialKey& config) {
|
||||
std::string shader = "void material(inout MaterialInputs material) {\n";
|
||||
|
||||
if (config.hasNormalTexture && !config.unlit) {
|
||||
shader += "float2 normalUV = getUV${normal}();\n";
|
||||
shader += "float2 normalUV = ${normal};\n";
|
||||
if (config.hasTextureTransforms) {
|
||||
shader += "normalUV = (vec3(normalUV, 1.0) * materialParams.normalUvMatrix).xy;\n";
|
||||
}
|
||||
@@ -95,7 +95,7 @@ static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap)
|
||||
)SHADER";
|
||||
|
||||
if (config.hasBaseColorTexture) {
|
||||
shader += "float2 baseColorUV = getUV${color}();\n";
|
||||
shader += "float2 baseColorUV = ${color};\n";
|
||||
if (config.hasTextureTransforms) {
|
||||
shader += "baseColorUV = (vec3(baseColorUV, 1.0) * "
|
||||
"materialParams.baseColorUvMatrix).xy;\n";
|
||||
@@ -122,7 +122,7 @@ static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap)
|
||||
material.emissive.rgb = materialParams.emissiveFactor.rgb;
|
||||
)SHADER";
|
||||
if (config.hasMetallicRoughnessTexture) {
|
||||
shader += "float2 metallicRoughnessUV = getUV${metallic}();\n";
|
||||
shader += "float2 metallicRoughnessUV = ${metallic};\n";
|
||||
if (config.hasTextureTransforms) {
|
||||
shader += "metallicRoughnessUV = (vec3(metallicRoughnessUV, 1.0) * "
|
||||
"materialParams.metallicRoughnessUvMatrix).xy;\n";
|
||||
@@ -134,7 +134,7 @@ static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap)
|
||||
)SHADER";
|
||||
}
|
||||
if (config.hasOcclusionTexture) {
|
||||
shader += "float2 aoUV = getUV${ao}();\n";
|
||||
shader += "float2 aoUV = ${ao};\n";
|
||||
if (config.hasTextureTransforms) {
|
||||
shader += "aoUV = (vec3(aoUV, 1.0) * materialParams.occlusionUvMatrix).xy;\n";
|
||||
}
|
||||
@@ -144,7 +144,7 @@ static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap)
|
||||
)SHADER";
|
||||
}
|
||||
if (config.hasEmissiveTexture) {
|
||||
shader += "float2 emissiveUV = getUV${emissive}();\n";
|
||||
shader += "float2 emissiveUV = ${emissive};\n";
|
||||
if (config.hasTextureTransforms) {
|
||||
shader += "emissiveUV = (vec3(emissiveUV, 1.0) * "
|
||||
"materialParams.emissiveUvMatrix).xy;\n";
|
||||
@@ -157,71 +157,14 @@ static std::string shaderFromKey(const MaterialKey& config, const UvMap& uvmap)
|
||||
}
|
||||
|
||||
shader += "}\n";
|
||||
|
||||
auto replaceAll = [&shader](const std::string& from, const std::string& to) {
|
||||
size_t pos = shader.find(from);
|
||||
for (; pos != std::string::npos; pos = shader.find(from, pos)) {
|
||||
shader.replace(pos, from.length(), to);
|
||||
}
|
||||
};
|
||||
|
||||
const auto normalUV = std::to_string(uvmap[config.normalUV] - 1);
|
||||
const auto baseColorUV = std::to_string(uvmap[config.baseColorUV] - 1);
|
||||
const auto metallicRoughnessUV = std::to_string(uvmap[config.metallicRoughnessUV] - 1);
|
||||
const auto emissiveUV = std::to_string(uvmap[config.emissiveUV] - 1);
|
||||
const auto aoUV = std::to_string(uvmap[config.aoUV] - 1);
|
||||
|
||||
replaceAll("${normal}", normalUV);
|
||||
replaceAll("${color}", baseColorUV);
|
||||
replaceAll("${metallic}", metallicRoughnessUV);
|
||||
replaceAll("${ao}", aoUV);
|
||||
replaceAll("${emissive}", emissiveUV);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
// Filament supports up to 2 UV sets. glTF has arbitrary texcoord set indices, but it allows
|
||||
// implementations to support only 2 simultaneous sets. Here we build a mapping table with 1-based
|
||||
// indices where 0 means unused. Note that the order in which we drop textures can affect the look
|
||||
// of certain assets. This "order of degradation" is stipulated by the glTF 2.0 specification.
|
||||
static void constrainMaterial(MaterialKey* key, UvMap* uvmap) {
|
||||
const int MAX_INDEX = 2;
|
||||
UvMap retval {};
|
||||
int index = 1;
|
||||
if (key->hasBaseColorTexture) {
|
||||
retval[key->baseColorUV] = (UvSet) index++;
|
||||
}
|
||||
if (key->hasMetallicRoughnessTexture && retval[key->metallicRoughnessUV] == UNUSED) {
|
||||
retval[key->metallicRoughnessUV] = (UvSet) index++;
|
||||
}
|
||||
if (key->hasNormalTexture && retval[key->normalUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasNormalTexture = false;
|
||||
} else {
|
||||
retval[key->normalUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
if (key->hasOcclusionTexture && retval[key->aoUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasOcclusionTexture = false;
|
||||
} else {
|
||||
retval[key->aoUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
if (key->hasEmissiveTexture && retval[key->emissiveUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasEmissiveTexture = false;
|
||||
} else {
|
||||
retval[key->emissiveUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
*uvmap = retval;
|
||||
}
|
||||
|
||||
static Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& uvmap,
|
||||
const char* name) {
|
||||
using CullingMode = MaterialBuilder::CullingMode;
|
||||
std::string shader = shaderFromKey(config, uvmap);
|
||||
std::string shader = shaderFromKey(config);
|
||||
gltfio::details::processShaderString(&shader, uvmap, config);
|
||||
MaterialBuilder builder = MaterialBuilder()
|
||||
.name(name)
|
||||
.flipUV(false)
|
||||
@@ -312,7 +255,7 @@ static Material* createMaterial(Engine* engine, const MaterialKey& config, const
|
||||
|
||||
MaterialInstance* MaterialGenerator::createMaterialInstance(MaterialKey* config, UvMap* uvmap,
|
||||
const char* label) {
|
||||
constrainMaterial(config, uvmap);
|
||||
gltfio::details::constrainMaterial(config, uvmap);
|
||||
auto iter = mCache.find(*config);
|
||||
if (iter == mCache.end()) {
|
||||
Material* mat = createMaterial(mEngine, *config, *uvmap, label);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gltfio/MaterialProvider.h>
|
||||
|
||||
using namespace gltfio;
|
||||
|
||||
bool gltfio::operator==(const MaterialKey& k1, const MaterialKey& k2) {
|
||||
return
|
||||
(k1.doubleSided == k2.doubleSided) &&
|
||||
(k1.unlit == k2.unlit) &&
|
||||
(k1.hasVertexColors == k2.hasVertexColors) &&
|
||||
(k1.hasBaseColorTexture == k2.hasBaseColorTexture) &&
|
||||
(k1.hasMetallicRoughnessTexture == k2.hasMetallicRoughnessTexture) &&
|
||||
(k1.hasNormalTexture == k2.hasNormalTexture) &&
|
||||
(k1.hasOcclusionTexture == k2.hasOcclusionTexture) &&
|
||||
(k1.hasEmissiveTexture == k2.hasEmissiveTexture) &&
|
||||
(k1.alphaMode == k2.alphaMode) &&
|
||||
(k1.baseColorUV == k2.baseColorUV) &&
|
||||
(k1.metallicRoughnessUV == k2.metallicRoughnessUV) &&
|
||||
(k1.emissiveUV == k2.emissiveUV) &&
|
||||
(k1.aoUV == k2.aoUV) &&
|
||||
(k1.normalUV == k2.normalUV) &&
|
||||
(k1.alphaMaskThreshold == k2.alphaMaskThreshold);
|
||||
}
|
||||
|
||||
// Filament supports up to 2 UV sets. glTF has arbitrary texcoord set indices, but it allows
|
||||
// implementations to support only 2 simultaneous sets. Here we build a mapping table with 1-based
|
||||
// indices where 0 means unused. Note that the order in which we drop textures can affect the look
|
||||
// of certain assets. This "order of degradation" is stipulated by the glTF 2.0 specification.
|
||||
void details::constrainMaterial(MaterialKey* key, UvMap* uvmap) {
|
||||
const int MAX_INDEX = 2;
|
||||
UvMap retval {};
|
||||
int index = 1;
|
||||
if (key->hasBaseColorTexture) {
|
||||
retval[key->baseColorUV] = (UvSet) index++;
|
||||
}
|
||||
if (key->hasMetallicRoughnessTexture && retval[key->metallicRoughnessUV] == UNUSED) {
|
||||
retval[key->metallicRoughnessUV] = (UvSet) index++;
|
||||
}
|
||||
if (key->hasNormalTexture && retval[key->normalUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasNormalTexture = false;
|
||||
} else {
|
||||
retval[key->normalUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
if (key->hasOcclusionTexture && retval[key->aoUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasOcclusionTexture = false;
|
||||
} else {
|
||||
retval[key->aoUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
if (key->hasEmissiveTexture && retval[key->emissiveUV] == UNUSED) {
|
||||
if (index > MAX_INDEX) {
|
||||
key->hasEmissiveTexture = false;
|
||||
} else {
|
||||
retval[key->emissiveUV] = (UvSet) index++;
|
||||
}
|
||||
}
|
||||
*uvmap = retval;
|
||||
}
|
||||
|
||||
void details::processShaderString(std::string* shader, const UvMap& uvmap,
|
||||
const MaterialKey& config) {
|
||||
auto replaceAll = [shader](const std::string& from, const std::string& to) {
|
||||
size_t pos = shader->find(from);
|
||||
for (; pos != std::string::npos; pos = shader->find(from, pos)) {
|
||||
shader->replace(pos, from.length(), to);
|
||||
}
|
||||
};
|
||||
static const std::string uvstrings[] = { "vec2(0)", "getUV0()", "getUV1()" };
|
||||
const auto& normalUV = uvstrings[uvmap[config.normalUV]];
|
||||
const auto& baseColorUV = uvstrings[uvmap[config.baseColorUV]];
|
||||
const auto& metallicRoughnessUV = uvstrings[uvmap[config.metallicRoughnessUV]];
|
||||
const auto& emissiveUV = uvstrings[uvmap[config.emissiveUV]];
|
||||
const auto& aoUV = uvstrings[uvmap[config.aoUV]];
|
||||
replaceAll("${normal}", normalUV);
|
||||
replaceAll("${color}", baseColorUV);
|
||||
replaceAll("${metallic}", metallicRoughnessUV);
|
||||
replaceAll("${ao}", aoUV);
|
||||
replaceAll("${emissive}", emissiveUV);
|
||||
}
|
||||
@@ -149,11 +149,17 @@ bool ResourceLoader::loadResources(FilamentAsset* asset) {
|
||||
const BufferBinding* bindings = asset->getBufferBindings();
|
||||
for (size_t i = 0, n = asset->getBufferBindingCount(); i < n; ++i) {
|
||||
auto bb = bindings[i];
|
||||
const uint8_t* data8 = bb.offset + (const uint8_t*) *bb.data;
|
||||
if (bb.vertexBuffer) {
|
||||
if (bb.vertexBuffer && !bb.generateDummyData) {
|
||||
const uint8_t* data8 = bb.offset + (const uint8_t*) *bb.data;
|
||||
mPool->addPendingUpload();
|
||||
VertexBuffer::BufferDescriptor bd(data8, bb.size, AssetPool::onLoadedResource, mPool);
|
||||
bb.vertexBuffer->setBufferAt(*mConfig.engine, bb.bufferIndex, std::move(bd));
|
||||
} else if (bb.vertexBuffer) {
|
||||
uint32_t* dummyData = (uint32_t*) malloc(bb.size);
|
||||
memset(dummyData, 0xff, bb.size);
|
||||
auto callback = (VertexBuffer::BufferDescriptor::Callback) free;
|
||||
VertexBuffer::BufferDescriptor bd(dummyData, bb.size, callback);
|
||||
bb.vertexBuffer->setBufferAt(*mConfig.engine, bb.bufferIndex, std::move(bd));
|
||||
} else if (bb.generateTrivialIndices) {
|
||||
uint32_t* data32 = (uint32_t*) malloc(bb.size);
|
||||
generateTrivialIndices(data32, bb.size / sizeof(uint32_t));
|
||||
@@ -161,6 +167,7 @@ bool ResourceLoader::loadResources(FilamentAsset* asset) {
|
||||
IndexBuffer::BufferDescriptor bd(data32, bb.size, callback);
|
||||
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
|
||||
} else if (bb.convertBytesToShorts) {
|
||||
const uint8_t* data8 = bb.offset + (const uint8_t*) *bb.data;
|
||||
size_t size16 = bb.size * 2;
|
||||
uint16_t* data16 = (uint16_t*) malloc(size16);
|
||||
convertBytesToShorts(data16, data8, bb.size);
|
||||
@@ -168,6 +175,7 @@ bool ResourceLoader::loadResources(FilamentAsset* asset) {
|
||||
IndexBuffer::BufferDescriptor bd(data16, size16, callback);
|
||||
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
|
||||
} else if (bb.indexBuffer) {
|
||||
const uint8_t* data8 = bb.offset + (const uint8_t*) *bb.data;
|
||||
mPool->addPendingUpload();
|
||||
IndexBuffer::BufferDescriptor bd(data8, bb.size, AssetPool::onLoadedResource, mPool);
|
||||
bb.indexBuffer->setBuffer(*mConfig.engine, std::move(bd));
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gltfio/MaterialProvider.h>
|
||||
|
||||
#include <filament/MaterialInstance.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
|
||||
#include "gltfresources.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::math;
|
||||
using namespace gltfio;
|
||||
using namespace utils;
|
||||
|
||||
namespace {
|
||||
|
||||
class UbershaderLoader : public MaterialProvider {
|
||||
public:
|
||||
UbershaderLoader(filament::Engine* engine);
|
||||
~UbershaderLoader() {}
|
||||
|
||||
filament::MaterialInstance* createMaterialInstance(MaterialKey* config, UvMap* uvmap,
|
||||
const char* label) override;
|
||||
|
||||
size_t getMaterialsCount() const noexcept override;
|
||||
const filament::Material* const* getMaterials() const noexcept override;
|
||||
void destroyMaterials() override;
|
||||
|
||||
Material* getMaterial(const MaterialKey& config) const;
|
||||
|
||||
Material* mMaterials[6];
|
||||
Material* mLitOpaqueMaterial;
|
||||
Material* mLitMaskedMaterial;
|
||||
Material* mLitTransparentMaterial;
|
||||
Material* mUnlitOpaqueMaterial;
|
||||
Material* mUnlitMaskedMaterial;
|
||||
Material* mUnlitTransparentMaterial;
|
||||
|
||||
filament::Engine* mEngine;
|
||||
};
|
||||
|
||||
UbershaderLoader::UbershaderLoader(Engine* engine) : mEngine(engine) {
|
||||
int i = 0;
|
||||
mMaterials[i++] = mLitOpaqueMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_LIT_OPAQUE_DATA, GLTFRESOURCES_LIT_OPAQUE_SIZE).build(*engine);
|
||||
mMaterials[i++] = mLitMaskedMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_LIT_MASKED_DATA, GLTFRESOURCES_LIT_MASKED_SIZE).build(*engine);
|
||||
mMaterials[i++] = mLitTransparentMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_LIT_TRANSPARENT_DATA, GLTFRESOURCES_LIT_TRANSPARENT_SIZE)
|
||||
.build(*engine);
|
||||
mMaterials[i++] = mUnlitOpaqueMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_UNLIT_OPAQUE_DATA, GLTFRESOURCES_UNLIT_OPAQUE_SIZE).build(*engine);
|
||||
mMaterials[i++] = mUnlitMaskedMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_UNLIT_MASKED_DATA, GLTFRESOURCES_UNLIT_MASKED_SIZE).build(*engine);
|
||||
mMaterials[i++] = mUnlitTransparentMaterial = Material::Builder()
|
||||
.package(GLTFRESOURCES_UNLIT_TRANSPARENT_DATA, GLTFRESOURCES_UNLIT_TRANSPARENT_SIZE)
|
||||
.build(*engine);
|
||||
}
|
||||
|
||||
size_t UbershaderLoader::getMaterialsCount() const noexcept {
|
||||
return sizeof(mMaterials) / sizeof(mMaterials[0]);
|
||||
}
|
||||
|
||||
const Material* const* UbershaderLoader::getMaterials() const noexcept {
|
||||
return &mMaterials[0];
|
||||
}
|
||||
|
||||
void UbershaderLoader::destroyMaterials() {
|
||||
for (auto& material : mMaterials) {
|
||||
mEngine->destroy(material);
|
||||
material = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Material* UbershaderLoader::getMaterial(const MaterialKey& config) const {
|
||||
switch(config.alphaMode) {
|
||||
case AlphaMode::OPAQUE:
|
||||
return config.unlit ? mUnlitOpaqueMaterial : mLitOpaqueMaterial;
|
||||
case AlphaMode::MASK:
|
||||
return config.unlit ? mUnlitMaskedMaterial : mLitMaskedMaterial;
|
||||
case AlphaMode::BLEND:
|
||||
return config.unlit ? mUnlitTransparentMaterial : mLitTransparentMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialInstance* UbershaderLoader::createMaterialInstance(MaterialKey* config, UvMap* uvmap,
|
||||
const char* label) {
|
||||
gltfio::details::constrainMaterial(config, uvmap);
|
||||
auto getUvIndex = [uvmap](uint8_t srcIndex, bool hasTexture) -> int {
|
||||
return hasTexture ? int(uvmap->at(srcIndex)) - 1 : -1;
|
||||
};
|
||||
Material* material = getMaterial(*config);
|
||||
MaterialInstance* mi = material->createInstance();
|
||||
mi->setParameter("baseColorIndex",
|
||||
getUvIndex(config->baseColorUV, config->hasBaseColorTexture));
|
||||
mi->setParameter("normalIndex", getUvIndex(config->normalUV, config->hasNormalTexture));
|
||||
mi->setParameter("metallicRoughnessIndex",
|
||||
getUvIndex(config->metallicRoughnessUV, config->hasMetallicRoughnessTexture));
|
||||
mi->setParameter("aoIndex", getUvIndex(config->aoUV, config->hasOcclusionTexture));
|
||||
mi->setParameter("emissiveIndex", getUvIndex(config->emissiveUV, config->hasEmissiveTexture));
|
||||
|
||||
if (config->alphaMode == AlphaMode::MASK) {
|
||||
mi->setParameter("maskThreshold", config->alphaMaskThreshold);
|
||||
}
|
||||
|
||||
// TODO: honor config->doubleSided
|
||||
|
||||
mat3f identity;
|
||||
mi->setParameter("baseColorUvMatrix", identity);
|
||||
mi->setParameter("metallicRoughnessUvMatrix", identity);
|
||||
mi->setParameter("normalUvMatrix", identity);
|
||||
mi->setParameter("occlusionUvMatrix", identity);
|
||||
mi->setParameter("emissiveUvMatrix", identity);
|
||||
return mi;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace gltfio {
|
||||
|
||||
MaterialProvider* MaterialProvider::createUbershaderLoader(filament::Engine* engine) {
|
||||
return new UbershaderLoader(engine);
|
||||
}
|
||||
|
||||
} // namespace gltfio
|
||||
Reference in New Issue
Block a user