Two separate causes behind "too glossy with a thin white outline": 1. The model carries its OWN cel line-work as extra, UNTEXTURED surfaces — an inverted-hull outline shell plus eye-line/highlight cards (Taila names them FullBlack / EyesFullBlack / EyesInvL / EyesHL). They are authored to read as flat black, but the glTF import hands them a default near-white albedo, and apply_toon_recursive then LIT them. That is the thin white rim: a pale, toon-shaded outline shell tracing every hair strand. They now render unshaded flat ink (flat white for the "HL" highlight card). 2. The skin textures already have cel shading painted in. Stacking our hard 3-tone break on top produced a bright stripe that slid across the hair as the camera moved — the "gloss". Characters now get a soft terminator and an almost-invisible second step, so the painted shading carries the form. Both live in a new LevelMaterials.apply_character_look(), called only for imported character GLBs, so props and level geometry keep exactly the crisp banding they were calibrated with. The one shared change is that the toon shader's second-step darkness is now the mid_tone uniform, defaulting to the 0.82 that was hardcoded — no change for existing callers. Character shadow tint (0.64, 0.56, 0.72) is measured off the Sketchfab reference render rather than guessed: sampling the hair there, shadow/midtone lands near (0.63, 0.53, 0.70). Green drops hardest, which is what keeps copper hair copper instead of washing it to brown. debug/character_look_capture.gd renders the raw import beside our treatment under matched lighting — the comparison this was tuned against. Co-Authored-By: Claude Opus 4.8 <[email protected]>
91 lines
2.5 KiB
GDScript
91 lines
2.5 KiB
GDScript
extends SceneTree
|
|
|
|
## Dev tool: a skin's RAW import beside the same model with the game's character
|
|
## treatment, under identical lighting — the reference shot for tuning
|
|
## LevelMaterials.apply_character_look() against the model's source render.
|
|
## godot --path . --windowed --resolution 1280x720 \
|
|
## -s res://debug/character_look_capture.gd -- <out_dir> [res-path-to-glb]
|
|
## Saves look_head.png and look_body.png (left = raw import, right = ours).
|
|
|
|
const DEFAULT_GLB := "res://assets/characters/skins/taila.glb"
|
|
|
|
var _frames := 0
|
|
var _out_dir := "."
|
|
var _glb_path := DEFAULT_GLB
|
|
var _cam: Camera3D = null
|
|
|
|
|
|
func _initialize() -> void:
|
|
var args := OS.get_cmdline_user_args()
|
|
if args.size() > 0:
|
|
_out_dir = args[0]
|
|
if args.size() > 1:
|
|
_glb_path = args[1]
|
|
|
|
var scene := Node3D.new()
|
|
root.add_child(scene)
|
|
current_scene = scene
|
|
|
|
# Neutral studio light, matched to the game's tonemap so colours compare.
|
|
var env := WorldEnvironment.new()
|
|
var e := Environment.new()
|
|
e.background_mode = Environment.BG_COLOR
|
|
e.background_color = Color(0.30, 0.32, 0.38)
|
|
e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
|
e.ambient_light_color = Color(0.85, 0.87, 0.95)
|
|
e.ambient_light_energy = 1.1
|
|
e.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
|
e.tonemap_white = 2.4
|
|
env.environment = e
|
|
scene.add_child(env)
|
|
|
|
var sun := DirectionalLight3D.new()
|
|
sun.rotation_degrees = Vector3(-38, 28, 0)
|
|
sun.light_energy = 2.0
|
|
scene.add_child(sun)
|
|
|
|
var raw := GLBLoader.load(_glb_path)
|
|
if not raw:
|
|
printerr("character_look_capture: could not load ", _glb_path)
|
|
quit(1)
|
|
return
|
|
raw.position = Vector3(-0.45, 0, 0)
|
|
scene.add_child(raw)
|
|
|
|
var ours := GLBLoader.load(_glb_path)
|
|
ours.position = Vector3(0.45, 0, 0)
|
|
scene.add_child(ours)
|
|
LevelMaterials.apply_toon_recursive(ours)
|
|
LevelMaterials.apply_character_look(ours)
|
|
|
|
_cam = Camera3D.new()
|
|
scene.add_child(_cam)
|
|
|
|
|
|
func _process(_delta: float) -> bool:
|
|
_frames += 1
|
|
if _frames < 30:
|
|
return false
|
|
if _frames == 30:
|
|
_look(Vector3(0, 1.42, 1.05), Vector3(0, 1.38, 0))
|
|
return false
|
|
if _frames == 32:
|
|
_snap("look_head")
|
|
_look(Vector3(0, 1.15, 2.2), Vector3(0, 0.95, 0))
|
|
return false
|
|
if _frames == 34:
|
|
_snap("look_body")
|
|
return true
|
|
return false
|
|
|
|
|
|
func _look(pos: Vector3, at: Vector3) -> void:
|
|
_cam.global_position = pos
|
|
_cam.look_at(at, Vector3.UP)
|
|
|
|
|
|
func _snap(tag: String) -> void:
|
|
var img := root.get_viewport().get_texture().get_image()
|
|
img.save_png(_out_dir + "/" + tag + ".png")
|
|
print("character_look_capture: saved ", tag)
|