Need help with texture matrix for GLSL shadows

Hi!

I am trying to get basic shadow map to work with GLSL shader. I have depth buffer working and passed to shader, but looks like I have build texture matrix wrong way or it passed wrong to shader.

I need to build matrix which transform vertex from model view to light clip space manually because CG shader inputs like trans_model_to_clip_of_dlight are not available for GLSL.

After reading lots of tutorials I decided what I need matrix like this:
T=BiasLpLv*V-1
where Lp - light projection matrix, Lv - light view matrix and V-1 - inverted modelview matrix (I need this beacuse in vertex shader I will work with modelview transformed vertex postions to avoid supplying model-world matrix for each model).

This how I build it (LCam is a camera for depth buffer, transform_mat convert Mat4 to shader supported PTA_LVecBase4f)

def transform_mat(mat):
        pta_mat = PTA_LVecBase4f.emptyArray(4)
	for i in range(4): 
		col = mat.getCol(i) 
		pta_mat[i] = UnalignedLVecBase4f(col[0], col[1], col[2], col[3]) 
	return pta_mat

light_proj = self.LCam.node().getLens().getProjectionMat()
light_view = self.LCam.getMat()
model_view = Mat4(base.camera.getMat())
model_view.invertInPlace()
bias=Mat4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0)
tx_mat = bias * light_proj * light_view * model_view 
mat = transform_mat(tx_mat)
node.setShaderInput("lproj[0]", mat)
node.setShaderInput("lproj", mat)

Vertex shader (part):

uniform vec4 lproj[4];
varying vec4 ShadowCoord;

void main() {
    mat4 sh_mat=mat4(lproj[0], lproj[1], lproj[2], lproj[3]);
ShadowCoord = sh_mat * gl_ModelViewMatrix * p3d_Vertex;
}

I have tried different matrix variations and always get wrong shadows (like lots of parellel black lines) so I think problem is texture matrix. I am sure about projection matrix, but light modelview and modelview?
How I can get current modelview matrix in panda?

UPDATE.
After some googling I think my texture matrix math is wrong. To compare depths of two passes, on render pass I need to convert model vertexes in the same way they transformed in depth buffer render.
I think i need to transform model positions to world (Mw matrix), transform world positions to light view space(Lv), then project with light projection Lp.
So, T=BiasMwLvLp, right? then in vertex shader do Tp3d_Vertex to get model coords converted to light clip space.
In short, I need to build trans_model_to_clip_of_light matrix in python the same way it build by panda3d for CG shaders.
I am trying with this (nodepath is shadow receiver to which shader is applied):

model_world = nodepath.getMat(self.render)
world_view = self.LCam.getMat(self.render)
light_proj = self.LCam.node().getLens().getProjectionMat()

t = bias * model_world * world_view * light_proj

But no luck still… the best I got is

Thanks for any help

You shouldn’t do the matrix calculation in Panda, you should do it in the shader. Plus, I don’t think you can pass a matrix that way to a shader.

Thanks for response!
I can mult them in shader, but I will need to pass several matrices (light projection, light view etc.) to shader instead of one.
Right now I have good looking shadows… but they very strange :slight_smile: , run code to see.
Thats my code:

import sys
from direct.showbase.ShowBase import ShowBase
from pandac.PandaModules import *
from direct.showbase import DirectObject 

from panda3d.core import DirectionalLight, PointLight, Spotlight
from direct.interval.IntervalGlobal import *

def get_gl_projection_mat(lens):
        gl_proj_mat=Mat4.convertMat(CSYupRight, lens.getCoordinateSystem()) * lens.getProjectionMat()
	# uncomment next line if matrix passed to shader with transform_mat call
	# gl_proj_mat.transposeInPlace()
        return gl_proj_mat

def get_gl_modelview_mat(node, camera):
        cs_transform=TransformState.makeMat(Mat4.convertMat(base.win.getGsg().getInternalCoordinateSystem(), CSZupRight))
        return cs_transform.invertCompose(node.getTransform(camera)).getMat()

