champions-of-luneraria/Assets/Luneraria/Editor/World/LuneMaterialFactory.cs
Martin Stickelbroeck 07fa4572e0 U5.3: Gebäude, Boss und Ringborke auf PBR; Bildfehler aus U5 behoben
Fortsetzung des angefangenen U5.3-Stands vom 2. Oktober.

- Fünf neue Sätze aus bake_pbr.py (metal, cloth, hide, bone, wood) für
  Türme, Ancients, Grotte und die Schlundkrone. Ringborkes Rinde (RB_Bark)
  auf ihren eigenen UVs mit dem 4k-Satz (Rauigkeit zu ORM gepackt),
  Normalen im Tangentenraum des Meshes. Aufnahmen aller drei Lanes.
- Befund: Seit U5 war die Welt halb so hell und ohne Grün (Mid-Lane
  Helligkeit 0,27, Grün 0 %). Ursachen und Korrekturen:
  - Die PBR-Albedos wurden über die Godot-Farben multipliziert (Rasen
    etwa x0,05). Jetzt teilt der Shader jede Karte durch ihren Mittel-
    wert; die Godot-Farbe bleibt der Durchschnitt, die Karte liefert
    Struktur. Ebenso die Rauigkeit, wo das Material eine eigene hat.
  - Normal- und ORM-Karten waren als sRGB-Farbe importiert. Jetzt
    Normalkarte bzw. linear.
  - bake_pbr.py verzerrte alle Muster 10:1 (Regenstreifen). Stein, Erde,
    Laub, Wasser und die neuen Sätze ohne Maserung neu gebacken.
  - Der Treffer-Flash ersetzte die Grundfarbe aller Materialplätze; im
    Nahkampf waren die Armeen weiß und der Boss gelb. Jetzt Tönung der
    eigenen Farbe je Platz, wenig Emission, 16 Stufen.
  - Nebel 0,0085 verschleierte die Kartenansicht zu 35-50 %; jetzt 0,0045.
  Ergebnis Mid-Lane: Helligkeit 0,41-0,44, Grün 31-39 % (U3: 0,45 / 27 %).
- Tests: EditMode 29/29, PlayMode 13/13, Verify 718/0, Kerntests .NET
  457/457, Fingerabdrücke unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Leistung: A/B mit einem U5.2-Player unter gleicher Last, kein messbarer
  Unterschied (91,4 gegen 93,8 fps Karte, 124,9 gegen 125,3 fps Mid).
- Messwerkzeug tools/port/png_stats.py; Doku U5-VISUAL (U5.3),
  PORT-STATUS, PORT-QA; Bilder screenshots/u5.3/ als JPEG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:34:02 +02:00

