""" Carrier blockout materials (module of the import runner, see import_assets.py). Independent of every other track's materials. Makes, under /Game/Carrier/Materials: M_CarrierSurface opaque lit master: BaseColor / Metallic / Roughness / Emissive, plus - procedural panel seams and hazard stripes from the mesh UVs (which are metres: Vehicles/CarrierMeshBuilder.cpp), - the small "dry ambient" term of DESIGN_UE.md 7.3: emissive += BaseColor * AmbientColor * AmbientNits M_CarrierPool translucent moon-pool water surface (rippling, opacity 0.86; port of the v2 pool shader) MI_Carrier_ one instance per Vehicles/CarrierMeshBuilder.h EMat entry (the names must match) Exposure: emissive and ambient values only mean something against the post-process exposure. Since the integration pass they are calibrated against the game's one exposure authority, ADescentEnvironment (manual exposure at descent.Look.ExposureEV, no physical camera exposure): the old values (800 / 15 nits, for bias 1.0 with the physical camera) times Descent::Tune::Carrier::InteriorExposureFactor. If the exposure changes, scale EMISSIVE_NITS, AMBIENT_NITS and LampBrightnessUnit (DescentTune_Carrier.h) by the same factor and re-run with -Force. Safe to re-run: graphs and instances are rebuilt every time (cheap). """ import unreal GAME_DIR = "/Game/Carrier" MAT_DIR = GAME_DIR + "/Materials" # --- calibration knobs (see the docstring) --------------------------------------------------------------------------- EMISSIVE_NITS = 1.36 # an emissive "1.0" (lamp strip): 800 * 0.0017 AMBIENT_NITS = 0.0255 # dry ambient term: radiance of an unlit interior surface = albedo * this (15 * 0.0017) AMBIENT_COLOR = (0.95, 0.90, 0.82) # The moon pool's water. The master's value was 300, for the old scenario exposure; the integration pass scaled the two numbers above by # 0.0017 and missed this one, so the pool was 588 times too bright: a pure white rectangle (fix list after M3). 300 * 0.0017 = 0.51: # dark water (0, 0.026, 0.036) with teal ripples up to (0.04, 0.15, 0.18). POOL_NITS = 0.51 def srgb(hexv): out = [] for shift in (16, 8, 0): c = ((hexv >> shift) & 0xFF) / 255.0 out.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4) return tuple(out) # name: dict(base=hex or None, metal, rough, emis=(r,g,b) or None, emis_k, ambient (multiplier of AMBIENT_NITS), panel (m, 0=off), hazard) SURFACES = { "Wall": dict(base=0x6a7480, metal=0.30, rough=0.65, ambient=1.0, panel=2.5), "Wall2": dict(base=0x4a525c, metal=0.50, rough=0.55, ambient=1.0, panel=1.25), "Floor": dict(base=0x585f68, metal=0.40, rough=0.70, ambient=1.0, panel=1.0, linedark=0.6), "Ceiling": dict(base=0x3a4048, metal=0.30, rough=0.80, ambient=1.0, panel=2.5), "Hazard": dict(base=0xc98a1a, metal=0.00, rough=0.60, ambient=1.0, hazard=1.0), "Wood": dict(base=0x8a6a4e, metal=0.00, rough=0.80, ambient=1.0), "Fabric": dict(base=0x4e6478, metal=0.00, rough=0.95, ambient=1.0), "White": dict(base=0xc8ccd0, metal=0.00, rough=0.50, ambient=1.0), "Rust": dict(base=0x7a4a34, metal=0.35, rough=0.85, ambient=1.0), "Olive": dict(base=0x55603f, metal=0.20, rough=0.80, ambient=1.0), "Hull": dict(base=0x3a4046, metal=0.55, rough=0.62, ambient=0.03, panel=4.0, linedark=0.6), "DarkMetal": dict(base=0x23282d, metal=0.65, rough=0.55, ambient=0.03), "RustMetal": dict(base=0x4a3a30, metal=0.40, rough=0.70, ambient=0.03), "Shaft": dict(base=0x5c666f, metal=0.50, rough=0.60, ambient=1.0, panel=2.0), # emissive: lamps, screens, nav lights (strength is a multiple of EMISSIVE_NITS) "LampWhite": dict(emis=(1.6, 1.5, 1.3), k=1.0), "LampCyan": dict(emis=(0.5, 1.6, 1.8), k=1.0), "LampAmber": dict(emis=(2.2, 1.3, 0.5), k=0.6), "LampRed": dict(emis=(1.6, 0.25, 0.15), k=1.0), "ScreenCyan": dict(emis=(0.25, 0.9, 1.1), k=0.5), "ScreenTeal": dict(emis=(0.05, 0.25, 0.30), k=0.5), "ScreenAmber": dict(emis=(0.9, 0.6, 0.2), k=0.5), "Core": dict(emis=(0.3, 1.2, 1.6), k=0.8), "Furnace": dict(emis=(3.0, 1.1, 0.3), k=0.8), "NavRed": dict(emis=(3.0, 0.2, 0.1), k=1.0), "NavGreen": dict(emis=(0.2, 3.0, 0.4), k=1.0), "NavWhite": dict(emis=(3.0, 3.0, 3.0), k=1.0), "WindowGlow": dict(emis=(0.35, 0.28, 0.16), k=0.15), } def vec_param(ctx, mat, name, default, x, y): n = ctx.mel.create_material_expression(mat, unreal.MaterialExpressionVectorParameter, x, y) n.set_editor_property("parameter_name", name) n.set_editor_property("default_value", unreal.LinearColor(default[0], default[1], default[2], 1.0)) return n def scalar_param(ctx, mat, name, default, x, y): n = ctx.mel.create_material_expression(mat, unreal.MaterialExpressionScalarParameter, x, y) n.set_editor_property("parameter_name", name) n.set_editor_property("default_value", float(default)) return n def custom_node(ctx, mat, code, input_names, x, y): """A Custom HLSL node with float3 output. Returns the node; connect inputs by name afterwards.""" n = ctx.mel.create_material_expression(mat, unreal.MaterialExpressionCustom, x, y) n.set_editor_property("code", code) n.set_editor_property("output_type", unreal.CustomMaterialOutputType.CMOT_FLOAT3) inputs = [] for nm in input_names: ci = unreal.CustomInput() ci.set_editor_property("input_name", nm) inputs.append(ci) n.set_editor_property("inputs", inputs) return n PANEL_HLSL = """ float2 uv = UV; float ps = max(PanelSize, 0.05); float2 f = frac(uv / ps); float2 d = min(f, 1.0 - f) * ps; float seam = (1.0 - smoothstep(0.012, 0.03, min(d.x, d.y))) * step(0.01, PanelSize); float stripe = step(0.5, frac((uv.x + uv.y) * 2.0)); float3 c = lerp(Base, float3(0.012, 0.012, 0.014), stripe * saturate(Hazard)); return c * (1.0 - LineDark * seam); """ POOL_HLSL = """ float2 p = UV * 0.8; float r = sin(p.x * 3.0 + T * 1.1 + sin(p.y * 2.0 + T) * 1.5) * sin(p.y * 2.6 - T * 0.9 + sin(p.x * 1.7) * 1.2); float k = smoothstep(0.3, 1.0, r); return lerp(float3(0.0, 0.05, 0.07), float3(0.08, 0.3, 0.35), k) * Nits; """ def build_surface_master(ctx): MEL = ctx.mel mat = ctx.get_or_create(MAT_DIR, "M_CarrierSurface", unreal.Material, unreal.MaterialFactoryNew()) MEL.delete_all_material_expressions(mat) mat.set_editor_property("blend_mode", unreal.BlendMode.BLEND_OPAQUE) mat.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_DEFAULT_LIT) mat.set_editor_property("two_sided", False) uv = MEL.create_material_expression(mat, unreal.MaterialExpressionTextureCoordinate, -1200, -400) base = vec_param(ctx, mat, "BaseColor", (0.3, 0.3, 0.3), -1200, -250) metal = scalar_param(ctx, mat, "Metallic", 0.0, -1200, -100) rough = scalar_param(ctx, mat, "Roughness", 0.6, -1200, 0) emis_c = vec_param(ctx, mat, "EmissiveColor", (0, 0, 0), -1200, 100) emis_k = scalar_param(ctx, mat, "EmissiveStrength", 0.0, -1200, 250) amb_n = scalar_param(ctx, mat, "AmbientNits", AMBIENT_NITS, -1200, 350) panel = scalar_param(ctx, mat, "PanelSize", 0.0, -1200, 450) line_dark = scalar_param(ctx, mat, "LineDark", 0.5, -1200, 550) hazard = scalar_param(ctx, mat, "Hazard", 0.0, -1200, 650) panels = custom_node(ctx, mat, PANEL_HLSL, ["UV", "PanelSize", "LineDark", "Hazard", "Base"], -800, -200) MEL.connect_material_expressions(uv, "", panels, "UV") MEL.connect_material_expressions(panel, "", panels, "PanelSize") MEL.connect_material_expressions(line_dark, "", panels, "LineDark") MEL.connect_material_expressions(hazard, "", panels, "Hazard") MEL.connect_material_expressions(base, "", panels, "Base") amb_col = MEL.create_material_expression(mat, unreal.MaterialExpressionConstant3Vector, -800, 300) amb_col.set_editor_property("constant", unreal.LinearColor(AMBIENT_COLOR[0], AMBIENT_COLOR[1], AMBIENT_COLOR[2], 1.0)) m1 = MEL.create_material_expression(mat, unreal.MaterialExpressionMultiply, -560, 200) MEL.connect_material_expressions(panels, "", m1, "A") MEL.connect_material_expressions(amb_col, "", m1, "B") m2 = MEL.create_material_expression(mat, unreal.MaterialExpressionMultiply, -380, 250) MEL.connect_material_expressions(m1, "", m2, "A") MEL.connect_material_expressions(amb_n, "", m2, "B") e1 = MEL.create_material_expression(mat, unreal.MaterialExpressionMultiply, -560, 100) MEL.connect_material_expressions(emis_c, "", e1, "A") MEL.connect_material_expressions(emis_k, "", e1, "B") add = MEL.create_material_expression(mat, unreal.MaterialExpressionAdd, -200, 180) MEL.connect_material_expressions(e1, "", add, "A") MEL.connect_material_expressions(m2, "", add, "B") MEL.connect_material_property(panels, "", unreal.MaterialProperty.MP_BASE_COLOR) MEL.connect_material_property(metal, "", unreal.MaterialProperty.MP_METALLIC) MEL.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) MEL.connect_material_property(add, "", unreal.MaterialProperty.MP_EMISSIVE_COLOR) MEL.recompile_material(mat) ctx.eal.save_loaded_asset(mat) return mat def build_pool_master(ctx): MEL = ctx.mel mat = ctx.get_or_create(MAT_DIR, "M_CarrierPool", unreal.Material, unreal.MaterialFactoryNew()) MEL.delete_all_material_expressions(mat) mat.set_editor_property("blend_mode", unreal.BlendMode.BLEND_TRANSLUCENT) mat.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT) mat.set_editor_property("two_sided", True) uv = MEL.create_material_expression(mat, unreal.MaterialExpressionTextureCoordinate, -700, 0) t = MEL.create_material_expression(mat, unreal.MaterialExpressionTime, -700, 150) nits = scalar_param(ctx, mat, "Nits", POOL_NITS, -700, 300) opac = scalar_param(ctx, mat, "Opacity", 0.86, -700, 450) ripple = custom_node(ctx, mat, POOL_HLSL, ["UV", "T", "Nits"], -400, 100) MEL.connect_material_expressions(uv, "", ripple, "UV") MEL.connect_material_expressions(t, "", ripple, "T") MEL.connect_material_expressions(nits, "", ripple, "Nits") MEL.connect_material_property(ripple, "", unreal.MaterialProperty.MP_EMISSIVE_COLOR) MEL.connect_material_property(opac, "", unreal.MaterialProperty.MP_OPACITY) MEL.recompile_material(mat) ctx.eal.save_loaded_asset(mat) return mat def run(ctx): MEL, EAL = ctx.mel, ctx.eal for d in (GAME_DIR, MAT_DIR): EAL.make_directory(d) surface = build_surface_master(ctx) pool = build_pool_master(ctx) for name, s in SURFACES.items(): mi = ctx.get_or_create(MAT_DIR, "MI_Carrier_" + name, unreal.MaterialInstanceConstant, unreal.MaterialInstanceConstantFactoryNew()) mi.set_editor_property("parent", surface) if "emis" in s: e = s["emis"] MEL.set_material_instance_vector_parameter_value(mi, "BaseColor", unreal.LinearColor(0.01, 0.01, 0.01, 1.0)) MEL.set_material_instance_vector_parameter_value(mi, "EmissiveColor", unreal.LinearColor(e[0], e[1], e[2], 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "EmissiveStrength", EMISSIVE_NITS * s.get("k", 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "AmbientNits", 0.0) MEL.set_material_instance_scalar_parameter_value(mi, "Roughness", 0.5) else: b = srgb(s["base"]) MEL.set_material_instance_vector_parameter_value(mi, "BaseColor", unreal.LinearColor(b[0], b[1], b[2], 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "Metallic", float(s["metal"])) MEL.set_material_instance_scalar_parameter_value(mi, "Roughness", float(s["rough"])) MEL.set_material_instance_scalar_parameter_value(mi, "AmbientNits", AMBIENT_NITS * s.get("ambient", 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "PanelSize", float(s.get("panel", 0.0))) MEL.set_material_instance_scalar_parameter_value(mi, "LineDark", float(s.get("linedark", 0.5))) MEL.set_material_instance_scalar_parameter_value(mi, "Hazard", float(s.get("hazard", 0.0))) MEL.update_material_instance(mi) EAL.save_loaded_asset(mi) mi = ctx.get_or_create(MAT_DIR, "MI_Carrier_PoolWater", unreal.MaterialInstanceConstant, unreal.MaterialInstanceConstantFactoryNew()) mi.set_editor_property("parent", pool) MEL.set_material_instance_scalar_parameter_value(mi, "Nits", POOL_NITS) MEL.update_material_instance(mi) EAL.save_loaded_asset(mi) ctx.log("carrier materials: M_CarrierSurface, M_CarrierPool and %d instances" % (len(SURFACES) + 1))