Feat/outline thickness and tp weapon hold #22
@@ -15,6 +15,11 @@ uniform float triplanar_tile = 2.0; // world units per texture tile
|
|||||||
uniform float band_edge : hint_range(-1.0, 1.0) = 0.05; // NdotL where light band starts
|
uniform float band_edge : hint_range(-1.0, 1.0) = 0.05; // NdotL where light band starts
|
||||||
uniform float band_softness : hint_range(0.001, 0.5) = 0.04;
|
uniform float band_softness : hint_range(0.001, 0.5) = 0.04;
|
||||||
uniform float mid_band_edge : hint_range(-1.0, 1.0) = 0.55; // second, brighter band
|
uniform float mid_band_edge : hint_range(-1.0, 1.0) = 0.55; // second, brighter band
|
||||||
|
// How dark the mid band sits below full light. Near 1.0 the second step almost
|
||||||
|
// disappears, which is what textures that already carry painted cel shading
|
||||||
|
// (imported anime characters) need — a hard step on top of painted shading
|
||||||
|
// reads as a glossy stripe sweeping across the hair.
|
||||||
|
uniform float mid_tone : hint_range(0.0, 1.0) = 0.82;
|
||||||
uniform vec4 shadow_color : source_color = vec4(0.62, 0.65, 0.78, 1.0); // cool shadow tint
|
uniform vec4 shadow_color : source_color = vec4(0.62, 0.65, 0.78, 1.0); // cool shadow tint
|
||||||
// Matte-anime defaults: zero specular (any stepped glint reads as shine
|
// Matte-anime defaults: zero specular (any stepped glint reads as shine
|
||||||
// sweeping across hair/cloth when the camera moves), whisper of rim.
|
// sweeping across hair/cloth when the camera moves), whisper of rim.
|
||||||
@@ -69,8 +74,8 @@ void light() {
|
|||||||
float lit = ndotl * ATTENUATION;
|
float lit = ndotl * ATTENUATION;
|
||||||
float band = smoothstep(band_edge - band_softness, band_edge + band_softness, lit);
|
float band = smoothstep(band_edge - band_softness, band_edge + band_softness, lit);
|
||||||
float mid = smoothstep(mid_band_edge - band_softness, mid_band_edge + band_softness, lit);
|
float mid = smoothstep(mid_band_edge - band_softness, mid_band_edge + band_softness, lit);
|
||||||
// 3 tones: shadow tint -> base band (0.82) -> full light.
|
// 3 tones: shadow tint -> base band (mid_tone) -> full light.
|
||||||
float tone = mix(0.82, 1.0, mid);
|
float tone = mix(mid_tone, 1.0, mid);
|
||||||
vec3 shade = mix(shadow_color.rgb, vec3(tone), band);
|
vec3 shade = mix(shadow_color.rgb, vec3(tone), band);
|
||||||
DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR / PI * shade;
|
DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR / PI * shade;
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ func load_model(path: String) -> void:
|
|||||||
|
|
||||||
# Cel-shaded look: toon shading over the imported textures + ink outline.
|
# Cel-shaded look: toon shading over the imported textures + ink outline.
|
||||||
LevelMaterials.apply_toon_recursive(scene)
|
LevelMaterials.apply_toon_recursive(scene)
|
||||||
|
# ...then the character-only pass: flat line-work + softer banding (see the
|
||||||
|
# function — toon-lighting the model's own outline shell is what put a white
|
||||||
|
# rim on every hair strand, and re-banding already-shaded textures read as
|
||||||
|
# gloss).
|
||||||
|
LevelMaterials.apply_character_look(scene)
|
||||||
if animation_player:
|
if animation_player:
|
||||||
_index_animations()
|
_index_animations()
|
||||||
_setup_anim_tree(scene)
|
_setup_anim_tree(scene)
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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)
|
||||||
@@ -87,7 +87,7 @@ static func toonify(src: Material) -> Material:
|
|||||||
mat.set_shader_parameter("albedo_color", col)
|
mat.set_shader_parameter("albedo_color", col)
|
||||||
# Fully matte characters: NO specular (even a 2% stepped glint reads as
|
# Fully matte characters: NO specular (even a 2% stepped glint reads as
|
||||||
# shine sweeping across hair when the camera moves) and only a whisper
|
# shine sweeping across hair when the camera moves) and only a whisper
|
||||||
# of rim for silhouette separation. Cel banding carries all the shape.
|
# of rim for silhouette separation.
|
||||||
mat.set_shader_parameter("rim_strength", 0.05)
|
mat.set_shader_parameter("rim_strength", 0.05)
|
||||||
mat.set_shader_parameter("rim_width", 0.28)
|
mat.set_shader_parameter("rim_width", 0.28)
|
||||||
mat.set_shader_parameter("specular_strength", 0.0)
|
mat.set_shader_parameter("specular_strength", 0.0)
|
||||||
@@ -95,6 +95,59 @@ static func toonify(src: Material) -> Material:
|
|||||||
return mat
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
## Ink used for the line-work an imported character carries in its own mesh.
|
||||||
|
const CHARACTER_INK := Color(0.07, 0.06, 0.09)
|
||||||
|
|
||||||
|
|
||||||
|
## Second pass for IMPORTED CHARACTER models (the anime GLB skins), run right
|
||||||
|
## after apply_toon_recursive. Two things those models need that props don't:
|
||||||
|
##
|
||||||
|
## 1. Their line-work is part of the mesh. Anime GLBs ship an inverted-hull
|
||||||
|
## outline shell plus eye-line/eye-highlight cards as extra, UNTEXTURED
|
||||||
|
## surfaces (Taila names them FullBlack / EyesFullBlack / EyesInvL / EyesHL).
|
||||||
|
## They are authored to read as flat black — or flat white for a highlight —
|
||||||
|
## and the glTF import hands them a default near-white albedo. Toon-LIGHTING
|
||||||
|
## that turns every black shell pale: that was the thin white rim tracing
|
||||||
|
## every hair strand.
|
||||||
|
##
|
||||||
|
## 2. Their textures are ALREADY painted with cel shading. Stacking the hard
|
||||||
|
## 3-tone break on top read as gloss — a bright stripe sliding across the
|
||||||
|
## hair as the camera moved. Characters get a soft terminator and an
|
||||||
|
## almost-invisible second step so the painted shading carries the form.
|
||||||
|
##
|
||||||
|
## Props and level geometry keep the crisp banding they were calibrated with.
|
||||||
|
static func apply_character_look(root: Node) -> void:
|
||||||
|
for mi in root.find_children("*", "MeshInstance3D", true, false):
|
||||||
|
if not mi.mesh:
|
||||||
|
continue
|
||||||
|
for s in range(mi.mesh.get_surface_count()):
|
||||||
|
var src: BaseMaterial3D = mi.mesh.surface_get_material(s) as BaseMaterial3D
|
||||||
|
if src == null:
|
||||||
|
continue
|
||||||
|
if src.albedo_texture == null:
|
||||||
|
# Untextured surface on a character = the model's own line-work.
|
||||||
|
var flat := StandardMaterial3D.new()
|
||||||
|
flat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
flat.cull_mode = src.cull_mode
|
||||||
|
# "HL" marks a highlight card (the glint in the pupil) — that
|
||||||
|
# one really is meant to be white.
|
||||||
|
var is_highlight: bool = src.resource_name.to_lower().contains("hl")
|
||||||
|
flat.albedo_color = Color.WHITE if is_highlight else CHARACTER_INK
|
||||||
|
mi.set_surface_override_material(s, flat)
|
||||||
|
continue
|
||||||
|
var toon: ShaderMaterial = mi.get_surface_override_material(s) as ShaderMaterial
|
||||||
|
if toon == null:
|
||||||
|
continue
|
||||||
|
toon.set_shader_parameter("band_softness", 0.16)
|
||||||
|
toon.set_shader_parameter("mid_tone", 0.92)
|
||||||
|
# Shadow tint measured off the Sketchfab reference render: sampling
|
||||||
|
# Taila's hair there, shadow/midtone lands near (0.63, 0.53, 0.70).
|
||||||
|
# Green drops hardest, and that is what keeps copper hair COPPER in
|
||||||
|
# shadow — the level default (0.62, 0.65, 0.78) lifts green above
|
||||||
|
# red and washes ginger toward a dull brown.
|
||||||
|
toon.set_shader_parameter("shadow_color", Color(0.64, 0.56, 0.72))
|
||||||
|
|
||||||
|
|
||||||
## Swap every mesh surface under `node` to toon shading and add an
|
## Swap every mesh surface under `node` to toon shading and add an
|
||||||
## inverted-hull outline overlay. Safe on skinned meshes (material_overlay
|
## inverted-hull outline overlay. Safe on skinned meshes (material_overlay
|
||||||
## re-renders the same deformed mesh).
|
## re-renders the same deformed mesh).
|
||||||
|
|||||||
Reference in New Issue
Block a user