Files
Brachistochrone/addons/Assets/Shader/ShaderSpace.gd
T
scorpion21211-cmd 965f0d7584 Initial commit
2026-10-03 23:29:41 -04:00

136 lines
4.7 KiB
GDScript

@tool
extends Node3D
class_name ShaderSpace
## Node to override any materials with a ShaderMaterial -- useful for applying shaders to lots of StandardMaterials
##
## Any child (recursively) of this node will have all their StandardMaterial3Ds converted to ShaderMaterial code, allowing
## injection of custom shader code to a wide variety of ShaderMaterials.
## Includes 'injected_vars' and 'injected_vertex' as a start, though ultimately code can be modified directly via `shader.code`
##
## If you're looking for viewmodel rendering, use ViewmodelSpace.gd
## @experimental
## Godot's rendering engine will only compile a shader when it appears in the camera for the first time which can cause stutters.
## This flag will force Godot to render all shaders when this space enters the scene.
@export var precompile_on_enter : bool = true
var _bake : bool = false
## This will 'bake' the custom materials this script generates -- otherwise the materials are generated when this node enters the scene.
## WARNING: Please note any changes you make to the *baked* versions of the Materials will not stay if/when you revert back to unbaked.
## It is recommended to make permanent changes only on unbaked versions.
@export var bake : bool = false:
get:
return _bake
set(value):
if Engine.is_editor_hint():
_bake = value
convert_surfaces()
if not connected_signal and _bake:
child_entered_tree.connect(new_child)
connected_signal = true
elif connected_signal and not _bake:
child_entered_tree.disconnect(new_child)
connected_signal = false
var connected_signal = false
@export var tracked_nodes : Array[GeometryInstance3D]
var injected_vars : String
var injected_vertex : String
func _enter_tree():
# Triggers the on-run compilation of materials
if not _bake and not Engine.is_editor_hint():
if not connected_signal and _bake:
child_entered_tree.connect(new_child)
connected_signal = true
convert_surfaces()
func new_child(node : Node):
# bake assets freshly added
if _bake == Engine.is_editor_hint():
convert_children.call_deferred(node)
func set_instance_shader_parameter(parameter_name : String, state : bool):
for node in tracked_nodes:
node.set_instance_shader_parameter(parameter_name,state)
func convert_mesh(mesh : Mesh):
for surface in range(mesh.get_surface_count()):
mesh.surface_set_material(surface,convert_surface(mesh.surface_get_material(surface)))
func convert_geometry(node : GeometryInstance3D):
var mat = node.material_override or node.material_overlay
var overlay = true if node.material_override else false
if mat:
if overlay:
node.material_overlay = convert_surface(mat)
else:
node.material_override = convert_surface(mat)
else:
return null
return node.material_overlay if overlay else node.material_override
func convert_children(node):
for child in node.get_children():
convert_children(child)
var overwrote = false
if node is MeshInstance3D:
for surface in range(node.get_surface_override_material_count()):
var premat = node.get_surface_override_material(surface)
var override = true
if premat == null:
override = false
premat = node.mesh.surface_get_material(surface)
var newmat = convert_surface(premat)
if not newmat:
print("WARNING: No material found for ",node)
else:
var set_material = node.set_surface_override_material if override else node.mesh.surface_set_material
set_material.call(surface,newmat)
if not overwrote:
# set true if *any* surface was successfully overwritten
overwrote = newmat != null
elif node is GeometryInstance3D:
if node.material_override or node.material_overlay:
overwrote = convert_geometry(node) != null
elif node is CSGShape3D:
if node is CSGPrimitive3D:
var newmat = convert_surface(node.material)
node.material = newmat
overwrote = newmat != null
else:
for mesh in node.get_meshes():
if mesh is Transform3D:
continue
convert_mesh(mesh)
overwrote = true
if overwrote:
tracked_nodes.push_back(node)
func convert_surfaces():
tracked_nodes = []
convert_children(self)
## Returns null if given invalid material
func convert_surface(mat : Material):
if not mat:
return
if mat is ConvertedMaterial:
if Engine.is_editor_hint() and not _bake:
# un-baking
return mat.get_original_material()
elif precompile_on_enter and not Engine.is_editor_hint():
ShaderPrecompiler.precompile(get_tree(),mat)
return mat
if not mat is StandardMaterial3D:
print("WARNING: Cannot convert ", mat, " to shader material")
else:
var newmaterial = Material3DConversion.convert_to_shadermat(mat,injected_vars,injected_vertex)
if precompile_on_enter and not Engine.is_editor_hint():
ShaderPrecompiler.precompile(get_tree(),newmaterial)
return newmaterial