class Poser(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.setup_models()
        self.setup_light()
        self.render.setShaderAuto()

        self.accept('escape', sys.exit)
        self.accept('o', self.oobe)

        base.disableMouse()
        base.camera.setPos(5, -20, 25)
        base.camera.lookAt(self.actor)
        base.setBackgroundColor(VBase4(0.5, 0.5, 0.5, 1.0))

        # make FBO
        winprops = WindowProperties.size(512, 512)
        props = FrameBufferProperties()
        props.setRgbColor(0)
        props.setAlphaBits(0)
        props.setDepthBits(1)
        LBuffer = base.graphicsEngine.makeOutput(
            base.pipe, "offscreen buffer", -2,
            props, winprops,
            GraphicsPipe.BFRefuseWindow,
            base.win.getGsg(), base.win)

        base.bufferViewer.toggleEnable()
        # render to texture
        Ldepthmap = Texture()
        LBuffer.addRenderTexture(Ldepthmap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepth)
        # clamp 
        Ldepthmap.setWrapU(Texture.WMClamp)
        Ldepthmap.setWrapV(Texture.WMClamp)
	
        # make depth camera
        self.LCam = base.makeCamera(LBuffer)
        self.LCam.node().setScene(render)
        # copy lens from light and reparent
        self.LCam.node().setLens(self.light.node().getLens())
        self.LCam.node().showFrustum()
        self.LCam.reparentTo(self.light)

        # hide floor from LCam so it wont self-shadow
        self.LCam.node().setCameraMask(BitMask32.bit(1))
        self.floor.hide(BitMask32.bit(1))

        # get projection and model top view matrices for LCam
        # code is based on panda C++ sources for passing matrices to CG shaders.. hope not errors in my C->Python translation
        light_proj=get_gl_projection_mat(self.LCam.node().getLens())
        light_view=get_gl_modelview_mat(base.render, self.LCam)
        # texture projection matrix
        tx_mat = light_view * light_proj
        # set shader inputs
        self.floor.setShaderInput("lproj[0]", tx_mat)
        self.floor.setShaderInput("lproj", tx_mat)
        self.floor.setShaderInput("shadowMap", Ldepthmap)
        sh1 = Shader.load(Shader.SLGLSL, "shaders/vshader_1.glsl", "shaders/fshader_1.glsl")
        self.floor.setShader(sh1)


    def setup_models(self):
        self.actor = loader.loadModel("box")
        self.actor.setDepthOffset(1)
        self.actor.reparentTo(render)
        self.actor.setScale(2)
        self.actor.setPos(-5, 0, 2)
        self.actor.setP(-45)

        self.actor1 = loader.loadModel("panda")
        self.actor1.setDepthOffset(1)
        self.actor1.reparentTo(render)
        self.actor1.setPos(-1, -2, 5)
        self.actor1.setP(-45)
        self.actor1.setScale(0.5)

        floorTex = loader.loadTexture('maps/envir-ground.jpg')
        cm = CardMaker('')
        cm.setFrame(-2, 2, -2, 2)
        self.floor = render.attachNewNode(PandaNode("floor"))
        for y in range(12):
            for x in range(12):
                nn = self.floor.attachNewNode(cm.generate())
                nn.setP(-90)
                nn.setPos((x - 6) * 4, (y - 6) * 4, 0)
        self.floor.setTexture(floorTex)
        self.floor.setScale(2)
        self.floor.flattenStrong()

    def setup_light(self):
        alight = AmbientLight('alight')
        alight.setColor(VBase4(0.1, 0.1, 0.1, 1))
        alnp = render.attachNewNode(alight)
        render.setLight(alnp)

        plight = Spotlight('plight')
        plight.showFrustum()
        plight.setColor(VBase4(1.0, 1.0, 1.0, 1))
        plnp = render.attachNewNode(plight)
        plnp.setPos(0, 0, 50)
        plnp.setHpr(0, -90, 0)
        plight.setAttenuation(Point3(1, 0, 0))
        render.setLight(plnp)

        plight.getLens().setFov(65)
        plight.getLens().setNearFar(1, 100)

        self.light = plnp

	# rotate light 
        hpr=self.light.getHpr()
        m1 = LerpHprInterval(self.light, 10, Vec3(hpr[0], -60, hpr[2]))
        m2 = LerpHprInterval(self.light, 10, Vec3(hpr[0], hpr[1], hpr[2]))
        move = Sequence(m1, m2, name="move_light1")
        move.loop()


p = Poser()
p.run()

Vertex shader (shaders/vshader_1.glsl):

attribute vec2 p3d_MultiTexCoord0;
attribute vec4 p3d_Vertex;
attribute vec3 p3d_Normal;

varying vec2 texcoord0; 
varying vec3 normal; 
varying vec3 lightDir;

varying vec4 ShadowCoord;

uniform vec4 lproj[4];

const mat4 biasMatrix = mat4( 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0);

void main() {
    gl_Position = gl_ModelViewProjectionMatrix * p3d_Vertex;
    texcoord0  = p3d_MultiTexCoord0;
    normal = gl_NormalMatrix * p3d_Normal;

    lightDir = vec3(gl_LightSource[0].position) - vec3(gl_ModelViewMatrix * p3d_Vertex);

    mat4 sh_mat=mat4(lproj[0], lproj[1], lproj[2], lproj[3]);

    ShadowCoord = biasMatrix * sh_mat * p3d_Vertex;
}

Fragment (shaders/fshader_1.glsl):

#version 110

varying vec2 texcoord0; 
varying vec3 normal; 
varying vec3 lightDir;

// modulate texture
uniform sampler2D p3d_Texture0;

uniform sampler2DShadow shadowMap;
varying vec4 ShadowCoord;

void main() {
    vec3 N = normalize(normal);
    vec3 L = normalize(lightDir); 

    vec4 tex = texture2D(p3d_Texture0, texcoord0.st);
    float NdotL = clamp(dot(N, L), 0.0, 1.0);

    float shadow = 1.0;
    if (ShadowCoord.w > 1.0) shadow = clamp(shadow2DProj(shadowMap, ShadowCoord).z + 0.5, 0.5, 1.0);

    vec4 finalColor = vec4 ( 0.05, 0.05, 0.05, 1.0 ) + vec4(tex.rgb * NdotL, 1.0) * shadow;
    gl_FragColor = finalColor;
}

I have used light_viewlight_projp3d_Vertex as texture matrix.

Initially I tried to pass it to shader with PTA_LVecBase4f/UnalignedLVecBase4f, but found what passing Mat4 works excellent for me!
To confirm this, I converted C+ code for composing OpenGL gl_ModelView, gl_Projection matrices, passed them to vshader as uniform vec4 mat[4] and convert p3d_Vertex with them and not panda3d prepared - result image stays the same, so I assume I got it correctly.

Now 2 questions remains:

  1. Why are shadows drawn in wrong direction? I think still error in my matrix math, as I dont use model_to_world transform, and I looking on this now.
  2. I have setup shader inputs once, but shadows moves correctly as I rotate light! I thought I need to update inputs each time light moves/rotate… Why it happens? Just because FBO changes?
  3. Maybe I am looking in worng direction at all and there is a ways to pass CG supported inputs to GLSL shaders? I have tried with passing nodepath, but it dont work for me.