feat: cel-shaded look — toon shader, ink outlines, stylized environment
- toon.gdshader: banded diffuse with cool-tinted shadows (3 tones), stepped specular, rim light, and an optional world-triplanar albedo path so level geometry keeps the 0.5 m grid with no UVs - toon_outline.gdshader: inverted-hull ink outline, applied via material_overlay so it works on skinned (deforming) meshes - LevelMaterials: tinted() now returns toon grid materials; toonify()/ apply_toon_recursive() convert imported character/prop materials in place - SkinnedPlayerModel: characters get toon shading + outline on load - LevelEnvironment: shared stylized environment for all maps — saturated anime sky, linear tonemap (filmic/ACES crush cel bands), bloom for emissives, saturation+contrast grade, light depth haze; test level and dust2 now use it and the procedural arena (which had NO environment) gets it plus runtime toonification of its baked geometry Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2c7b5a1aca
commit
37c15bed1a
@@ -1,41 +1,93 @@
|
||||
extends Object
|
||||
class_name LevelMaterials
|
||||
|
||||
## Shared prototype-grid materials for code-built levels.
|
||||
## Shared cel-shaded materials for code-built levels and characters.
|
||||
##
|
||||
## Every level builder makes its geometry from flat-colored boxes; this gives
|
||||
## them all the same world-space grid surface (0.5 m cells via triplanar
|
||||
## mapping) so speed and distance stay readable at movement-shooter velocity,
|
||||
## while keeping each builder's color coding as a tint.
|
||||
## Level geometry gets the toon shader with world-space triplanar grid
|
||||
## (0.5 m cells) tinted per surface; characters get the same toon shading
|
||||
## over their own textures via convert_to_toon(), plus an inverted-hull
|
||||
## outline overlay for the inked silhouette.
|
||||
|
||||
const GRID_GRAY := "res://assets/textures/prototype/grid_gray.png"
|
||||
const GRID_DARK := "res://assets/textures/prototype/grid_dark.png"
|
||||
const TOON_SHADER := "res://assets/shaders/toon.gdshader"
|
||||
const OUTLINE_SHADER := "res://assets/shaders/toon_outline.gdshader"
|
||||
|
||||
## One texture tile = 2 m of world, so one grid cell = 0.5 m.
|
||||
const WORLD_UNITS_PER_TILE := 2.0
|
||||
|
||||
static var _cache: Dictionary = {}
|
||||
static var _outline_cache: Dictionary = {}
|
||||
|
||||
|
||||
## A tinted grid material. Cached per (tint, dark) so identical surfaces share
|
||||
## one material (fewer draw-state changes, and edits apply everywhere).
|
||||
static func tinted(tint: Color, dark: bool = false) -> StandardMaterial3D:
|
||||
## A tinted toon grid material for level geometry. Cached per (tint, dark) so
|
||||
## identical surfaces share one material.
|
||||
static func tinted(tint: Color, dark: bool = false) -> Material:
|
||||
var key := "%s|%s" % [tint.to_html(), dark]
|
||||
if _cache.has(key):
|
||||
return _cache[key]
|
||||
var mat := StandardMaterial3D.new()
|
||||
var shader: Shader = load(TOON_SHADER)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
var tex_path := GRID_DARK if dark else GRID_GRAY
|
||||
var tex: Texture2D = load(tex_path) if ResourceLoader.exists(tex_path) else null
|
||||
if tex:
|
||||
mat.albedo_texture = tex
|
||||
# World triplanar: boxes need no UVs and the grid stays world-scaled.
|
||||
mat.uv1_triplanar = true
|
||||
mat.uv1_world_triplanar = true
|
||||
mat.uv1_scale = Vector3.ONE / WORLD_UNITS_PER_TILE
|
||||
mat.set_shader_parameter("albedo_texture", tex)
|
||||
mat.set_shader_parameter("has_texture", true)
|
||||
mat.set_shader_parameter("use_triplanar", true)
|
||||
mat.set_shader_parameter("triplanar_tile", WORLD_UNITS_PER_TILE)
|
||||
else:
|
||||
mat.set_shader_parameter("has_texture", false)
|
||||
# The texture is grayscale ~mid value; multiply by ~2x-brightened tint to
|
||||
# land near the original flat color while keeping the grid contrast.
|
||||
mat.albedo_color = Color(
|
||||
minf(tint.r * 1.9, 1.0), minf(tint.g * 1.9, 1.0), minf(tint.b * 1.9, 1.0))
|
||||
mat.roughness = 0.85
|
||||
mat.set_shader_parameter("albedo_color", Color(
|
||||
minf(tint.r * 1.9, 1.0), minf(tint.g * 1.9, 1.0), minf(tint.b * 1.9, 1.0)))
|
||||
_cache[key] = mat
|
||||
return mat
|
||||
|
||||
|
||||
## Toon version of an arbitrary material (usually a character's imported
|
||||
## StandardMaterial3D): keeps its albedo texture/color, swaps the shading.
|
||||
static func toonify(src: Material) -> Material:
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = load(TOON_SHADER)
|
||||
var tex: Texture2D = null
|
||||
var col := Color.WHITE
|
||||
if src is BaseMaterial3D:
|
||||
tex = src.albedo_texture
|
||||
col = src.albedo_color
|
||||
mat.set_shader_parameter("albedo_texture", tex)
|
||||
mat.set_shader_parameter("has_texture", tex != null)
|
||||
mat.set_shader_parameter("use_triplanar", false)
|
||||
mat.set_shader_parameter("albedo_color", col)
|
||||
# Characters read best with a slightly wider lit band and stronger rim.
|
||||
mat.set_shader_parameter("rim_strength", 0.45)
|
||||
return mat
|
||||
|
||||
|
||||
## Swap every mesh surface under `node` to toon shading and add an
|
||||
## inverted-hull outline overlay. Safe on skinned meshes (material_overlay
|
||||
## re-renders the same deformed mesh).
|
||||
static func apply_toon_recursive(node: Node, outline_width: float = 0.015) -> void:
|
||||
if node is MeshInstance3D:
|
||||
var mi := node as MeshInstance3D
|
||||
var surface_count: int = mi.mesh.get_surface_count() if mi.mesh else 0
|
||||
for s in range(surface_count):
|
||||
var src := mi.get_active_material(s)
|
||||
if src and not (src is ShaderMaterial):
|
||||
mi.set_surface_override_material(s, toonify(src))
|
||||
if outline_width > 0.0:
|
||||
mi.material_overlay = outline(outline_width)
|
||||
for child in node.get_children():
|
||||
apply_toon_recursive(child, outline_width)
|
||||
|
||||
|
||||
static func outline(width: float = 0.015) -> ShaderMaterial:
|
||||
var key := "%.4f" % width
|
||||
if _outline_cache.has(key):
|
||||
return _outline_cache[key]
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = load(OUTLINE_SHADER)
|
||||
mat.set_shader_parameter("outline_width", width)
|
||||
_outline_cache[key] = mat
|
||||
return mat
|
||||
|
||||
Reference in New Issue
Block a user