载入中...
搜索中...
未找到
GltfMaterials.cpp
浏览该文件的文档.
2#include <algorithm>
3#include <cmath>
4#include <limits>
8namespace {
9struct Rejection {
10 std::string message;
11};
12void require(bool value, const char* message) {
13 if (!value) throw Rejection{message};
14}
15const Value::Object& object(const Value& value) {
16 auto* o = value.getIf<Value::Object>();
17 require(o, "material field must be an object");
18 return *o;
19}
20const Value::Array& array(const Value* value) {
21 auto* a = value ? value->getIf<Value::Array>() : nullptr;
22 require(a, "material collection must be an array");
23 return *a;
24}
25size_t index(const Value* value, size_t count) {
26 require(value && value->isInt64() && value->asInt() >= 0 && uint64_t(value->asInt()) < count,
27 "material reference index out of range");
28 return size_t(value->asInt());
29}
30struct Factors {
31 const GltfImportRequest& request;
32 PreparedAssetImport& output;
33 std::string path;
34 double adjust(double value, double replacement, std::string_view field) {
36 throw Rejection{path + "." + std::string(field) + ": material factor out of range"};
37 output.findings.push_back(
38 {request.sourceName, path + "." + std::string(field), ImportDisposition::Baked,
39 "warning: material factor " + std::to_string(value) + " clamped to " + std::to_string(replacement),
41 return replacement;
42 }
43 double scalar(const Value* value, double fallback, double minimum = 0, double maximum = 1,
44 std::string_view field = "factor") {
45 if (!value) return fallback;
46 require(value->isNumeric(), "material factor must be numeric");
47 const double n = value->isInt64() ? double(value->asInt()) : value->asDouble();
48 require(std::isfinite(n), "material factor must be finite");
49 return n < minimum || n > maximum ? adjust(n, std::clamp(n, minimum, maximum), field) : n;
50 }
51 Value vector(const Value* value, std::initializer_list<double> fallback, double minimum = 0, double maximum = 1,
52 std::string_view field = "vector") {
53 if (!value) {
55 for (auto n : fallback) a.emplace_back(n);
56 return Value(std::move(a));
57 }
58 const auto& a = array(value);
59 require(a.size() == fallback.size(), "invalid material factor vector");
60 Value::Array out;
61 for (size_t i = 0; i < a.size(); ++i)
62 out.emplace_back(scalar(&a[i], 0, minimum, maximum, std::string(field) + "[" + std::to_string(i) + "]"));
63 return Value(std::move(out));
64 }
65};
67 auto r = detail::assetRef(id);
68 require(r.ok(), "invalid material identity");
69 return std::move(r).takeValue();
70}
71void budget(const GltfImportRequest& request, const PreparedAssetImport& out) {
72 require(out.manifest.assets.size() <= request.limits.maximumAssets, "material asset budget exceeded");
73 uint64_t size = 0;
74 for (const auto& e : out.entries) {
75 require(e.bytes.size() <= request.limits.maximumDecodedBytes - size, "material byte budget exceeded");
76 size += e.bytes.size();
77 }
78}
79void link(PreparedAssetImport& out, const AssetRef& from, const AssetRef& to, std::string role, std::string schema) {
80 out.manifest.dependencies.push_back(
81 {from, to, asset::EvaDependencyKind::RuntimeRequired, std::move(role), {}, std::move(schema)});
82}
83void append(const GltfImportRequest& request, const Value::Object& root,
84 const std::vector<std::span<const uint8_t>>& buffers, PreparedAssetImport& out) {
85 const auto* materialsValue = member(root, "materials");
86 if (!materialsValue) {
87 const auto& meshes = array(member(root, "meshes"));
88 for (const auto& mesh : meshes)
89 for (const auto& primitive : array(member(object(mesh), "primitives")))
90 require(!member(object(primitive), "material"), "primitive material collection is missing");
91 return;
92 }
93 const auto& materials = array(materialsValue);
94 std::map<std::string, AssetRef> images;
95 for (size_t m = 0; m < materials.size(); ++m) {
96 const auto& source = object(materials[m]);
97 Factors factors{request, out, "materials[" + std::to_string(m) + "]"};
98
99 const Value::Object empty;
100 const auto* pbrValue = member(source, "pbrMetallicRoughness");
101 const auto& pbr = pbrValue ? object(*pbrValue) : empty;
102 auto ref = reference(request.package.packageId.child("gltf:material:" + std::to_string(m)));
103 std::string mode = "OPAQUE";
104 if (auto* v = member(source, "alphaMode")) {
105 require(v->isString(), "alphaMode must be a string");
106 mode = v->asString();
107 }
108 require(mode == "OPAQUE" || mode == "MASK" || mode == "BLEND", "unknown alphaMode");
109 Value::Object definition{{"schema", Value("eve.material")},
110 {"schemaVersion", Value(int64_t(15))},
111 {"alphaToCoverage", Value(false)},
112 {"shadingModel", Value("pbr")},
113 {"surfaceMode", Value(mode == "OPAQUE" ? "opaque"
114 : mode == "MASK" ? "masked"
115 : "transparent")},
116 {"baseColor", factors.vector(member(pbr, "baseColorFactor"), {1, 1, 1, 1})},
117 {"metallic", Value(factors.scalar(member(pbr, "metallicFactor"), 1))},
118 {"roughness", Value(factors.scalar(member(pbr, "roughnessFactor"), 1))},
119 {"emissive", factors.vector(member(source, "emissiveFactor"), {0, 0, 0})},
120 {"alphaCutoff", Value(factors.scalar(member(source, "alphaCutoff"), 0.5))}};
121 if (auto* v = member(source, "doubleSided")) {
122 require(v->isBool(), "doubleSided must be boolean");
123 definition["doubleSided"] = *v;
124 } else
125 definition["doubleSided"] = Value(false);
126 if (mode == "BLEND") definition["blendMode"] = Value("alpha");
127 if (auto* name = member(source, "name")) {
128 require(name->isString() && name->asString().size() <= request.limits.maximumStringBytes,
129 "material name exceeds budget");
130 definition["name"] = *name;
131 }
132 Value::Object textureInfos;
133 for (const auto* role :
134 {"baseColorTexture", "metallicRoughnessTexture", "normalTexture", "occlusionTexture", "emissiveTexture"}) {
135 const auto* v = member(
136 std::string_view(role) == "baseColorTexture" || std::string_view(role) == "metallicRoughnessTexture"
137 ? pbr
138 : source,
139 role);
140 if (v) textureInfos[role] = *v;
141 }
142 const double unbounded = std::numeric_limits<double>::max();
143 if (const auto* extensions = member(source, "extensions")) {
144 const auto& materialExtensions = object(*extensions);
145 require(!materialExtensions.contains("KHR_materials_unlit") || materialExtensions.size() == 1,
146 "unlit cannot be combined with PBR material extensions");
147 for (const auto& [name, value] : materialExtensions) {
148 require(supportsMaterialExtension(name) && name != "KHR_texture_transform",
149 "unknown material extension");
150 const auto& ext = object(value);
151 auto factor = [&](const char* key, double fallback, double minimum = 0, double maximum = 1) {
152 definition[key] = Value(factors.scalar(member(ext, key), fallback, minimum, maximum, key));
153 };
154 auto texture = [&](const char* key) {
155 if (auto* v = member(ext, key)) textureInfos[key] = *v;
156 };
157 if (name == "KHR_materials_specular") {
158 factor("specularFactor", 1);
159 definition["specularColorFactor"] =
160 factors.vector(member(ext, "specularColorFactor"), {1, 1, 1}, 0, unbounded);
161 texture("specularTexture");
162 texture("specularColorTexture");
163 } else if (name == "KHR_materials_anisotropy") {
164 factor("anisotropyStrength", 0);
165 factor("anisotropyRotation", 0, -unbounded, unbounded);
166 texture("anisotropyTexture");
167 } else if (name == "KHR_materials_clearcoat") {
168 factor("clearcoatFactor", 0);
169 factor("clearcoatRoughnessFactor", 0);
170 texture("clearcoatTexture");
171 texture("clearcoatRoughnessTexture");
172 texture("clearcoatNormalTexture");
173 } else if (name == "KHR_materials_emissive_strength") {
174 factor("emissiveStrength", 1, 0, unbounded);
175 } else if (name == "KHR_materials_ior") {
176 double ior = factors.scalar(member(ext, "ior"), 1.5, 0, unbounded, "ior");
177 if (ior > 0 && ior < 1) ior = factors.adjust(ior, 1, "ior");
178 definition["ior"] = Value(ior);
179 } else if (name == "KHR_materials_unlit")
180 definition["shadingModel"] = Value("unlit");
181 }
182 }
183 for (const auto& [role, textureInfo] : textureInfos) {
184 const bool color =
185 role == "baseColorTexture" || role == "emissiveTexture" || role == "specularColorTexture";
186 const auto& info = object(textureInfo);
187 const Value* effectiveUv = member(info, "texCoord");
188 if (effectiveUv) require(effectiveUv->isInt64() && effectiveUv->asInt() >= 0, "invalid texCoord");
189 if (const auto* extensions = member(info, "extensions")) {
190 for (const auto& [name, value] : object(*extensions)) {
191 require(name == "KHR_texture_transform", "unknown texture-info extension");
192 const auto& transform = object(value);
193 if (auto* uv = member(transform, "texCoord")) effectiveUv = uv;
194 definition[role + "Transform"] = Value(Value::Object{
195 {"offset", factors.vector(member(transform, "offset"), {0, 0}, -unbounded, unbounded)},
196 {"scale", factors.vector(member(transform, "scale"), {1, 1}, -unbounded, unbounded)},
197 {"rotation", Value(factors.scalar(member(transform, "rotation"), 0, -unbounded, unbounded))},
198 {"texCoord", effectiveUv ? *effectiveUv : Value(int64_t(0))}});
199 }
200 }
201 if (effectiveUv)
202 require(effectiveUv->isInt64() && effectiveUv->asInt() >= 0 &&
203 uint64_t(effectiveUv->asInt()) <= std::numeric_limits<uint32_t>::max(),
204 "invalid effective UV set");
205 const auto uvSet = effectiveUv ? uint32_t(effectiveUv->asInt()) : 0u;
206 if (!definition.contains(role + "Transform")) definition[role + "TexCoord"] = Value(int64_t(uvSet));
207 for (const auto& mesh : array(member(root, "meshes"))) {
208 for (const auto& primitive : array(member(object(mesh), "primitives"))) {
209 const auto& p = object(primitive);
210 const auto* materialIndex = member(p, "material");
211 if (!materialIndex || index(materialIndex, materials.size()) != m) continue;
212 const auto* attributes = member(p, "attributes");
213 require(attributes && object(*attributes).contains("TEXCOORD_" + std::to_string(uvSet)),
214 "texture UV set is absent from a bound primitive");
215 }
216 }
217 const auto& textures = array(member(root, "textures"));
218 const auto& texture = object(textures[index(member(info, "index"), textures.size())]);
219 require(!member(texture, "extensions"), "texture extensions require explicit translation");
220 const auto& sourceImages = array(member(root, "images"));
221 auto imageIndex = index(member(texture, "source"), sourceImages.size());
222 const auto key = std::to_string(imageIndex) + ":" + role;
223 auto found = images.find(key);
224 if (found == images.end()) {
225 const auto& image = object(sourceImages[imageIndex]);
226 std::vector<uint8_t> bytes;
227 if (auto* uri = member(image, "uri")) {
228 require(uri->isString(), "image URI must be a string");
229 auto resource = request.externalResources.find(uri->asString());
230 require(resource != request.externalResources.end(), "image URI was not supplied explicitly");
231 bytes = resource->second;
232 } else {
233 const auto& views = array(member(root, "bufferViews"));
234 const auto& view = object(views[index(member(image, "bufferView"), views.size())]);
235 const auto b = index(member(view, "buffer"), buffers.size());
236 auto offset = unsignedValue(member(view, "byteOffset"), "image offset", false, 0);
237 auto length = unsignedValue(member(view, "byteLength"), "image length");
238 require(offset.ok() && length.ok(), "invalid image buffer range");
239 require(offset.value() <= buffers[b].size() && length.value() <= buffers[b].size() - offset.value(),
240 "image buffer range out of bounds");
241 auto span = buffers[b].subspan(size_t(offset.value()), size_t(length.value()));
242 bytes.assign(span.begin(), span.end());
243 }
244 auto identity = request.package;
245 identity.packageId = identity.packageId.child("gltf:image:" + key);
246 auto imported =
247 prepareImageImport({identity, "images[" + std::to_string(imageIndex) + "]", std::move(bytes),
249 if (!imported) throw Rejection{imported.error()->message()};
250 auto imageRef = imported.value().manifest.assets.front().asset;
251 for (auto& a : imported.value().manifest.assets) out.manifest.assets.push_back(std::move(a));
252 // Each sub-import report belongs to that sub-import only, not the aggregate archive.
253 for (auto& e : imported.value().entries)
254 if (e.path.starts_with("assets/")) out.entries.push_back(std::move(e));
255 found = images.emplace(key, imageRef).first;
256 budget(request, out);
257 }
258 definition[role] = Value(found->second.format());
259 link(out, ref, found->second, role, "eve.image/3");
260 Value::Object sampler{{"wrapS", Value(int64_t(10497))}, {"wrapT", Value(int64_t(10497))}};
261 if (auto* id = member(texture, "sampler")) {
262 const auto& samplers = array(member(root, "samplers"));
263 const auto& supplied = object(samplers[index(id, samplers.size())]);
264 for (const auto& [name, value] : supplied) {
265 require(value.isInt64(), "sampler fields must be integer enums");
266 auto n = value.asInt();
267 require((name == "wrapS" || name == "wrapT") ? (n == 33071 || n == 33648 || n == 10497)
268 : name == "magFilter" ? (n == 9728 || n == 9729)
269 : name == "minFilter" ? (n == 9728 || n == 9729 || (n >= 9984 && n <= 9987))
270 : false,
271 "unsupported sampler field");
272 sampler[name] = value;
273 }
274 }
275 definition[std::string(role) + "Sampler"] = Value(std::move(sampler));
276 if (role == "normalTexture" || role == "clearcoatNormalTexture")
277 definition[role == "normalTexture" ? "normalScale" : "clearcoatNormalScale"] =
278 Value(factors.scalar(member(info, "scale"), 1, -1e6, 1e6));
279 if (std::string_view(role) == "occlusionTexture")
280 definition["occlusionStrength"] = Value(factors.scalar(member(info, "strength"), 1));
281 }
282 const auto path = "assets/" + ref.id().format() + "/asset.json";
283 auto json = Value(std::move(definition)).toJson();
284 require(json.ok(), "cannot encode material");
285 std::vector<uint8_t> bytes(json.value().begin(), json.value().end());
286 out.manifest.assets.push_back(
287 {ref, "eve.material", SchemaVersion(15), path, detail::sha256(bytes), {"source:gltf2"}});
288 out.entries.push_back({path, std::move(bytes)});
289 out.manifest.entrypoints.emplace("material:" + std::to_string(m), ref);
290 out.findings.push_back({request.sourceName, "materials[" + std::to_string(m) + "]",
292 "PBR material, image semantics and sampler bindings retained"});
293 budget(request, out);
294 }
295 const auto& meshes = array(member(root, "meshes"));
296 for (size_t m = 0; m < meshes.size(); ++m) {
297 const auto& primitives = array(member(object(meshes[m]), "primitives"));
298 for (size_t p = 0; p < primitives.size(); ++p) {
299 const auto* value = member(object(primitives[p]), "material");
300 if (!value) continue;
301 auto material = reference(
302 request.package.packageId.child("gltf:material:" + std::to_string(index(value, materials.size()))));
303 auto mesh = reference(
304 request.package.packageId.child("gltf:mesh:" + std::to_string(m) + ":primitive:" + std::to_string(p)));
305 link(out, mesh, material, "material", "eve.material/15");
306 }
307 }
308}
309} // namespace
310bool supportsMaterialExtension(std::string_view name) {
311 return name == "KHR_materials_specular" || name == "KHR_materials_anisotropy" ||
312 name == "KHR_materials_clearcoat" || name == "KHR_materials_emissive_strength" ||
313 name == "KHR_materials_ior" || name == "KHR_materials_unlit" || name == "KHR_texture_transform";
314}
316 const std::vector<std::span<const uint8_t>>& buffers, PreparedAssetImport& output) {
317 try {
318 append(request, root, buffers, output);
319 return Result<void>::success();
320 } catch (const Rejection& error) {
322 Diagnostic::error(DiagnosticCode::Unsupported, error.message, {}, {}, "asset.import"));
323 }
324}
325} // namespace eve::asset_import::gltf
double value
int root
Definition AnimSmr.cpp:119
std::string output
std::string from
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
std::unordered_map< std::string, QuestRuntime > entries
float uv
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
std::string message
float maximum[3]
float minimum[3]
const GltfImportRequest & request
std::uint32_t key
vk::UniqueSampler sampler
vk::UniqueImage image
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
double r
float v
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
size_t offset
std::string imported
std::uint64_t bytes
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
graphics::PbrSurface pbr
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::string uri
Mesh * mesh
glm::mat4 view
Material * material
bool found
std::string resource
std::uint32_t count
Json object
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
uint32_t index
const UnitySourceAsset & source
float m[16]
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
Result< AssetRef > assetRef(PersistentId id)
Asset ref.
bool supportsMaterialExtension(std::string_view name)
Whether this adapter translates the named standard material/texture extension.
Result< std::uint64_t > unsignedValue(const Value *value, std::string path, bool required, std::uint64_t fallback)
Validate an optional integer field. @ownership Borrowed nullable field owned by the input document....
Result< void > appendMaterials(const GltfImportRequest &request, const Value::Object &root, const std::vector< std::span< const uint8_t > > &buffers, PreparedAssetImport &output)
EVENGINE_API_PLATFORM Result< PreparedAssetImport > prepareImageImport(const ImageImportRequest &request)
Decode image metadata and prepare one canonical eve.image source asset.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Untrusted .gltf/.glb input plus explicitly supplied external URI bytes.
Owning, validated source archive candidate not yet published to disk/database.
glm::uvec4 info
glm::vec4 color