I’m making a system that programmatically generates shaders by editing their code String.
I click on an @export_tool_button, a shader code String is generated. It is then applied to a Material
var code : String = generate_shader_through_magic()
if !(material_override is ShaderMaterial):
material_override = ShaderMaterial.new()
if material_override.shader == null:
material_override.shader = Shader.new()
material_override.shader.code = code
# Idk if that's needed, trying to find out
material_override.emit_changed()
material_override.shader.emit_changed()
I’ve noticed 2 things :
Whenever I generate a new shader like this, the shader editor automatically opens the new shader
When I try to edit my shader through code by clicking the @export_tool_button again, the shader doesn’t get updated if it’s open in the shader editor (may be a bug), I need to close it first and then try to generate the shader code
This means that I constantly get cock-blocked by the shader editor as I’m trying to test my shader because every time between 2 generations I need to manually close the shader in the editor.
It’s useful to be able to read the resulting shader to see what my code outputed and debug, but it’s annoying to have to constantly close the editor that I generally won’t use.
Is there a way not to have the shader editor open every time a shader is created ?
Or
Is there a way to programmatically edit the code of a shader while it’s open in the shader editor ?
@tool
class_name ShaderGenerator
extends MeshInstance3D
@export var color : Color
@export_tool_button("Generate", "Shader") var generate = _generate
func generate_shader_through_magic() -> String:
return "shader_type spatial;
render_mode unshaded;
const vec4 color = vec4"+str(color)+";
void fragment()
{
ALBEDO = color.rgb;
ALPHA = color.a;
}"
func _generate():
var code : String = generate_shader_through_magic()
if !(material_override is ShaderMaterial):
material_override = ShaderMaterial.new()
if material_override.shader == null:
material_override.shader = Shader.new()
material_override.shader.code = code
# Idk if that's needed
material_override.emit_changed()
material_override.shader.emit_changed()
Add this script to a MeshInstance3D. Generate a shader with the button. For some reason you will have to reload the project for the shader to apply*
When you generate the shader, it will be opened in the shader editor. Change the color and generate again, the color won’t change on the model, UNLESS you closed the shader editor first.
*and again if you deleted the “shader” property of your material, basically whenever the Shader.new() gets called it’s a new project reset (but that’s not too bad since you don’t create a new shader often)
In any case, assign the complete material to material_override property, with the shader and its code already assigned to the material object. This will trigger the actual shader compilation and update.
func _generate():
var code: String = generate_shader_through_magic()
var material = ShaderMaterial.new()
material.shader = Shader.new()
material.shader.code = code
material_override = material
You also might want to linearize your color value:
const vec4 color = vec4" + str(color.srgb_to_linear()) + ";
Your solution works so well. Not only does your code not open the shader editor, but even if I manually open the shader editor, I can still update the code with the tool button. thanks.