Using GLSL Shaders on objects in UPBGE

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.

First, make sure this code I made is used first:

    import bge
    import bge.logic as gl
    import bge.texture as tex

    own = cont.owner

    mat = tex.materialID(own, 'MA' + own['material'])

    channel = own['channel']

    texture = tex.Texture(own, matID, channel)

    name = own['shader']

    shader = gl.expandPath('//shader_path/' + name)

    new_source = tex.ImageFFmpeg(shader)
    
    gl.texture = texture
    gl.texture.source = new_source
    gl.texture.refresh(True)

Then, save this GLSL code I made below for testing the bug (make sure there is a shader_path directory in the same path as the blend file), and use it on the cube:

out vec4 fragColor;

void main(void) {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

If you set it up correctly, the cube should turn bright red. On an M1 iMac or MacBook, it will instead remain black due to the M1 shader compiling bug. This requires the usage of a Python script in a controller, NOT a 2D Filter actuator and a material property (get the material name you typed in), a shader property that points to the filename that must end in .glsl, and a channel property, this tells the code what node texture to use.

Don’t you get an error for cont not beeing defined?

I forgot to mention you need to either define the name cont as in the controller, or use def main(cont) and indent the text.

Try using a glsl shader on it. Should you be on an M1 Mac, it shall just be black with an unloadable texture due to M1’s shader compiling bug. On other machines it should work flawlessly.

Nope, doesn’t work. Windows machine, no error messages.

Looks like I’ll have to wait until the Metal version of UPBGE comes out to test and write my own shaders.

By the way, I do check upbge.org’s download page sometimes for new nightly builds, but it can be a month, and still no nightly release build despite it saying “Released a new build every week.” A week is 7 days, not a random amount of days.

With UPBGE 0.36 having Metal support, this would eliminate the slow “compiling shaders (x amount done)” almost completely on the Mac version, as the GPU Metal backend will be able to do it much faster. It would also increase performance, and allow for us, Mac users to once again map our written shaders via videoTexture to an object like I was trying to do without the hassle of trying to write arrays or something complicated just to get it to work.

Shaders as mentioned before, especially 2D filters will be much faster and more responsive.

The M1’s “UNSUPPORTED (log once) POSSIBLE ISSUE” shader compilation bug will no longer be an issue due to shaders being supported on the Metal backend, not the depreciated openGL.

I wonder if SetSource() will come back for 0.36? I’m a bit curious as to when this version would get released.

I’m sorry to drag everyone through this post, but openGL seems to not work properly on M1 when a shader is mapped to an object as a texture through an int value (bge.logic.videotexture.materialID). It should work in the next version through Metal though, although I’m not excited. Nevertheless, I look forward to UPBGE becoming a great game engine for everyone out there.

As stated on GitHub, UPBGE no longer supports BL_Shader.setSource() or other former BGE compatible KX_BlenderMaterial attributes/methods. Instead, use the GPU module as BluePrintRandom mentioned.