Dynamnic Shadow for custom shader possible?

Hi I am building custom shader for in bGE but it will lose the dynamic shadow . How do i get the dynamic shadow to work in the custom shaders

Is there a way to change the default shaders of the viewport or blender ?

Nope, not to my knowledge. I’d probably recommend trying a node material, where it combines the custom shader material and a blank one “on top”, but there aren’t any guarantees this would either work or even be desirable. I think custom shaders can’t be shared between objects at the moment, either, which isn’t so great, and may stunt your particular usage case.

The best I have been able to with regards to the built in shadow code, is to use OpenGL to find the shadow texture, pass that into my custom shader, and then do the shadow testing in my shader. It’s not pretty, but it got the job done. I was able to find the texture I was looking for by looking at a combination of the texture’s dimensions and format. I think variance shadow maps use GL_RG32F, and normal shadow maps are probably using some kind of depth format. If you are interested in going down that route I can get you some more specifics.

Is there any way to make a shadow via video textures?

Overlay a decal in real time?

Cast a few rays away from the light, and place decals?

Depending on what you are looking to accomplish, you could use light textures. Not sure if these can be manipulated via the video texture module, or not. I imagine the texture would at least need to be shared with a material for video texture to find it.

So like a shadow atlas on anything that it was casting on?

Can you use UV offsets to only place a slice of a atlas?

@Kupoman

Thats seems the best approach . How do i do that?

Ok, so one of the harder parts about this process is finding the shadow texture(s). When I did this, I was only looking for one texture that came from a sun lamp. I knew the size was 1024x1024, and I knew the format was GL_RG32F (the image format used for variance shadow maps in the BGE). With these two pieces of information I was able to look through the OpenGL textures until I found one that matched:


if self.shadow_texture == 0:
    for i in range(self.textures[0]):
        try:
            glBindTexture(GL_TEXTURE_2D, i)
        except OpenGL.error.GLError:
            continue
        format = glGetTexLevelParameteriv(GL_TEXTURE_2D, \
            0, GL_TEXTURE_INTERNAL_FORMAT)
        size = glGetTexLevelParameteriv(GL_TEXTURE_2D, \
            0, GL_TEXTURE_WIDTH)
        if format == GL_RG32F and size == 1024:
            self.shadow_texture = i
            break
    else:
        self.shadow_texture = -1

