feat(dashboard): metal and glow looks in the UGC and zone 3D views

NifFile::ShaderLookFor knows Polished Metal (98), Brushed Steel (99) and
LEGO-Emissive (53). The UGC mesh route sends each mesh's look (from its
multishader tag), and the UGC 3D view draws metal as reflective and glow
unlit. The zone views draw LEGO-Emissive objects going to their vertex
color by its alpha, as the shader does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-28 09:55:00 -05:00
parent b34e8cb254
commit bc9ffeefaf
10 changed files with 130 additions and 21 deletions

View File

@@ -687,7 +687,7 @@ namespace NifFile {
}
}
uint8_t ShaderLookFor(int32_t shader) {
uint16_t ShaderLookFor(int32_t shader) {
// By the technique each shader class sets up (ShaderManager's factory table at 0x01889608, indexed by gameValue;
// the class's technique setup names it). Checked in the client: 33 and 82 Technique_Basic_NoLighting_VertColor_
// NoTexture, 35 and 84 Technique_Basic_NoLighting_VertColor, 37 Technique_Basic_Lighting_VertColor_NoTexture,
@@ -730,6 +730,15 @@ namespace NifFile {
case 8: case 10: case 54: case 15: case 23: case 35: case 52: case 57: case 62: case 68: case 70: case 73: case 81:
case 84: case 87: case 101:
return UNLIT;
// Polished Metal (Technique_Lighting_PolishedMetal_VertColor in Metallic.fx) and Brushed Steel (its noise
// in object space); both load their reflection textures themselves
case 98:
return REFLECTIVE;
case 99:
return REFLECTIVE | BRUSHED;
// LEGO-Emissive
case 53:
return EMISSIVE;
default:
return 0;
}
@@ -744,7 +753,7 @@ namespace NifFile {
return Parser(data, 0).Dds(block, error);
}
std::string Encode(const Model& model, const std::vector<std::string>& textures, const std::vector<std::string>& darkTextures) {
std::string Encode(const Model& model, const std::vector<std::string>& textures, const std::vector<std::string>& darkTextures, const std::vector<uint16_t>& looks) {
std::string body;
nlohmann::json meshes = nlohmann::json::array();
std::vector<std::string> names;
@@ -771,6 +780,7 @@ namespace NifFile {
{"vertexColors", material.vertexColorMode}, {"texture", textureIndex}, {"clampU", material.clampU}, {"clampV", material.clampV},
{"shaderTag", material.shaderTag}, {"darkTexture", uv2 ? darkIndex : -1}, {"uv2", uv2}
};
if (m < looks.size()) entry["look"] = looks[m];
Append(body, mesh.positions.data(), mesh.positions.size() * sizeof(float));
if (!mesh.normals.empty()) {
std::vector<int8_t> packed(mesh.normals.size());

View File

@@ -83,7 +83,7 @@ namespace NifFile {
* BasicShaders.fx, LEGOPPLighting.fx and Ocean.fx). Fixed function (-1) has none of these: Gamebryo lights it with
* the material and NiVertexColorProperty.
*/
enum eShaderLook : uint8_t {
enum eShaderLook : uint16_t {
UNLIT = 1, // no lighting: the colors as they are (the "NoLighting" techniques)
NO_TEXTURE = 2, // the texture isn't sampled ("NoTexture")
NO_VERTEX_COLORS = 4, // vertex colors aren't read
@@ -91,11 +91,18 @@ namespace NifFile {
// Two textures (base and dark, each with its UV set): blended by the vertex alpha, which is then no opacity
// ("Two Layers Blended"), or added, weighted by the material's diffuse red and green ("Two Textures Added")
TWO_LAYERS_BLENDED = 16,
TWO_LAYERS_ADDED = 32
TWO_LAYERS_ADDED = 32,
// Metal (Metallic.fx): the lit color plus an environment map (textures/metal/metal_reflection_*.dds, which the
// shader loads itself) tinted by the vertex color, polished or, with BRUSHED, brushed with object space noise
REFLECTIVE = 64,
BRUSHED = 128,
// LEGO-Emissive: lerp(lit, vertex color, vertex alpha * NiMaterialProperty's emissive red); the vertex alpha is
// that mask, not opacity
EMISSIVE = 256
};
// eShaderLook bits of a shader (mapShaders.gameValue); 0 for the usual lit look and for fixed function
uint8_t ShaderLookFor(int32_t shader);
uint16_t ShaderLookFor(int32_t shader);
// Where a node is in the model's space: p' = rotation * p + translation (row-major, scale folded in)
struct NodeTransform {
@@ -125,9 +132,11 @@ namespace NifFile {
* int8 normals (3 per vertex, times 127, padded to 4 bytes) when "normals", float32 UVs (2 per vertex) when "uv",
* float32 dark texture UVs when "uv2", uint8 RGBA colors when "colors", then uint16 indices (padded to 4 bytes).
* `textures[i]` is where mesh i's texture is (empty: none) and `darkTextures[i]` its dark texture; the header
* lists each once in "textures" and a mesh's "texture" and "darkTexture" index it (-1: none).
* lists each once in "textures" and a mesh's "texture" and "darkTexture" index it (-1: none). `looks[i]`, when
* given, is mesh i's "look" (eShaderLook bits of the shader it is drawn with, for views without a scenery manifest).
*/
std::string Encode(const Model& model, const std::vector<std::string>& textures, const std::vector<std::string>& darkTextures = {});
std::string Encode(const Model& model, const std::vector<std::string>& textures, const std::vector<std::string>& darkTextures = {},
const std::vector<uint16_t>& looks = {});
/**
* A texture stored inside a .nif (NiPixelData or NiPersistentSrcTextureRendererData, block `block`) as a DDS file