510 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Luneraria.View;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace Luneraria.EditorTools
{
/// <summary>
/// Creates the shared materials (U2B) from the Godot material library
/// (scripts/luneraria/view/lune_materials.gd), the baked world materials and the prop
/// materials of map_props.fbx, with team variants for team surfaces. Every material uses
/// Luneraria/Surface (modes see the shader) or Luneraria/Pad. Idempotent: existing assets are
/// updated in place, so scene references stay valid.
/// </summary>
public static class LuneMaterialFactory
{
public const string Folder = "Assets/Luneraria/Materials";
public const string SetPath = Folder + "/LuneMaterials.asset";
const string Textures = "Assets/Luneraria/Art/Textures";
const string StoneMapPath = Textures + "/limestone-albedo.png";
// Surface modes of Luneraria/Surface.
public const int Plain = 0, Terrain = 1, Stone = 2, Paving = 3, Soil = 4, Bark = 5, Foliage = 6, Water = 7, Vein = 8, Crystal = 9;
/// <summary>Team style (Blau, Rot, neutral grotto) of lune_materials.gd TEAM_STYLE.</summary>
public static readonly Color[] TeamCrystal = { Hex("78c6ff"), Hex("ff7d45"), Hex("b784ff") };
public static readonly Color[] TeamRune = { Hex("86d2ff"), Hex("ffae5a"), Hex("c29cff") };
public static readonly Color[] TeamMetal = { new Color(.80f, .85f, .92f), new Color(.92f, .64f, .34f), new Color(.70f, .62f, .50f) };
public static readonly Color[] TeamCloth = { Hex("2a5aa8"), Hex("9a2c20"), Hex("45305f") };
public static readonly Color[] TeamLight = { Hex("8cc8ff"), Hex("ffae72"), Hex("b37cff") };
public static readonly Color[] TeamColor = { Hex("5aa8ff"), Hex("ff6a55"), Hex("b37cff") };
static readonly string[] TeamNames = { "Blau", "Rot", "Neutral" };
/// <summary>U5 PBR sets of tools/blender/bake_pbr.py: albedo, normal, ORM.</summary>
static readonly string[] PbrSets = { "limestone", "soil", "bark", "leaf", "rock", "water", "metal", "cloth", "hide", "bone", "wood" };
sealed class Spec
{
public int Mode;
public Color Base = Color.white;
public float Roughness = .88f;
public float Metallic;
public Color Emission = Color.black;
public float Energy;
public bool VertexColor;
public bool DoubleSided;
public float StoneScale = .23f;
public float Relief = .016f;
/// <summary>U5 PBR set name (limestone, soil, bark, leaf, rock, water) or "" for none.</summary>
public string Pbr = "";
public float NormalStrength = 1f;
/// <summary>Weg 2: 1 = lane tier (full PBR), 0 = edge tier (colour + stone relief).</summary>
public float Quality = 1f;
/// <summary>U5.3: 1 = sample the authored UVs of a character (Ringborke bark), 0 = world triplanar.</summary>
public bool UvMaps;
/// <summary>Authored UV textures of a character (Ringborke); overrides the baked set.</summary>
public Texture2D UvAlbedo;
public Texture2D UvNormal;
public Texture2D UvOrm;
}
static Shader _surface;
static Texture2D _stone;
static readonly Dictionary<string, Texture2D> _albedo = new Dictionary<string, Texture2D>();
static readonly Dictionary<string, Texture2D> _normal = new Dictionary<string, Texture2D>();
static readonly Dictionary<string, Texture2D> _orm = new Dictionary<string, Texture2D>();
/// <summary>Mean linear value of a map (albedo colour or ORM channels), measured once per build.</summary>
static readonly Dictionary<Texture2D, Vector4> _means = new Dictionary<Texture2D, Vector4>();
public static Color Hex(string hex)
{
ColorUtility.TryParseHtmlString("#" + hex, out Color color);
return color;
}
/// <summary>
/// An HDR emission value for an [HDR] material colour: Godot's emission (sRGB colour, linear
/// energy multiplier) as the linear value Unity hands to the shader unchanged.
/// </summary>
public static Color Hdr(Color srgb, float energy)
{
Color linear = srgb.linear * energy;
return new Color(linear.r, linear.g, linear.b, 1f);
}
/// <summary>
/// A plain (non-HDR) colour that Unity converts from gamma to linear, carrying a linear
/// value above one (RenderSettings.ambientLight = colour * energy).
/// </summary>
public static Color GammaEncoded(Color srgb, float energy)
{
Color linear = srgb.linear * energy;
return new Color(Mathf.LinearToGammaSpace(linear.r), Mathf.LinearToGammaSpace(linear.g), Mathf.LinearToGammaSpace(linear.b), 1f);
}
public static LuneMaterialSet Build(LuneWorldBake bake, out Dictionary<int, Material> worldMaterials)
{
Directory.CreateDirectory(Folder + "/Buildings");
Directory.CreateDirectory(Folder + "/World");
Directory.CreateDirectory(Folder + "/Props");
AssetDatabase.Refresh();
_surface = Shader.Find("Luneraria/Surface");
if (_surface == null)
throw new InvalidOperationException("shader Luneraria/Surface missing");
_stone = AssetDatabase.LoadAssetAtPath<Texture2D>(StoneMapPath);
_albedo.Clear();
_normal.Clear();
_orm.Clear();
_means.Clear();
foreach (string set in PbrSets)
{
_albedo[set] = Map(Textures + "/" + set + "-albedo-2k.png", TextureImporterType.Default, true);
_normal[set] = Map(Textures + "/" + set + "-normal-2k.png", TextureImporterType.NormalMap, false);
_orm[set] = Map(Textures + "/" + set + "-orm-2k.png", TextureImporterType.Default, false);
}
var setAsset = AssetDatabase.LoadAssetAtPath<LuneMaterialSet>(SetPath);
if (setAsset == null)
{
setAsset = ScriptableObject.CreateInstance<LuneMaterialSet>();
AssetDatabase.CreateAsset(setAsset, SetPath);
}
setAsset.Entries.Clear();
BuildingMaterials(setAsset);
PropMaterials(setAsset);
worldMaterials = WorldMaterials(bake);
EditorUtility.SetDirty(setAsset);
AssetDatabase.SaveAssets();
return setAsset;
}
// ------------------------------------------------------------ G2 buildings and boss (LN_*)
static void BuildingMaterials(LuneMaterialSet set)
{
Fixed(set, "LN_Stone_Pale", new Spec { Mode = Stone, Base = new Color(.72f, .68f, .61f), VertexColor = true, Pbr = "limestone" });
Fixed(set, "LN_Stone_Dark", new Spec { Mode = Stone, Base = new Color(.30f, .30f, .33f), Roughness = .9f, VertexColor = true, Pbr = "limestone" });
Fixed(set, "LN_Stone_Grotto", new Spec { Mode = Stone, Base = new Color(.43f, .43f, .46f), VertexColor = true, Pbr = "limestone" });
Fixed(set, "LN_Rock", new Spec { Mode = Stone, Base = new Color(.38f, .38f, .40f), Roughness = .93f, VertexColor = true, StoneScale = .13f, Pbr = "rock" });
// U5.3: metal, cloth, hide, bone and wood carry their own baked PBR set (target
// quality of every figure along the lanes; the generators keep their geometry).
Teams(set, "LN_Metal_Team", team => new Spec { Mode = Plain, Base = TeamMetal[team] * .92f, Metallic = .9f, Roughness = .32f, VertexColor = true, Pbr = "metal" });
Fixed(set, "LN_Metal_Iron", new Spec { Mode = Plain, Base = new Color(.24f, .24f, .26f), Metallic = .8f, Roughness = .52f, VertexColor = true, Pbr = "metal" });
Fixed(set, "LN_Metal_Bronze", new Spec { Mode = Plain, Base = new Color(.66f, .47f, .24f), Metallic = .9f, Roughness = .42f, VertexColor = true, Pbr = "metal" });
Teams(set, "LN_Crystal_Team", team => new Spec { Mode = Crystal, Base = TeamCrystal[team], Emission = TeamCrystal[team], Energy = 3.2f });
Fixed(set, "LN_Crystal_Grotto", new Spec { Mode = Crystal, Base = TeamCrystal[2], Emission = TeamCrystal[2], Energy = 3.2f });
// Godot lights only the glyph strokes of a rune band; a dim band stands in for them.
Teams(set, "LN_Rune_Team", team => new Spec { Mode = Plain, Base = new Color(.16f, .16f, .18f), Emission = TeamRune[team], Energy = .45f, Roughness = .5f, VertexColor = true });
Fixed(set, "LN_Rune_Grotto", new Spec { Mode = Plain, Base = new Color(.16f, .16f, .18f), Emission = TeamRune[2], Energy = .45f, Roughness = .5f, VertexColor = true });
Teams(set, "LN_Cloth_Team", team => new Spec { Mode = Plain, Base = TeamCloth[team], Roughness = .85f, DoubleSided = true, VertexColor = true, Pbr = "cloth" });
Fixed(set, "LN_Water_Glow", new Spec { Mode = Plain, Base = new Color(.45f, .35f, .80f) * .3f, Emission = new Color(.45f, .35f, .80f), Energy = 1.2f, Roughness = .1f });
Fixed(set, "LN_Boss_Hide", new Spec { Mode = Plain, Base = new Color(.24f, .25f, .29f), Roughness = .84f, VertexColor = true, Pbr = "hide" });
Fixed(set, "LN_Boss_Bone", new Spec { Mode = Plain, Base = new Color(.74f, .71f, .64f), Roughness = .62f, VertexColor = true, Pbr = "bone" });
Fixed(set, "LN_Boss_Bronze", new Spec { Mode = Plain, Base = new Color(.42f, .31f, .18f), Metallic = .85f, Roughness = .42f, VertexColor = true, Pbr = "metal" });
// The grotto boss glows in the neutral colour; a deployed boss in its team's.
Teams(set, "LN_Crystal_Boss", team => new Spec { Mode = Crystal, Base = TeamCrystal[team], Emission = TeamCrystal[team], Energy = 2.4f }, neutral: true);
Fixed(set, "LN_Wood", new Spec { Mode = Plain, Base = new Color(.33f, .23f, .15f), Roughness = .82f, VertexColor = true, Pbr = "wood" });
Fixed(set, "LN_Cave", new Spec { Mode = Plain, Base = new Color(.02f, .015f, .03f), Roughness = 1f });
// U5.3: Ringborke bark on its authored UVs (the 4k set of the U2 asset hand-off).
// The roughness map is packed into ORM (R ao = 1, G roughness, B metal = 0).
Texture2D rbAlbedo = Map(Textures + "/ringborke-cut-albedo-4k.png", TextureImporterType.Default, true);
Texture2D rbNormal = Map(Textures + "/ringborke-cut-normal-4k.png", TextureImporterType.NormalMap, false);
Texture2D rbRough = AssetDatabase.LoadAssetAtPath<Texture2D>(Textures + "/ringborke-cut-roughness-4k.png");
if (rbAlbedo != null && rbNormal != null && rbRough != null)
{
// Plain, not the world bark mode: its world-space ridges would slide over a moving
// figure. The authored maps carry the bark; no vertex colours on the figure.
Fixed(set, "RB_Bark", new Spec
{
Mode = Plain,
Base = new Color(.42f, .34f, .24f),
Roughness = .84f,
VertexColor = false,
Pbr = "bark",
UvMaps = true,
UvAlbedo = rbAlbedo,
UvNormal = rbNormal,
UvOrm = PackRoughnessOrm(rbRough)
});
}
}
/// <summary>
/// A PBR map with the import its data needs (U5.3 finding): albedo is sRGB colour, normal
/// maps import as normal maps, ORM as linear data. The U5 sets imported all three as sRGB
/// colour, so the GPU gamma-decoded normals (tilted surfaces) and AO/roughness (darker,
/// glossier). Changed settings are written to the .meta and reimported once.
/// </summary>
static Texture2D Map(string path, TextureImporterType type, bool srgb)
{
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null && (importer.textureType != type || importer.sRGBTexture != srgb))
{
importer.textureType = type;
importer.sRGBTexture = srgb;
importer.SaveAndReimport();
}
return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
}
/// <summary>
/// Mean linear value of a map (every 7th pixel of its PNG; sRGB maps decoded to linear).
/// The shader divides by it, so a map adds detail around the Godot colour and roughness of
/// the material instead of multiplying them down (U5 drew the albedo maps over the Godot
/// colours: the terrain lost 80–95 % of its brightness and all of its green).
/// </summary>
static Vector4 Mean(Texture2D map, bool srgb)
{
if (map == null)
return Vector4.one;
if (_means.TryGetValue(map, out Vector4 mean))
return mean;
var probe = new Texture2D(2, 2, TextureFormat.RGBA32, false, true);
probe.LoadImage(File.ReadAllBytes(AssetDatabase.GetAssetPath(map)));
Color32[] pixels = probe.GetPixels32();
UnityEngine.Object.DestroyImmediate(probe);
var decode = new double[256];
for (int i = 0; i < 256; i++)
decode[i] = srgb ? Mathf.GammaToLinearSpace(i / 255f) : i / 255.0;
double r = 0, g = 0, b = 0;
int n = 0;
for (int i = 0; i < pixels.Length; i += 7)
{
r += decode[pixels[i].r];
g += decode[pixels[i].g];
b += decode[pixels[i].b];
n++;
}
mean = n > 0 ? new Vector4((float)(r / n), (float)(g / n), (float)(b / n), 1f) : Vector4.one;
_means[map] = mean;
return mean;
}
/// <summary>ORM from a grayscale roughness map (R ao = 1, G roughness, B metal = 0).</summary>
static Texture2D PackRoughnessOrm(Texture2D roughness)
{
string path = Textures + "/ringborke-cut-orm-4k.png";
var existing = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (existing != null)
return existing;
string roughPath = AssetDatabase.GetAssetPath(roughness);
var sourceImporter = AssetImporter.GetAtPath(roughPath) as TextureImporter;
if (sourceImporter != null && !sourceImporter.isReadable)
{
sourceImporter.isReadable = true;
sourceImporter.SaveAndReimport();
}
int width = roughness.width;
int height = roughness.height;
var orm = new Texture2D(width, height, TextureFormat.RGB24, false, true);
Color[] source = roughness.GetPixels();
var packed = new Color[source.Length];
for (int i = 0; i < source.Length; i++)
packed[i] = new Color(1f, source[i].r, 0f);
orm.SetPixels(packed);
orm.Apply(false, false);
File.WriteAllBytes(path, orm.EncodeToPNG());
UnityEngine.Object.DestroyImmediate(orm);
AssetDatabase.ImportAsset(path);
Texture2D imported = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (imported != null)
{
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null && importer.sRGBTexture)
{
importer.sRGBTexture = false;
importer.SaveAndReimport();
}
}
return imported != null ? imported : roughness;
}
// ------------------------------------------------------------ props of map_props.fbx (ENV_*)
static void PropMaterials(LuneMaterialSet set)
{
const string props = "Assets/Luneraria/Art/Environment/map_props.fbx";
foreach (Material source in AssetDatabase.LoadAllAssetsAtPath(props).OfType<Material>())
{
string key = source.name;
Color color = source.HasProperty("_BaseColor") ? source.GetColor("_BaseColor") : source.color;
Spec spec;
if (key.Contains("brass"))
// Godot keeps the glTF material for brass (no Kluft surface).
spec = new Spec { Mode = Plain, Base = color, Metallic = source.HasProperty("_Metallic") ? source.GetFloat("_Metallic") : .8f, Roughness = .4f };
else if (key.StartsWith("ENV_"))
spec = new Spec { Mode = SurfaceMode(key), Base = color };
else
spec = new Spec { Mode = Plain, Base = color, Roughness = .8f, DoubleSided = true };
Add(set, key, Create(Folder + "/Props/" + key + ".mat", spec), null, null);
}
}
public static int SurfaceMode(string key)
{
if (key.Contains("leaf"))
return Foliage;
if (key.Contains("bark"))
return Bark;
if (key.Contains("soil"))
return Soil;
if (key.Contains("paving"))
return Paving;
return Stone;
}
// ------------------------------------------------------------ baked world materials
static Dictionary<int, Material> WorldMaterials(LuneWorldBake bake)
{
var result = new Dictionary<int, Material>();
var materials = (JArray)bake.Data["materials"];
var veins = 0;
for (int index = 0; index < materials.Count; index++)
{
var entry = (JObject)materials[index];
string type = (string)entry["type"];
string shader = (string)entry["shader"] ?? "";
string key = (string)entry["surface_key"];
JObject parameters = (JObject)entry["params"] ?? new JObject();
string name;
Spec spec;
if (key != null)
{
name = key;
spec = new Spec { Mode = SurfaceMode(key), Base = ColorOf(parameters["base_color"]) };
}
else if (shader.EndsWith("lune_terrain.gdshader"))
{
name = "Terrain";
spec = new Spec { Mode = Terrain, StoneScale = .21f };
result[index] = Create(Folder + "/World/" + name + ".mat", spec);
// Weg 2 edge tier: the surrounding mountains keep colour and stone relief only.
Create(Folder + "/World/TerrainEdge.mat", new Spec { Mode = Terrain, StoneScale = .21f, Quality = 0f });
continue;
}
else if (shader.EndsWith("river.gdshader"))
{
name = "River";
spec = new Spec { Mode = Water };
}
else if (shader.EndsWith("lune_vein.gdshader"))
{
// Vein tint and seed are per-instance parameters in Godot; the builder assigns
// one material per vein (see VeinMaterial).
continue;
}
else if (shader.EndsWith("lune_pad.gdshader"))
{
Color tint = ColorOf(parameters["tint"]);
int team = tint.r > tint.b ? 1 : 0;
name = "Pad_" + TeamNames[team];
result[index] = PadMaterial(Folder + "/World/" + name + ".mat", tint, (float)parameters["pad_length"], (float)parameters["row_start"]);
continue;
}
else if (type == "standard")
{
Color albedo = ColorOf(entry["albedo"]);
bool glow = entry["emission"] != null;
name = "Glow_" + ColorUtility.ToHtmlStringRGB(albedo) + (glow ? "_" + ((float)entry["emission_energy"]).ToString("0.0#", System.Globalization.CultureInfo.InvariantCulture) : "");
spec = new Spec { Mode = Plain, Base = albedo, Roughness = (float)entry["roughness"], Metallic = (float)entry["metallic"] };
if (glow)
{
spec.Emission = ColorOf(entry["emission"]);
spec.Energy = (float)entry["emission_energy"];
}
}
else
{
throw new InvalidOperationException("unknown world material " + entry);
}
result[index] = Create(Folder + "/World/" + name + ".mat", spec);
}
return result;
}
/// <summary>One material per energy vein (tint from its team, seed from the bake).</summary>
public static Material VeinMaterial(string name, Color tint, float seed)
{
Material material = Create(Folder + "/World/" + name + ".mat", new Spec { Mode = Vein, Base = tint });
material.SetFloat("_Seed", seed);
material.SetFloat("_Energy", 1f);
EditorUtility.SetDirty(material);
return material;
}
static Material PadMaterial(string path, Color tint, float length, float rowStart)
{
Shader shader = Shader.Find("Luneraria/Pad");
var material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material == null)
{
material = new Material(shader);
AssetDatabase.CreateAsset(material, path);
}
material.shader = shader;
material.SetColor("_Tint", tint);
material.SetFloat("_PadLength", length);
material.SetFloat("_RowStart", rowStart);
EditorUtility.SetDirty(material);
return material;
}
public static Color ColorOf(JToken values)
{
return new Color((float)values[0], (float)values[1], (float)values[2], values.Count() > 3 ? (float)values[3] : 1f);
}
static void Fixed(LuneMaterialSet set, string key, Spec spec)
{
Add(set, key, Create(Folder + "/Buildings/" + key + ".mat", spec), null, null);
}
static void Teams(LuneMaterialSet set, string key, Func<int, Spec> spec, bool neutral = true)
{
Material blau = Create(Folder + "/Buildings/" + key + "_Blau.mat", spec(0));
Material rot = Create(Folder + "/Buildings/" + key + "_Rot.mat", spec(1));
Material none = neutral ? Create(Folder + "/Buildings/" + key + "_Neutral.mat", spec(2)) : blau;
Add(set, key, none, blau, rot);
}
static void Add(LuneMaterialSet set, string key, Material neutral, Material blau, Material rot)
{
set.Entries.Add(new LuneMaterialSet.Entry { Key = key, Neutral = neutral, Blau = blau, Rot = rot });
}
static Material Create(string path, Spec spec)
{
var material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material == null)
{
material = new Material(_surface);
AssetDatabase.CreateAsset(material, path);
}
material.shader = _surface;
material.enableInstancing = true;
material.SetColor("_BaseColor", spec.Base);
material.SetFloat("_Mode", spec.Mode);
material.SetFloat("_VertexColor", spec.VertexColor ? 1f : 0f);
material.SetFloat("_Metallic", spec.Metallic);
material.SetFloat("_Roughness", spec.Roughness);
material.SetColor("_EmissionColor", spec.Energy > 0f ? Hdr(spec.Emission, spec.Energy) : Color.black);
material.SetFloat("_StoneScale", spec.StoneScale);
material.SetFloat("_Relief", spec.Relief);
material.SetFloat("_NormalStrength", spec.NormalStrength);
material.SetFloat("_Quality", spec.Quality);
material.SetFloat("_UvMaps", spec.UvMaps ? 1f : 0f);
material.SetFloat("_Cull", spec.DoubleSided ? 0f : 2f);
material.SetTexture("_StoneMap", _stone);
ApplyPbr(material, spec);
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.None;
EditorUtility.SetDirty(material);
return material;
}
/// <summary>U5: bind the baked albedo/normal/ORM set (lane tier) or the authored UVs of a character.</summary>
static void ApplyPbr(Material material, Spec spec)
{
if (spec.UvMaps && spec.UvAlbedo != null)
{
// U5.3: Ringborke bark on its authored UVs (albedo, normal, roughness as ORM).
Texture2D orm = spec.UvOrm != null ? spec.UvOrm : Texture2D.whiteTexture;
material.SetTexture("_AlbedoMap", spec.UvAlbedo);
material.SetTexture("_NormalMap", spec.UvNormal != null ? spec.UvNormal : Texture2D.normalTexture);
material.SetTexture("_OrmMap", orm);
material.SetVector("_AlbedoMean", Mean(spec.UvAlbedo, true));
material.SetVector("_OrmMean", spec.UvOrm != null ? Mean(spec.UvOrm, false) : Vector4.one);
return;
}
string key = spec.Pbr;
if (key.Length == 0)
key = PbrForMode(spec.Mode);
Texture2D albedo;
if (spec.Quality < .5f || key.Length == 0 || !_albedo.TryGetValue(key, out albedo) || albedo == null)
{
material.SetTexture("_AlbedoMap", Texture2D.whiteTexture);
material.SetTexture("_NormalMap", Texture2D.normalTexture);
material.SetTexture("_OrmMap", Texture2D.whiteTexture);
material.SetVector("_AlbedoMean", Vector4.one);
material.SetVector("_OrmMean", Vector4.one);
return;
}
material.SetTexture("_AlbedoMap", albedo);
material.SetTexture("_NormalMap", _normal[key]);
material.SetTexture("_OrmMap", _orm[key]);
material.SetVector("_AlbedoMean", Mean(albedo, true));
material.SetVector("_OrmMean", Mean(_orm[key], false));
}
public static string PbrForMode(int mode)
{
switch (mode)
{
case Stone:
case Paving:
return "limestone";
case Soil:
return "soil";
case Bark:
return "bark";
case Foliage:
return "leaf";
case Water:
return "water";
default:
return mode == Terrain ? "limestone" : "";
}
}
}
}