Fix Nahida toon binding and test assets

This commit is contained in:
2026-04-13 21:09:40 +08:00
parent e462f7d6f7
commit 48daaa1bd0
9 changed files with 531 additions and 686 deletions

View File

@@ -50,7 +50,7 @@ constexpr uint32_t kFrameHeight = 720;
constexpr int kWarmupFrames = 45;
enum class DiagnosticMode {
Original,
Toon,
NoShadows,
ForwardLit,
Unlit
@@ -59,10 +59,13 @@ enum class DiagnosticMode {
DiagnosticMode GetDiagnosticMode() {
const char* value = std::getenv("XC_NAHIDA_DIAG_MODE");
if (value == nullptr) {
return DiagnosticMode::Original;
return DiagnosticMode::Toon;
}
const std::string mode(value);
if (mode == "toon" || mode == "original") {
return DiagnosticMode::Toon;
}
if (mode == "no_shadows") {
return DiagnosticMode::NoShadows;
}
@@ -73,12 +76,12 @@ DiagnosticMode GetDiagnosticMode() {
return DiagnosticMode::Unlit;
}
return DiagnosticMode::Original;
return DiagnosticMode::Toon;
}
const char* GetDiagnosticModeName(DiagnosticMode mode) {
switch (mode) {
case DiagnosticMode::Original: return "original";
case DiagnosticMode::Toon: return "toon";
case DiagnosticMode::NoShadows: return "no_shadows";
case DiagnosticMode::ForwardLit: return "forward_lit";
case DiagnosticMode::Unlit: return "unlit";
@@ -88,7 +91,7 @@ const char* GetDiagnosticModeName(DiagnosticMode mode) {
const char* GetGoldenFileName(DiagnosticMode mode) {
switch (mode) {
case DiagnosticMode::Original: return "GT.ppm";
case DiagnosticMode::Toon: return "GT.ppm";
case DiagnosticMode::NoShadows: return "GT.no_shadows.ppm";
case DiagnosticMode::ForwardLit: return "GT.forward_lit.ppm";
case DiagnosticMode::Unlit: return "GT.unlit.ppm";
@@ -96,6 +99,278 @@ const char* GetGoldenFileName(DiagnosticMode mode) {
}
}
constexpr const char* kToonShaderPath = "Assets/Shaders/Toon.shader";
constexpr const char* kNahidaTextureRoot = "Assets/Models/nahida/";
XCEngine::Containers::String RemapMaterialPropertyName(
const Material& targetMaterial,
const XCEngine::Containers::String& sourceName) {
if (targetMaterial.HasProperty(sourceName)) {
return sourceName;
}
if (sourceName == XCEngine::Containers::String("_BaseMap") && targetMaterial.HasProperty("_MainTex")) {
return XCEngine::Containers::String("_MainTex");
}
if (sourceName == XCEngine::Containers::String("_MainTex") && targetMaterial.HasProperty("_BaseMap")) {
return XCEngine::Containers::String("_BaseMap");
}
if (sourceName == XCEngine::Containers::String("_Color") && targetMaterial.HasProperty("_BaseColor")) {
return XCEngine::Containers::String("_BaseColor");
}
if (sourceName == XCEngine::Containers::String("_BaseColor") && targetMaterial.HasProperty("_Color")) {
return XCEngine::Containers::String("_Color");
}
return XCEngine::Containers::String();
}
bool IsNahidaCharacterRenderable(const GameObject* gameObject) {
if (gameObject == nullptr) {
return false;
}
const std::string& name = gameObject->GetName();
return name.rfind("Body_Mesh", 0) == 0 ||
name == "Brow" ||
name == "EyeStar" ||
name == "Face" ||
name == "Face_Eye";
}
std::string BuildNahidaTextureAssetPath(const char* fileName) {
return std::string(kNahidaTextureRoot) + fileName;
}
void SetMaterialFloatIfPresent(Material* material, const char* name, float value) {
if (material == nullptr || name == nullptr || !material->HasProperty(name)) {
return;
}
material->SetFloat(name, value);
}
void SetMaterialFloat4IfPresent(Material* material, const char* name, const XCEngine::Math::Vector4& value) {
if (material == nullptr || name == nullptr || !material->HasProperty(name)) {
return;
}
material->SetFloat4(name, value);
}
void SetMaterialTexturePath(Material* material, const char* name, const char* fileName) {
if (material == nullptr || name == nullptr || fileName == nullptr || !material->HasProperty(name)) {
return;
}
const std::string assetPath = BuildNahidaTextureAssetPath(fileName);
const ResourceHandle<Texture> texture = ResourceManager::Get().Load<Texture>(assetPath.c_str());
if (texture.Get() != nullptr) {
material->SetTexture(name, texture);
return;
}
material->SetTextureAssetRef(name, AssetRef(), XCEngine::Containers::String(assetPath.c_str()));
}
void ApplyNahidaSharedToonDefaults(Material* material) {
if (material == nullptr) {
return;
}
material->ClearKeywords();
material->ClearTags();
material->SetRenderStateOverrideEnabled(false);
SetMaterialFloat4IfPresent(material, "_BaseColor", XCEngine::Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
SetMaterialFloatIfPresent(material, "_Cutoff", 0.5f);
SetMaterialFloatIfPresent(material, "_IsDay", 1.0f);
SetMaterialFloatIfPresent(material, "_DoubleSided", 0.0f);
SetMaterialFloat4IfPresent(material, "_LightDirectionMultiplier", XCEngine::Math::Vector4(1.0f, 0.5f, 1.0f, 0.0f));
SetMaterialFloatIfPresent(material, "_ShadowOffset", 0.1f);
SetMaterialFloatIfPresent(material, "_ShadowSmoothness", 0.4f);
SetMaterialFloat4IfPresent(material, "_ShadowColor", XCEngine::Math::Vector4(1.1f, 1.1f, 1.1f, 1.0f));
SetMaterialFloatIfPresent(material, "_UseCustomMaterialType", 0.0f);
SetMaterialFloatIfPresent(material, "_CustomMaterialType", 1.0f);
SetMaterialFloatIfPresent(material, "_UseEmission", 0.0f);
SetMaterialFloatIfPresent(material, "_EmissionIntensity", 0.2f);
SetMaterialFloatIfPresent(material, "_UseNormalMap", 0.0f);
SetMaterialFloatIfPresent(material, "_IsFace", 0.0f);
SetMaterialFloat4IfPresent(material, "_FaceDirection", XCEngine::Math::Vector4(0.0f, 0.0f, 1.0f, 0.0f));
SetMaterialFloatIfPresent(material, "_FaceShadowOffset", 0.0f);
SetMaterialFloat4IfPresent(material, "_FaceBlushColor", XCEngine::Math::Vector4(1.0f, 0.72156864f, 0.69803923f, 1.0f));
SetMaterialFloatIfPresent(material, "_FaceBlushStrength", 0.0f);
SetMaterialFloatIfPresent(material, "_UseSpecular", 0.0f);
SetMaterialFloatIfPresent(material, "_SpecularSmoothness", 5.0f);
SetMaterialFloatIfPresent(material, "_NonmetallicIntensity", 0.3f);
SetMaterialFloatIfPresent(material, "_MetallicIntensity", 8.0f);
SetMaterialFloatIfPresent(material, "_UseRim", 0.0f);
SetMaterialFloatIfPresent(material, "_RimOffset", 5.0f);
SetMaterialFloatIfPresent(material, "_RimThreshold", 0.5f);
SetMaterialFloatIfPresent(material, "_RimIntensity", 0.5f);
SetMaterialFloatIfPresent(material, "_UseSmoothNormal", 0.0f);
SetMaterialFloatIfPresent(material, "_OutlineWidth", 1.6f);
SetMaterialFloat4IfPresent(material, "_OutlineWidthParams", XCEngine::Math::Vector4(0.0f, 6.0f, 0.1f, 0.6f));
SetMaterialFloatIfPresent(material, "_OutlineZOffset", 0.1f);
SetMaterialFloat4IfPresent(material, "_ScreenOffset", XCEngine::Math::Vector4(0.0f, 0.0f, 0.0f, 0.0f));
SetMaterialFloat4IfPresent(material, "_OutlineColor", XCEngine::Math::Vector4(0.5176471f, 0.35686275f, 0.34117648f, 1.0f));
SetMaterialFloat4IfPresent(material, "_OutlineColor2", XCEngine::Math::Vector4(0.3529412f, 0.3529412f, 0.3529412f, 1.0f));
SetMaterialFloat4IfPresent(material, "_OutlineColor3", XCEngine::Math::Vector4(0.47058824f, 0.47058824f, 0.5647059f, 1.0f));
SetMaterialFloat4IfPresent(material, "_OutlineColor4", XCEngine::Math::Vector4(0.5176471f, 0.35686275f, 0.34117648f, 1.0f));
SetMaterialFloat4IfPresent(material, "_OutlineColor5", XCEngine::Math::Vector4(0.35f, 0.35f, 0.35f, 1.0f));
}
void ApplyNahidaSurfaceRecipe(
Material* material,
const char* baseMapFile,
const char* lightMapFile,
const char* normalMapFile,
const char* shadowRampFile,
bool doubleSided,
bool useSmoothNormal,
float outlineZOffset,
const XCEngine::Math::Vector4* outlineColor = nullptr) {
if (material == nullptr) {
return;
}
ApplyNahidaSharedToonDefaults(material);
SetMaterialTexturePath(material, "_BaseMap", baseMapFile);
SetMaterialTexturePath(material, "_LightMap", lightMapFile);
SetMaterialTexturePath(material, "_NormalMap", normalMapFile);
SetMaterialTexturePath(material, "_ShadowRamp", shadowRampFile);
SetMaterialTexturePath(material, "_MetalMap", "Avatar_Tex_MetalMap.png");
SetMaterialFloatIfPresent(material, "_UseEmission", 1.0f);
SetMaterialFloatIfPresent(material, "_UseNormalMap", 1.0f);
SetMaterialFloatIfPresent(material, "_UseRim", 1.0f);
SetMaterialFloatIfPresent(material, "_UseSpecular", 1.0f);
SetMaterialFloatIfPresent(material, "_UseSmoothNormal", useSmoothNormal ? 1.0f : 0.0f);
SetMaterialFloatIfPresent(material, "_DoubleSided", doubleSided ? 1.0f : 0.0f);
if (outlineZOffset != 0.0f) {
SetMaterialFloatIfPresent(material, "_OutlineZOffset", outlineZOffset);
}
if (outlineColor != nullptr) {
SetMaterialFloat4IfPresent(material, "_OutlineColor", *outlineColor);
}
material->EnableKeyword("_EMISSION");
material->EnableKeyword("_NORMAL_MAP");
material->EnableKeyword("_SPECULAR");
material->EnableKeyword("_RIM");
if (doubleSided) {
MaterialRenderState renderState = material->GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material->SetRenderState(renderState);
}
}
void ApplyNahidaFaceRecipe(Material* material, bool eyebrowVariant) {
if (material == nullptr) {
return;
}
ApplyNahidaSharedToonDefaults(material);
SetMaterialTexturePath(material, "_BaseMap", "Avatar_Loli_Catalyst_Nahida_Tex_Face_Diffuse.png");
SetMaterialTexturePath(material, "_FaceLightMap", "Avatar_Loli_Tex_FaceLightmap.png");
SetMaterialTexturePath(material, "_FaceShadow", "Avatar_Tex_Face_Shadow.png");
SetMaterialTexturePath(material, "_ShadowRamp", "Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png");
SetMaterialFloatIfPresent(material, "_IsFace", 1.0f);
SetMaterialFloatIfPresent(material, "_UseCustomMaterialType", 1.0f);
SetMaterialFloatIfPresent(material, "_UseRim", 1.0f);
SetMaterialFloat4IfPresent(
material,
"_FaceDirection",
XCEngine::Math::Vector4(-0.0051237254f, -0.11509954f, -0.99334073f, 0.0f));
if (eyebrowVariant) {
SetMaterialFloatIfPresent(material, "_OutlineWidth", 0.0f);
SetMaterialFloat4IfPresent(
material,
"_BaseColor",
XCEngine::Math::Vector4(0.9764706f, 0.80103135f, 0.76164705f, 1.0f));
} else {
SetMaterialFloatIfPresent(material, "_FaceBlushStrength", 0.3f);
SetMaterialFloatIfPresent(material, "_OutlineZOffset", 0.5f);
}
material->EnableKeyword("_IS_FACE");
material->EnableKeyword("_RIM");
}
void ApplyNahidaToonRecipe(Material* material, const GameObject* owner) {
if (material == nullptr || owner == nullptr) {
return;
}
const std::string& name = owner->GetName();
if (name == "Body_Mesh0") {
const XCEngine::Math::Vector4 outlineColor(0.2784314f, 0.18039216f, 0.14901961f, 1.0f);
ApplyNahidaSurfaceRecipe(
material,
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Diffuse.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Lightmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Normalmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Shadow_Ramp.png",
false,
true,
0.0f,
&outlineColor);
return;
}
if (name == "Body_Mesh1") {
ApplyNahidaSurfaceRecipe(
material,
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Diffuse.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Lightmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Normalmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png",
false,
true,
0.0f);
return;
}
if (name == "Body_Mesh2") {
ApplyNahidaSurfaceRecipe(
material,
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Diffuse.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Lightmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Normalmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png",
true,
true,
0.5f);
return;
}
if (name == "Body_Mesh3") {
ApplyNahidaSurfaceRecipe(
material,
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Diffuse.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Lightmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Normalmap.png",
"Avatar_Loli_Catalyst_Nahida_Tex_Hair_Shadow_Ramp.png",
true,
false,
0.5f);
return;
}
if (name == "Brow") {
ApplyNahidaFaceRecipe(material, true);
return;
}
if (name == "EyeStar" || name == "Face" || name == "Face_Eye") {
ApplyNahidaFaceRecipe(material, false);
}
}
std::unordered_set<std::string> GetIsolationObjectNames() {
std::unordered_set<std::string> result;
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
@@ -338,7 +613,7 @@ private:
void ApplyDiagnosticOverrides();
void DumpTargetDiagnostics();
RHIResourceView* GetCurrentBackBufferView();
Material* CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath);
Material* CreateOverrideMaterial(const Material* sourceMaterial, const char* shaderPath, const GameObject* owner);
HMODULE m_assimpModule = nullptr;
std::unique_ptr<Scene> m_scene;
@@ -489,7 +764,20 @@ void NahidaPreviewSceneTest::TouchSceneMaterialsAndTextures() {
continue;
}
meshFilter->GetMesh();
Mesh* mesh = meshFilter->GetMesh();
if (mesh == nullptr) {
continue;
}
for (Material* material : mesh->GetMaterials()) {
if (material == nullptr) {
continue;
}
for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) {
material->GetTextureBindingTexture(bindingIndex);
}
}
}
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
@@ -539,31 +827,111 @@ void NahidaPreviewSceneTest::ApplyIsolationFilter() {
}
}
Material* NahidaPreviewSceneTest::CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath) {
Material* NahidaPreviewSceneTest::CreateOverrideMaterial(
const Material* sourceMaterial,
const char* shaderPath,
const GameObject* owner) {
if (sourceMaterial == nullptr || shaderPath == nullptr) {
return nullptr;
}
const ResourceHandle<Shader> shader = ResourceManager::Get().Load<Shader>(shaderPath);
if (shader.Get() == nullptr) {
return nullptr;
}
auto material = std::make_unique<Material>();
IResource::ConstructParams params = {};
params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial");
params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str());
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetShader(ResourceManager::Get().Load<Shader>(shaderPath));
material->SetShader(shader);
material->SetRenderQueue(sourceMaterial->GetRenderQueue());
if (std::string(shaderPath) == kToonShaderPath) {
ApplyNahidaToonRecipe(material.get(), owner);
} else {
if (sourceMaterial->HasRenderStateOverride()) {
material->SetRenderState(sourceMaterial->GetRenderState());
}
const ResourceHandle<Texture> baseMap = sourceMaterial->GetTexture("_BaseMap");
if (baseMap.Get() != nullptr) {
material->SetTexture("_MainTex", baseMap);
for (uint32_t tagIndex = 0; tagIndex < sourceMaterial->GetTagCount(); ++tagIndex) {
material->SetTag(sourceMaterial->GetTagName(tagIndex), sourceMaterial->GetTagValue(tagIndex));
}
for (uint32_t keywordIndex = 0; keywordIndex < sourceMaterial->GetKeywordCount(); ++keywordIndex) {
material->EnableKeyword(sourceMaterial->GetKeyword(keywordIndex));
}
for (const MaterialProperty& property : sourceMaterial->GetProperties()) {
const XCEngine::Containers::String targetName = RemapMaterialPropertyName(*material, property.name);
if (targetName.Empty()) {
continue;
}
switch (property.type) {
case MaterialPropertyType::Float:
material->SetFloat(targetName, property.value.floatValue[0]);
break;
case MaterialPropertyType::Float2:
material->SetFloat2(
targetName,
XCEngine::Math::Vector2(property.value.floatValue[0], property.value.floatValue[1]));
break;
case MaterialPropertyType::Float3:
material->SetFloat3(
targetName,
XCEngine::Math::Vector3(
property.value.floatValue[0],
property.value.floatValue[1],
property.value.floatValue[2]));
break;
case MaterialPropertyType::Float4:
material->SetFloat4(
targetName,
XCEngine::Math::Vector4(
property.value.floatValue[0],
property.value.floatValue[1],
property.value.floatValue[2],
property.value.floatValue[3]));
break;
case MaterialPropertyType::Int:
material->SetInt(targetName, property.value.intValue[0]);
break;
case MaterialPropertyType::Bool:
material->SetBool(targetName, property.value.boolValue);
break;
case MaterialPropertyType::Texture:
case MaterialPropertyType::Cubemap:
case MaterialPropertyType::Int2:
case MaterialPropertyType::Int3:
case MaterialPropertyType::Int4:
default:
break;
}
}
for (uint32_t bindingIndex = 0; bindingIndex < sourceMaterial->GetTextureBindingCount(); ++bindingIndex) {
const XCEngine::Containers::String sourceName = sourceMaterial->GetTextureBindingName(bindingIndex);
const XCEngine::Containers::String targetName = RemapMaterialPropertyName(*material, sourceName);
if (targetName.Empty()) {
continue;
}
const ResourceHandle<Texture> texture = sourceMaterial->GetTextureBindingTexture(bindingIndex);
if (texture.Get() != nullptr) {
material->SetTexture(targetName, texture);
continue;
}
const AssetRef textureRef = sourceMaterial->GetTextureBindingAssetRef(bindingIndex);
const XCEngine::Containers::String texturePath = sourceMaterial->GetTextureBindingPath(bindingIndex);
if (textureRef.IsValid() || !texturePath.Empty()) {
material->SetTextureAssetRef(targetName, textureRef, texturePath);
}
}
}
if (std::string(shaderPath) == "builtin://shaders/forward-lit") {
material->EnableKeyword("XC_ALPHA_TEST");
material->SetFloat("_Cutoff", sourceMaterial->GetFloat("_Cutoff"));
}
material->SetFloat4("_BaseColor", sourceMaterial->GetFloat4("_BaseColor"));
m_overrideMaterials.push_back(std::move(material));
return m_overrideMaterials.back().get();
}
@@ -574,10 +942,6 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
const DiagnosticMode mode = GetDiagnosticMode();
std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode));
if (mode == DiagnosticMode::Original) {
return;
}
if (mode == DiagnosticMode::NoShadows) {
const std::vector<LightComponent*> lights = m_scene->FindObjectsOfType<LightComponent>();
for (LightComponent* light : lights) {
@@ -588,36 +952,46 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
return;
}
const char* shaderPath = mode == DiagnosticMode::ForwardLit
? "builtin://shaders/forward-lit"
: "builtin://shaders/unlit";
const char* shaderPath = nullptr;
switch (mode) {
case DiagnosticMode::Toon:
shaderPath = "Assets/Shaders/Toon.shader";
break;
case DiagnosticMode::ForwardLit:
shaderPath = "builtin://shaders/forward-lit";
break;
case DiagnosticMode::Unlit:
shaderPath = "builtin://shaders/unlit";
break;
case DiagnosticMode::NoShadows:
default:
return;
}
std::unordered_map<Material*, Material*> overrideBySource;
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
for (MeshRendererComponent* meshRenderer : meshRenderers) {
if (meshRenderer == nullptr) {
if (meshRenderer == nullptr || !IsNahidaCharacterRenderable(meshRenderer->GetGameObject())) {
continue;
}
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
Material* sourceMaterial = meshRenderer->GetMaterial(materialIndex);
auto* meshFilter = meshRenderer->GetGameObject()->GetComponent<MeshFilterComponent>();
Mesh* mesh = meshFilter != nullptr ? meshFilter->GetMesh() : nullptr;
const size_t materialCount = std::max(
meshRenderer->GetMaterialCount(),
mesh != nullptr ? static_cast<size_t>(mesh->GetMaterials().Size()) : 0u);
for (size_t materialIndex = 0; materialIndex < materialCount; ++materialIndex) {
const Material* sourceMaterial = nullptr;
if (materialIndex < meshRenderer->GetMaterialCount()) {
sourceMaterial = meshRenderer->GetMaterial(materialIndex);
}
if (sourceMaterial == nullptr && mesh != nullptr && materialIndex < mesh->GetMaterials().Size()) {
sourceMaterial = mesh->GetMaterial(static_cast<uint32_t>(materialIndex));
}
if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) {
continue;
}
if (std::string(sourceMaterial->GetShader()->GetPath().CStr()) != "Assets/Shaders/XCCharacterToon.shader") {
continue;
}
Material* overrideMaterial = nullptr;
const auto found = overrideBySource.find(sourceMaterial);
if (found != overrideBySource.end()) {
overrideMaterial = found->second;
} else {
overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath);
overrideBySource.emplace(sourceMaterial, overrideMaterial);
}
Material* overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath, meshRenderer->GetGameObject());
if (overrideMaterial != nullptr) {
meshRenderer->SetMaterial(materialIndex, overrideMaterial);
}
@@ -632,10 +1006,11 @@ void NahidaPreviewSceneTest::DumpTargetDiagnostics() {
const char* const targetObjects[] = {
"Body_Mesh0",
"Body_Mesh1",
"Body_Mesh2",
"Body_Mesh3",
"Brow",
"EyeStar",
"Dress_Mesh0",
"Hair_Mesh0",
"Face",
"Face_Eye"
};