Using GLSL Shaders on objects in UPBGE

in 2.79, I could apply a shader to an object (this one below is intended to turn a plane red), use setSource() and just look at the result. But in 0.3, its complicated. The shader does not show up. This is my GLSL shader:


float red = 1;
float green = 0;
float blue = 0;
float alpha = 0.5;
uniform float time;
out vec4 fragColor;

void main(void) {
    fragColor = vec4(abs(sin(time)),0,0,1);
}

I’m trying to make the plane (which has a bricks texture) pulsate from red to black via the shader, but I cannot get it to.

The console says this error, but I don’t see anything wrong with the script:

ERROR (gpu.shader): custom FragShader: 
      | 
    1 | #version 330
      | 
      | Error: 'shader' : syntax error: syntax error

The time variable is supposed to be controlled by a Timer property. In the case of the red green blue alpha variables, they are supposed to be for controlling object alpha and colour through multiple game properties. My intent is to apply said shader to only 1 object in the scene, but not the entire scene like a 2D Filter does, like in 2.79, as this gave me a lot of control on what object could or could not have a shader.

Example:

Hook up a GLSL shader python script that just simply changes the object colour to a light grey or something else in 2.79 to an object (like the default Cube), and press P. You’ll see what I mean. No 2D filters required!

Go to gpu module and check the examples,

I checked the UPBGE online manual and setSource(), setUniform1f, etc are all depreciated, leaving only 2D filters as the option. The problem arises in which a 2D filter applies to the whole game view, not one object. So, is there any way to get a shader to be applied on an object in 0.3, or am I stuck with 2D filters forever? Many game designers want to place only one shader, or more onto an object, and not have it fill the entire screen, a good example might be a glass shader.

I want to project a 2D filter shader onto an object like how you would as a texture. How can I do this?

Read the gpu module,

We can draw anything. 2d filters are not your only option.

We can use texture cords of another object in the shader graph, what are you trying to do specifically?

Make an object display a GLSL Shader on it rather than a 2d filter, like how setSource() used to do it…

This is in gpu module, there is an example

We build a batch to draw, create a fragment shader, and a vertex shader, and bind and compile them.

You should probably read the documentation for the gpu module*

You can’t apply a custom GLSL material shader to objects in UPBGE 0.3 the same way it was done in previous versions, from BL_Shader:

BL_Shader is a class used to compile and use custom shaders scripts. This header set the #version directive, so the user must not define his own #version. Since 0.3.0, this class is only used with custom 2D filters.

You have to use the GPU Module. Good luck.

1 Like

I made this code, but it doesn’t apply to an object if you run it in UPBGE:

def main(cont):
    
    import bpy
    import gpu
    from gpu.types import GPUShader as gpu_shader
    from gpu_extras.batch import batch_for_shader
    from gpu.types import GPUOffScreen as OffScreen
    
    vert_out = gpu.types.GPUStageInterfaceInfo('my_interface')
    vert_out.smooth('VEC3', 'pos')
    
    shader_info = gpu.types.GPUShaderCreateInfo()
    shader_info.push_constant('MAT4','viewProjectionMatrix')
    shader_info.push_constant('FLOAT','brightness')
    shader_info.vertex_in(0, 'VEC3', 'position')
    shader_info.vertex_out(vert_out)
    shader_info.fragment_out(0, 'VEC4', 'FragColor')
    
    width = 128
    height = 128
    format = 'RGBA16'
    
    shader_info.vertex_source(
    "void main()"
    "{"
    "pos = position;"
    "gl_Position = viewProjectionMatrix * vec4(position, 1.0f);"
    "}"
    )
    
    shader_info.fragment_source(
    "uniform float time;"
    "uniform float resolution;"
    ""
    "void main()"
    "{"
    "FragColor = vec4(pos * brightness, 1.0);"
    "}"
    )
    
    shader = gpu.shader.create_from_info(shader_info)
    del shader_info
    
    coords = [(1, 1, 1), (2, 0, 0), (-2, -1, 3)]
    batch = batch_for_shader(shader, 'TRIS', {'position':coords})
    
    def draw():
        matrix = bpy.context.region_data.perspective_matrix
        shader.bind()
        shader.uniform_float('viewProjectionMatrix',matrix)
        shader.uniform_float('brightness', 0.5)
        batch.draw(shader)
    
    draw()
    

It doesn’t apply to the plane, and instead, just shows up as a normal plane.

There is a vertex shader and a fragment shader in the code, and then I used the bind() to bind the two shaders and then a batch tries to draw the shader. But it never applies to the plane when I press P.

How can I make a shader batch apply in the UPBGE through a script?

you need to compile a shader only, then take it and swap the source of the bge object blender object to it

I asked GPT3

#To replace the shader source of an object in Blender using Python, you can follow these steps:

#Get a reference to the object and its material:

import bpy

# Get the object
obj = bpy.data.objects['MyObject']

# Get the object's material
mat = obj.active_material
#Find the shader node that you want to modify. If you are using a ShaderNodeCustomFunction node, you can find it using the following code:

# Find the Custom Function node
node = mat.node_tree.nodes.get("Custom Function")
#Modify the shader source code of the node by updating the custom_function property:

# Define the new shader source code
new_shader_src = """
void main() {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
"""

Update the shader source code node.custom_function = new_shader_src This will replace the shader source code of the Custom Function node with the new shader code. You can use similar steps to modify the shader source code of other types of shader nodes. Note that you will need to compile the shader after modifying the source code in order for the changes to take effect. You can use the bpy.ops.shader.custom_function_call() operator to compile the shader, as shown in the previous example.

I don’t know if this works or not

youle says GPT3 made a bit up there, but we can reaplce a texture in a material with a shader

I will try and get the code :3

That results in:

AttributeError: 'str' object has no attribute 'custom_function_call'

And I cannot find a Custom Function node anywhere in the shader node editor menu. Do I have to build it myself? If so, I don’t know where to start.

All I can find is Script, but that has no inputs/outputs, therefore making it useless…

If I use any other node with the node_tree.get(), the console says:

AttributeError: 'NoneType' object has no attribute 'custom_function'

Which would be the result of the node, not the object having that kind of attribute. I’m getting really stumped here, and need help, as I have been trying to solve this for days, as it was so simple to apply to an object via a Python script back in 2.79, just use setSource() and bam! Instant shader, whereas now it’s almost impossible for some reason…

I am using UPBGE 0.3.5 Alpha (which is based on Blender 3.5)

“youle says GPT3 made a bit up there,”

GPT apparently makes up functions as it goes so it’s not super reliable.

https://upbge.org/docs/latest/api/bge.texture.html

what you need is to cobble together parts from bgl and video texture examples I think.

we get the texture slot from the material and override it’s source*
I asked youle for a example but did not get one back yet (its the holidays / everyone is busy)

BGL is going to be removed in a future release, so BGL is not viable.

Yeah gpu module apparently will have feature parity with bgl before bgl is removed

So, the team is in the process of moving those bgl variables over?

Oh, and how would I “get the texture slot and override it’s source”? I tried it with VideoTexture, and it just returned as a black texture that was invalid, rather than a proper shader. Tried with bgl.glShaderSource and with videoTexture, and it just returned a solid red plane…

Why is my shader being invalidated? I am getting very frustrated!

What I really just want to do is to get a simple shader working on an object, which was so simple and easy pre 0.3, but is really hard now.

I really need a solution to this complicated shader object setup that was really easy to do in UPBGE 0.2.5 and lower.

Update: Due to M1’s GLSL shader compiling bug, the reason a plane on OSX with M1 will be black is because the GPU thinks the texture is bound to a variable, and doesnt see the texture as a proper texture. This does not happen with 2D filters, but happens if you try and map any GLSL shader onto an object via the VideoTexture module.

How do I bypass this bug on OSX and get a shader I code to look what its supposed to do? I also use ImageFFmpeg() to map the shader data onto the plane as a texture, and update it using the source.refresh() method. Nothing is wrong with the shaders I create. The GPU will not render it, and says:

UNSUPPORTED (log once):  POSSIBLE ISSUE: Texture GLD_TEXTURE NUMBER (int) is bound to variable of type [insert example here like Float], texture is unloadable.

This bug does not affect other machines, only M1 Macs. I assume the GLSL shader compilation woes on MacOS will be fixed with the next version of UPBGE having full Metal support starting with 0.36. I am looking forward to this version that may finally fix that issue. Apple has also depreciated openGL, which could also be one of the reasons too.

I really would like to test your code on my windows machine.