How to instantiate a shader in GDScript?

bluepangolin55bluepangolin55 Posts: 4Member
edited May 13 in Shaders

I wrote a simple shader which I want to apply to a mesh that I am procedurally generating in GDScript. I tested the shader in Godot in a test scene and it works perfectly. However I don't know how to instantiate the shader in GDScript. I was not able to find any resources about how this is done. The documentation of the Shader class seems incomplete.
I tried loading the shader code from a file and setting it. However as soon as I create a shader object, Godot throws an error:

SHADER ERROR: (null): Expected 'shader_type' at the beginning of shader.
   At: :1.
ERROR: _update_shader: Condition ' err != OK ' is true.
   At: drivers/gles3/rasterizer_storage_gles3.cpp:1698.

What is the correct way of loading a shader from a file at runtime and applying it to a ShaderMaterial without generating these errors?

The code I was trying out is of the following lines:

# read the shader code from a file
var file = File.new()
file.open("res://shaders/terrain_mix.shader", File.READ)
var code = ""
while !file.eof_reached():
    code += file.get_line() + "\n"

# create the shader and shader material
var shader = Shader.new()
shader.set_code(code)

var material = ShaderMaterial.new()
material.set_shader_param("splatmap", ResourceManager.get_texture("splatmap"))
material.set_shader_param("texture1", ResourceManager.get_texture("sand"))
material.set_shader_param("texture2", ResourceManager.get_texture("grass"))
material.shader = shader

After setting the shader code, shader.get_code() returns the shader code correctly. However the problem is, that the error pops up as soon as the shader object instance is being created.
Here's the shader code although I don't think it's relevant, since it works when applied through the Godot editor:

shader_type spatial;

uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
uniform sampler2D texture4;
uniform sampler2D splatmap;


void fragment () {
    vec3 result;
    float mix1 = texture(splatmap, UV).r;
    float mix2 = texture(splatmap, UV).g;
    float mix3 = texture(splatmap, UV).b;
    float mix4 = texture(splatmap, UV).a;
    vec3 color1 = texture(texture1, UV).rgb*mix1;
    vec3 color2 = texture(texture1, UV).rgb*mix2;
    vec3 color3 = texture(texture1, UV).rgb*mix3;
    vec3 color4 = texture(texture1, UV).rgb*mix4;

    result = color1 + color2 + color3 + color4;
    ALBEDO = result;
}

Thanks!

Answers

  • MegalomaniakMegalomaniak Posts: 669Admin

    As a quick thought: do your instance objects have a basic shader for initialization that you override per-instance later?

  • bluepangolin55bluepangolin55 Posts: 4Member
    edited May 13

    Thanks for the quick reply.
    No, I don't think so.
    I create the mesh using SurfaceTool. I first set the material and then add the vertices. This is correctly rendered when using SpatialMaterial. The problem arises when I try to create a shader object.

    I'm really new to Godot, so I might not understand this all completely.

  • MegalomaniakMegalomaniak Posts: 669Admin

    Ah, you are defining constants inside the void fragment()! In GLES you are not supposed to do that. Try this perhaps(not tested just typed it up here quickly):

    shader_type spatial;
    
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform sampler2D texture3;
    uniform sampler2D texture4;
    
    uniform sampler2D splatmap;
    
    vec3 result;
    
    float mix1;
    float mix2;
    float mix3;
    float mix4;
    
    vec3 color1;
    vec3 color2;
    vec3 color3;
    vec3 color4;
    
    void fragment () {
        mix1 = texture(splatmap, UV).r;
        mix2 = texture(splatmap, UV).g;
        mix3 = texture(splatmap, UV).b;
        mix4 = texture(splatmap, UV).a;
        color1 = texture(texture1, UV).rgb*mix1;
        color2 = texture(texture1, UV).rgb*mix2;
        color3 = texture(texture1, UV).rgb*mix3;
        color4 = texture(texture1, UV).rgb*mix4;
        result = color1 + color2 + color3 + color4;
        ALBEDO = result;
    }
    

    Mind you this could be optimized. Starting with using mix or smoothstep for splatting perhaps.

  • bluepangolin55bluepangolin55 Posts: 4Member
    edited May 13 Answer ✓

    I was able to solve my problem indirectly by creating a node in the scene tree that contains a MeshInstance with the shader loaded.
    Next I load this shader in my resource manager singleton:

    var shaders = preload("res://Shaders.tscn")
    
    func _init():
        material1 = shaders.instance().get_node("shader1_instance").get_surface_material(0)
    

    and later apply it to the mesh:

    surface_tool.set_material(ResourceManager.material1)
    

    It works perfectly :)
    Still I'm wondering what would be the preferred way to do it.

  • bluepangolin55bluepangolin55 Posts: 4Member

    @Megalomaniak said:
    Ah, you are defining constants inside the void fragment()! In GLES you are not supposed to do that. Try this perhaps(not tested just typed it up here quickly):

    shader_type spatial;
    
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform sampler2D texture3;
    uniform sampler2D texture4;
    
    uniform sampler2D splatmap;
    
    vec3 result;
    
    float mix1;
    float mix2;
    float mix3;
    float mix4;
    
    vec3 color1;
    vec3 color2;
    vec3 color3;
    vec3 color4;
    
    void fragment () {
        mix1 = texture(splatmap, UV).r;
        mix2 = texture(splatmap, UV).g;
        mix3 = texture(splatmap, UV).b;
        mix4 = texture(splatmap, UV).a;
        color1 = texture(texture1, UV).rgb*mix1;
        color2 = texture(texture1, UV).rgb*mix2;
        color3 = texture(texture1, UV).rgb*mix3;
        color4 = texture(texture1, UV).rgb*mix4;
        result = color1 + color2 + color3 + color4;
        ALBEDO = result;
    }
    

    Mind you this could be optimized. Starting with using mix or smoothstep for splatting perhaps.

    When I define any variable outside of fragment() I get:
    error(9): Expected '(' after identifier
    Thanks for the tips on how to optimize :)

    As I mentioned, I don't think there is anything substantially wrong with the shader as it works otherwise. I just don't know how to load it from GDScript. I can use the shader in the Godot editor and later access it (see my previous post) but I can't load it directly from GDScript.

  • MegalomaniakMegalomaniak Posts: 669Admin
    edited May 13

    Now that I think about it - perhaps it was only the uniforms and varyings that had to be defined outside(and at the very beginning) the voids. Been a while since I have written a shader for godot - might have gotten my wires crossed, currently busy writing a new theme for the future forum upgrade. :tongue:

    Also I'm starting to remember someone else having had a somewhat similar problem. Here:
    https://godotdevelopers.org/forum/discussion/comment/21655/#Comment_21655

    Uses SpatialShader IIRC, but also needed to do something similar to what you did.

    Perhaps the materials(shaders) need to get compiled by the gpu before they can be instanced/applied to generated meshes or something.

  • emo10001emo10001 Posts: 56Member

    Ok, been working on trying to get a splatmat on a terrain all day. Been trying this video

    and using this post. But I'm getting all kinds of errors.

    Not only that, but in the video example he gets an area to assign his textures (8:40 in).

    Have shaders changed in 3.0.2 to where this post, and this video are no longer relevant?

    Even copying/pasting the code from this thread gives me errors.

    My assumption is that you can no longer just say "uniform texture grass", but have to declare a type of sampler2D? (WITH a capitol D by the way).

    I even change my vec3 declarations to uniform vec3, and then those errors go away, but then my "results = grasscolor+dirtcolor+rockcolor;" throws errors....

    Ugh...pretty frustrated right now. :grimace:

    -emo

  • emo10001emo10001 Posts: 56Member

    Ok, found this thread LOL

    https://godotdevelopers.org/forum/discussion/comment/21589/#Comment_21589

    Now I have no errors...but just a blue plane with no textures at all...

  • MegalomaniakMegalomaniak Posts: 669Admin
    edited May 15

    Need to load the textures from the uniforms respective properties in the inspector(step one inspector level back). :) Don't forget to save your shader first!

  • emo10001emo10001 Posts: 56Member

    I think that's what I've done here right? On the right side where they are showing up? And I hit save up there on the top of the inspector.

  • emo10001emo10001 Posts: 56Member

    I think it's something with my obj file. Was reading this post here.

    https://godotdevelopers.org/forum/discussion/18950/godot-3-terrain-with-splatmap-example-not-working-on-windows

    I re-exported my terrain as Collada and now it seems to be working...however it's really washed out.

    I'll keep grinding, but any thoughts are appreciated :smile: !

    -emo

  • MegalomaniakMegalomaniak Posts: 669Admin
    edited May 15

    Ah, yes, you will want to output something into the shaders Roughness value. The ShaderMaterial is basically overriding values of the SpatialMaterial and right now I guess your materials roughness is 0. So it's super reflective.

    Also does your material do something with the metallic/specular component(s)?

    It would be easiest if you included your shader code. :)

  • emo10001emo10001 Posts: 56Member
    edited May 15

    You bet :smile: The shader code is:

    shader_type spatial;
    uniform float grassres = 1;
    uniform float rockres = 1;
    uniform float dirtres = 1;
    uniform sampler2D grass;
    uniform sampler2D rock;
    uniform sampler2D dirt;
    uniform sampler2D splatmap;
    void fragment(){
     float grassval = texture(splatmap, UV).g;
     float rockval = texture(splatmap, UV).b;
     float dirtval = texture(splatmap, UV).r;
     vec3 grasstex = texture(grass, UV*grassres).rgb * grassval;
     vec3 rocktex = texture(rock, UV*rockres).rgb * rockval;
     vec3 dirttex = texture(dirt, UV*dirtres).rgb * dirtval;
     vec3 result = grasstex+rocktex+dirttex;
     ALBEDO = result;
     }
    

    Is the SpatialMaterial roughness something I have to code for? Also the material shouldn't be doing anything with metallic or specular components (that I'm aware of any)

    -emo

  • MegalomaniakMegalomaniak Posts: 669Admin
    edited May 15

    Hm, in version 3.0.2 it's working fine for me, but then I am applying it to a Sphere primitive MeshInstance. Yep, added ROUGHNESS = float(0.0); to confirm and it turned reflective then. So works as it should on my end.

    edit: @bluepangolin55 If you want me to split @emo10001 's posts in a separate thread just let me know.

  • emo10001emo10001 Posts: 56Member
    edited May 15

    @Megalomaniak said:
    Hm, in version 3.0.2 it's working fine for me, but then I am applying it to a Sphere primitive MeshInstance. Yep, added ROUGHNESS = float(0.0); to confirm and it turned reflective then. So works as it should on my end.

    Ok, I can see what Roughness can do (I put this in the shader void fragment correct?). But I'm not sure this is the issue. The terrain has this washed out blue tint to everything. Is this the sky? (seems like a watched a tutorial where they talked about the default sky causing things to look funny)

    It looks the same if I put ROUGHNESS at 1 or comment it out. Washed out blue tint.

    -emo

  • MegalomaniakMegalomaniak Posts: 669Admin

    @emo10001 said:
    Ok, I can see what Roughness can do (I put this in the shader void fragment correct?).

    Yep.

    @emo10001 said:
    But I'm not sure this is the issue. The terrain has this washed out blue tint to everything. Is this the sky? (seems like a watched a tutorial where they talked about the default sky causing things to look funny)

    I bet it's to do with the colorspaces of the textures you are using(they are likely sRGB while godot's rendering pipleine is HDR and works in linear space!)

    So see the edit I added to my last post in the thread you linked to earlier and the post above it for an example of using hints:
    https://godotdevelopers.org/forum/discussion/comment/22241/#Comment_22241

  • emo10001emo10001 Posts: 56Member

    Ha...thanks for the reminder. I actually had the hints in there at one point...I must have copy/pasted some older code I had before I put it in (it's actually in my 2nd screenshot LOL).

    That certainly helps! Thanks a ton @Megalomaniak !

    -emo

Sign In or Register to comment.