(For more context on this code snippet, the full file can be found here: https://github.com/Kupoman/pcgtests/blob/master/src/renderfx/deferred_shading.py)

The reason I pass in self.textures[0] into the range function is because OpenGL assigns sequential numbers as texture names, and by using the number assigned to a texture I created, I have a rough idea of how many textures Blender has created. The try/except is for a couple of 1D textures Blender created.

This process could be trickier for multiple shadow casters. While variance shadow maps are the only thing that uses GL_RG32F textures that I know of in the BGE, each shadow caster actually has two textures for blurring. This is the reason for the size check. In my case, I had two GL_RG32F textures. One at the size I specified in the UI, and one at 1/4 that size (512x512). If all of your shadow casters use the same texture size, you should be able to do something similar.

Once you have found the texture(s) you need, it is simple enough to pass them to your shader. Since I had taken over most of the rendering process in my project, I simply used OpenGL’s glUniform* functions. For a more standard custom shader, you can use the API for passing uniforms into custom shaders: http://www.blender.org/api/blender_python_api_2_73_release/bge.types.BL_Shader.html. For textures, use the Uniform1i version (you are passing in the texture name, which is one integer).

Once you have the texture in your custom shader, you can just reuse the BGE’s shadow code. Here are a couple of the shadow functions from the BGE source code:


void test_shadowbuf(vec3 rco, sampler2DShadow shadowmap, mat4 shadowpersmat, float shadowbias, float inp, out float result)
{
    if(inp <= 0.0) {
        result = 0.0;
    }
    else {
        vec4 co = shadowpersmat*vec4(rco, 1.0);

        //float bias = (1.5 - inp*inp)*shadowbias;
        co.z -= shadowbias*co.w;
        
        if (co.w > 0.0 && co.x > 0.0 && co.x/co.w < 1.0 && co.y > 0.0 && co.y/co.w < 1.0)
            result = shadow2DProj(shadowmap, co).x;
        else
            result = 1.0;
    }
}

void test_shadowbuf_vsm(vec3 rco, sampler2D shadowmap, mat4 shadowpersmat, float shadowbias, float bleedbias, float inp, out float result)
{
    if(inp <= 0.0) {
        result = 0.0;
    }
    else {
        vec4 co = shadowpersmat*vec4(rco, 1.0);
        if (co.w > 0.0 && co.x > 0.0 && co.x/co.w < 1.0 && co.y > 0.0 && co.y/co.w < 1.0) {
            vec2 moments = texture2DProj(shadowmap, co).rg;
            float dist = co.z/co.w;
            float p = 0.0;
            
            if(dist <= moments.x)
                p = 1.0;

            float variance = moments.y - (moments.x*moments.x);
            variance = max(variance, shadowbias/10.0);

            float d = moments.x - dist;
            float p_max = variance / (variance + d*d);

            // Now reduce light-bleeding by removing the [0, x] tail and linearly rescaling (x, 1]
            p_max = clamp((p_max-bleedbias)/(1.0-bleedbias), 0.0, 1.0);

            result = max(p, p_max);
        }
        else {
            result = 1.0;
        }
    }
}

Use test_shadowbuf for normal shadow maps, and test_shadowbuf_vsm for variance shadow maps. Here are some details on the parameters:

  • rco: The view space position of the current fragment
  • shadowmap: The texture you worked so hard to find earlier
  • shadowpersmat: A matrix used to convert rco into the lamp’s view space (including projection)
  • shadowbias/bleedbias: Settings from the UI
  • inp: If you look at the functions, this is mostly unused. Set it to something like 1 and you should be fine.
  • result: This is an out variable containing the amount of shadow the fragment receives

Here is some sample code for calculating the shadowpersmat:


lproj = mathutils.Matrix.Identity(4)
lproj[0][0] = lproj[1][1] = 1.0 / CLIP_SIZE
lproj[2][2] = -2.0 / (CLIP_END - CLIP_START)
lproj[2][3] = -(CLIP_END + CLIP_START)/(CLIP_END-CLIP_START)
spersmat = lproj
spersmat = spersmat * light.worldTransform.inverted()
spersmat = spersmat * view_mat.inverted()
spersmat = [i for col in spersmat.col for i in col]

CLIP_END, CLIP_START, and CLIP_SIZE are from the lamp’s UI settings. It is important to note this calculation uses an orthographic projection matrix (lproj) because this was for a sun lamp. A normal perspective matrix will be needed for spot lamps.

I have made a few assumptions about familiarity with OpenGL/GLSL in this post (and possibly some other assumptions as well), so feel free to ask any questions you might have.

Hey,

I know this thread is some month old but I have seen some threads here on blender artists about this topic and I can’t find a runnable file or code. So I try it myself with help of your informations. I’m able (looks like it work) to get the shadowmap with bgl that’s not the problem (I only use a normal Shadomap from a Sunlamp to simplify this for testing later if I get the shader working I will change it).

The only question I have is what do you mean with “self.textures[0]”? Because I don’t understand how to use it in my code At the moment I use only a high integer number for the loop but it would be nicer when I could know a closer range for the texturescount.

But the main problem is that I don’t get the shader working and I get no error message in the console. I’ve done everything as I think it is right but I not sure that I understand it and I’m not a glsl pro, so it would be nice if you or someone else who know something about this could help me.

Here is my code:

import bge
import mathutils
import bgl

VertexShader = """
varying vec4 position;

void main(void)
{
    position = gl_ModelViewMatrix * gl_Vertex;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""

FragmentShader = """
uniform sampler2DShadow shadowMap;
uniform mat4 shadowpersmat;
varying vec4 position;

void test_shadowbuf(vec3 rco, sampler2DShadow shadowmap, mat4 shadowpersmat, float shadowbias, float inp, out float result)
{
    if(inp <= 0.0) {
        result = 0.0;
    }
    else {
        vec4 co = shadowpersmat*vec4(rco, 1.0);

        //float bias = (1.5 - inp*inp)*shadowbias;
        co.z -= shadowbias*co.w;
        
        if (co.w > 0.0 && co.x > 0.0 && co.x/co.w < 1.0 && co.y > 0.0 && co.y/co.w < 1.0)
            result = shadow2DProj(shadowmap, co).x;
        else
            result = 1.0;
    }
}

void main (void)
{   
    vec3 rco = position.xyz;
    float bias = 1.0;
    float inp = 1.0;
    float shadow;
    
    test_shadowbuf(rco, shadowMap, shadowpersmat, bias, inp, shadow);
    gl_FragColor = vec4((shadow*vec3(1.0)),1.0);   
}
"""
own = bge.logic.getCurrentController().owner
scene = bge.logic.getCurrentScene()
cam = scene.active_camera

def getShadowpersmat():
    light = scene.lights[0]
    CLIP_START = light['clipStart']
    CLIP_END = light['clipEnd']
    CLIP_SIZE = light['spotSize']
    
    lproj = mathutils.Matrix.Identity(4)
    lproj[0][0] = lproj[1][1] = 1.0 / CLIP_SIZE
    lproj[2][2] = -2.0 / (CLIP_END - CLIP_START)
    lproj[2][3] = -(CLIP_END + CLIP_START)/(CLIP_END-CLIP_START)
    spersmat = lproj
    spersmat = spersmat * light.worldTransform.inverted()
    spersmat = spersmat * cam.worldTransform.inverted()
    spersmat = [i for col in spersmat.col for i in col]

    return spersmat


def getShadowmap():
    for i in range(200): #index max?
        bgl.glBindTexture(bgl.GL_TEXTURE_2D,i)
        format = bgl.Buffer(bgl.GL_INT , [1])
        bglWidth = bgl.Buffer(bgl.GL_INT , [1])
        bglHeight = bgl.Buffer(bgl.GL_INT , [1])
        bgl.glGetTexLevelParameteriv(bgl.GL_TEXTURE_2D, 0, bgl.GL_TEXTURE_INTERNAL_FORMAT, format)
        bgl.glGetTexLevelParameteriv(bgl.GL_TEXTURE_2D, 0, bgl.GL_TEXTURE_WIDTH, bglWidth)
        bgl.glGetTexLevelParameteriv(bgl.GL_TEXTURE_2D, 0, bgl.GL_TEXTURE_HEIGHT, bglHeight)
        
        if format[0] == bgl.GL_DEPTH_COMPONENT:
            print("Format " , format[0])
            print("Width " , bglWidth[0])
            print("Height " ,bglHeight[0])
            print(i)
            #image = bgl.Buffer(bgl.GL_BYTE , [bglWidth[0],bglHeight[0]])
            #bgl.glGetTexImage(bgl.GL_TEXTURE_2D, 0, bgl.GL_DEPTH_COMPONENT, bgl.GL_UNSIGNED_BYTE, image)
            map = i
    return map


for mesh in own.meshes:
    for material in mesh.materials:
        shader = material.getShader()
        if shader != None:
            if not shader.isValid():
                shader.setSource(VertexShader, FragmentShader, True)
                shader.setUniform1i('shadowMap', getShadowmap())
            shader.setUniformMatrix4('shadowpersmat',getShadowpersmat())          

Here is a Blend file too
shadowshader.blend (549 KB)

Does no one has an idea? :no:

Does no one has an idea what could be wrong? :no:

EDIT: Sorry for the double post, it was accidentally:o

self.textures was a list of texture ids for textures I had created.

As for why your shader is not working, it would first help to know more about what’s not working. No change in viewport? Black output? Shadows in the wrong spot? More details would make it easier to narrow down the problem.

edit: I did not see the blend file, looking at it now.

I found part of your problem. What should be passed in to the shader for a sampler input is not the OpenGL name, but the texture unit the sampler is bound to. This means you need to pick a texture unit, make sure your texture is bound to it, and then send in texture unit number to the shader.

Something like this:


if 'shadowmap' not in own:
    own['shadowmap'] = getShadowmap()
for mesh in own.meshes:
    for material in mesh.materials:
        shader = material.getShader()
        if shader != None:
            if not shader.isValid():
                shader.setSource(VertexShader, FragmentShader, True)
            bgl.glActiveTexture(bgl.GL_TEXTURE1)
            bgl.glBindTexture(bgl.GL_TEXTURE_2D, own['shadowmap'])
            shader.setUniform1i('shadowMap', 1)
            shader.setUniformMatrix4('shadowpersmat',getShadowpersmat())

Unfortunately the texture looks empty. It is showing up as all white for me, which is likely what it gets cleared to before writing the depth information to it for shadows. As far as I know, the shadow maps should be created before the mesh with the custom shader gets drawn. I’ll look more into it tomorrow.

edit: I got curious and did some more digging now instead of tomorrow. My guess is there could be some kind of state issue. Ideally, you want that texture bind to happen close to the draw call for the mesh. I tried doing the bind in a pre_draw callback and using texture unit 8 to try and avoid any state problems, but I got the same results as before. I may have just been lucky when I did something similar since I was in control of the draw calls that needed the texture. I’m not sure if I can be of more help, this whole process is very much a hack and may just not work in this case. HG1 has done some shader hacking recently, so he may have some ideas.

I hope you’ll find a solution! It would be a great ressource for custom shaders :slight_smile: Thanks for your investigations. I’ll try to make some tests too…

hm, that aren’t good news. I never thought that this could be so complicated even if it is a hack, but it seems like that it is. But anyway thanks for your investigations and I still hope we will find a solution too.

I will fix the problem with the texture unit and I will try some things too, now where I know that there probably are no mistakes in the shader. But it’s very unlikely that I get it wokring with my little knowledge. Yes but maybe HG1 can help if it’s possible.

Maujoe, I think I’ve got a kind of surprise for you: http://www.pasteall.org/blend/39772

This needs to be reviewed and simplified. I took some parts of the blender sources code… If you want to make a ressource, you’re welcome. I’ve not much time these days.

EDIT: In fact, I’m not sure of what I did LOL. Sorry if I make an enormous mistake… I think we have to reactivate lights somewhere. Suzannes are rendered black.

To make it a bit shorter:

import bge
from bgl import *


scene = bge.logic.getCurrentScene()


obj = scene.objects['Plane']


if "prog" not in obj:


    for prog in range(32767):
        if glIsProgram(prog) == True:
            obj["prog"] = prog


for i in range(20):
    glBindTexture(GL_TEXTURE_2D,i)
    format = Buffer(GL_INT , [1])
    bglWidth = Buffer(GL_INT , [1])
    bglHeight = Buffer(GL_INT , [1])
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, format)
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, bglWidth)
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, bglHeight)


    if format[0] == GL_DEPTH_COMPONENT:
        
        image = Buffer(GL_BYTE , [bglWidth[0],bglHeight[0]]) 
        
        glBindTexture(GL_TEXTURE_2D, i);
        glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, image)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
                        GL_NONE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);


        glActiveTexture(GL_TEXTURE0 + i)
        glBindTexture(GL_TEXTURE_2D, i)  
        glUseProgram(obj["prog"])
        
        glUniform1i(glGetUniformLocation(obj["prog"], "shadowMap"), i)
        glActiveTexture(GL_TEXTURE0)
        

But I don’t know how to deal with lights. I hope HG1 or Kupoman could fix this :slight_smile:

no colors anymore i want them to turn black

EDIT2: You can simply add another duplicated lamp to have lightened suzannes. Now we just have to make all this mess more friendly and I think it could become a very cool ressource for custom shaders.

Oh wow that looks like you are on the right way

And I think pulse mode isn’t needed for the script because it seems it’s enough to run it only once and texture will be updated automatically.

I think we have to reactivate lights somewhere.

Yes, thats strange seems like it’s only the light from which the shadowmap comes from any other lights works.

HG1 made a patch to access program number so we could get rid of the initial for loop (I can’t find the link…) I hope it will be merged soon as it’s a tiny patch, not dangerous. I hope this can work with sun lamps (the most important in my opinion). I didn’t tested yet

And I think pulse mode isn’t needed for the script because it seems it’s enough to run it only once and texture will be updated automatically.

That’s a good news :slight_smile: I didn’t remarked

And I thinkt the error with the light has someting to do with this line glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_COMPARE_MODE,GL_NONE); because when I deactivate it the Objects are not black but once I used this line the Objects are black and it’s independently from what I do after this line.

Good to know :slight_smile: . I just tested with sun lights and it works but the script needs some modifications regarding to matrix computations. Very cool. At this state we can affirm that all is possible! :smiley: