Blending between multiple textures using a shader

Hi, this is my first real Panda3D project aside from the tutorials. Here is what I want to do:
I have a number of textures (50+) that should be played as a slow animation or slideshow on an object.

According to the texture-swapping tutorial (which I modified to load the textures when they are needed, not all at start) I can load the textures and show them on the object, but this way there is no blending or fading, just hard swapping.

So next I tried the texture blend mode (node.setCombineRgb(TextureStage.CMInterpolate, …) which allowed to blend between two texture stages forth and back. I then exchenge the texture which is currently not shown. But first there is a weird error, that although the texture in both stages are reloaded and set, only the even numbers of images are changed, so say there are images from 1 to 10, alternately set in the two texture stages, the shown images are like 1, 2, 1, 4, 1, 6, 1, 8, 1, 10…
Second issue is the max number of texture stages (4), when using 2 stages for one kind of textures, i run out of texture stages for bump and glow map (which also should be animated).

Now nearing the real question… :wink: I looked into shaders.
I wrote a simple shader that takes two textures and a float as input and outputs a blend between those textures using a lerp with the float. this works quite nice.

//Cg

void vshader(
    uniform float4x4 mat_modelproj,
    in float4 vtx_position : POSITION,
    in float2 vtx_texcoord0 : TEXCOORD0,
    out float2 l_texcoord : TEXCOORD0,
    out float4 l_position : POSITION)
{
    l_position = mul(mat_modelproj, vtx_position);
    l_texcoord = vtx_texcoord0;
}

void fshader(
    uniform sampler2D k_tex_0,
    uniform sampler2D k_tex_1,
    uniform float4 k_blendfactor,
    in float2 l_texcoord : TEXCOORD0,
    out float4 o_color : COLOR)
{
    o_color = lerp(tex2D(k_tex_0, l_texcoord), tex2D(k_tex_1, l_texcoord), k_blendfactor);
}

But this shader doesn’t have any lightnig or shadowing, just coloring.

Now I thought, I could combine it with the auto-shader generated using “dump-generated-shaders” in the config.
(at least I hope that way I retain all the fancy lightning and shadowing and just add my blending)
My problem now is, that this generated shader uses a lot of inputs that I don’t know how to provide:
(To clarify, I know setShaderInput(), but not where to fetch e. g. plight_plight0_rel_view, which is not even noted in the documentation list of shader inputs…)

void vshader(
	 in float4 vtx_texcoord0 : TEXCOORD0,
	 out float4 l_texcoord0 : TEXCOORD0,
	 in float4 vtx_texcoord1 : TEXCOORD1,
	 out float4 l_texcoord1 : TEXCOORD1,
	 in float4 vtx_texcoord2 : TEXCOORD2,
	 out float4 l_texcoord2 : TEXCOORD2,
	 uniform float4x4 trans_model_to_view,
	 out float4 l_eye_position : TEXCOORD3,
	 uniform float4x4 tpose_view_to_model,
	 out float4 l_eye_normal : TEXCOORD4,
	 in float4 vtx_normal : TEXCOORD3,
	 in float4 vtx_tangent1 : TEXCOORD4,
	 in float4 vtx_binormal1 : TEXCOORD5,
	 out float4 l_tangent : TEXCOORD5,
	 out float4 l_binormal : TEXCOORD6,
	 uniform float4x4 trans_model_to_clip_of_slight0,
	 out float4 l_slightcoord0,
	 float4 vtx_position : POSITION,
	 out float4 l_position : POSITION,
	 uniform float4x4 mat_modelproj
) {...}

void fshader(
	 in float4 l_eye_position : TEXCOORD3,
	 in float4 l_eye_normal : TEXCOORD4,
	 uniform sampler2D tex_0,
	 in float4 l_texcoord0 : TEXCOORD0,
	 uniform sampler2D tex_1,
	 in float4 l_texcoord1 : TEXCOORD1,
	 uniform sampler2D tex_2,
	 in float4 l_texcoord2 : TEXCOORD2,
	 in float3 l_tangent : TEXCOORD5,
	 in float3 l_binormal : TEXCOORD6,
	 uniform float4 alight_alight0,
	 uniform float4x4 plight_plight0_rel_view,
	 uniform float4x4 slight_slight0_rel_view,
	 uniform float4   satten_slight0,
	 uniform sampler2DShadow k_slighttex0,
	 float4 l_slightcoord0,
	 uniform float4 row1_view_to_model,
	 out float4 o_color : COLOR0,
	 uniform float4 attr_color,
	 uniform float4 attr_colorscale
) {...}

Is there a way I can also generate/dump the used shader inputs?
Or can someone enlighten me, what exactly to provide as input?

Thanks in advance

I don’t know of any way to have the shader inputs dumped, but for what i’ve seen, the inputs you’re wondering about are related to the light NodePaths : alight0 for ambient light, slight0 for the first spotlight, and plight0 for the first point light (next ones would be called : plight1, plight2, etc).

Providing the light nodepaths for these inputs should work, if not can you also paste the python code you use for testing?

OK, here’s some sample code:

        self.model = loader.loadModel('models/plane')
        self.model.reparentTo(self.render)     

        alight = render.attachNewNode(AmbientLight("Ambient"))
        alight.node().setColor(Vec4(0.5, 0.5, 0.5, 1))
        render.setLight(alight)
             
        plight = PointLight('plight')
        plight.setColor(VBase4(.5, .5, 0.5, 1))
        plnp = render.attachNewNode(plight)
        plnp.setPos(0, 5, 1)
        render.setLight(plnp)

        slight = Spotlight('slight')
        slight.setShadowCaster(True, 4096, 4096)
        slight.setColor(VBase4(.7, .7, .7, 1))
        slnp = render.attachNewNode(slight)
        slnp.setPos(5, -5, 5)
        slnp.lookAt(self.model)
        render.setLight(slnp)

        shader = loader.loadShader("genshader0.sha")
        self.model.setShader(shader)
        self.model.setShaderInput("alight0", alight)	# this works
        self.model.setShaderInput("plight0", plight)	# this works not

I added the shader inputs by trial-and-error, without any inputs I get “AssertionError: Shader input alight0 is not present.”
So I added alight, and it worked.
At next start of program error was “AssertionError: Shader input plight0 is not present.”
So I added the plight line above, but now I get:

self.model.setShaderInput(“plight0”, plight)
TypeError: Arguments must match one of:
setShaderInput(non-const NodePath this, const ShaderInput inp)
setShaderInput(non-const NodePath this, non-const InternalName id)
setShaderInput(non-const NodePath this, string id)
setShaderInput(non-const NodePath this, non-const InternalName id, const Vec4 v)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex)
setShaderInput(non-const NodePath this, string id, const Vec4 v)
setShaderInput(non-const NodePath this, string id, const NodePath np)
setShaderInput(non-const NodePath this, string id, non-const Texture tex)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1)
setShaderInput(non-const NodePath this, string id, float n1)
setShaderInput(non-const NodePath this, non-const InternalName id, const Vec4 v, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, string id, const Vec4 v, int priority)
setShaderInput(non-const NodePath this, string id, const NodePath np, int priority)
setShaderInput(non-const NodePath this, string id, non-const Texture tex, int priority)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2)
setShaderInput(non-const NodePath this, string id, float n1, float n2)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4)
setShaderInput(non-const NodePath this, non-const InternalName id, float n1, float n2, float n3, float n4, int priority)
setShaderInput(non-const NodePath this, string id, float n1, float n2, float n3, float n4, int priority)

Looking at the parameter definition in the shader, alight is a float4 and plight is a float4x4 so there must be more… From the shader code

	 lcolor = plight_plight0_rel_view[0];
	 lspec  = plight_plight0_rel_view[1];
	 lpoint = plight_plight0_rel_view[2];
	 latten = plight_plight0_rel_view[3];

I see it’s color, specular color, position and attenuation, but how do I get these into the shader?

	self.model.setShaderInput("plight0", Vec4(plight.getColor(), plight.getSpecularColor(), plight.getPoint(), plight.getAttenuation()))

won’t work, says:
self.model.setShaderInput(“plight0”, Vec4(plight.getColor(), plight.getSpecularColor(), plight.getPoint(), plight.getAttenuation()))
TypeError: a float is required

OK some methods return only Vec3, so next try:

self.model.setShaderInput("plight0", 
	Vec4(Vec4(plight.getColor().x, plight.getColor().y, plight.getColor().z, 1.0),
    Vec4(plight.getSpecularColor().x, plight.getSpecularColor().y, plight.getSpecularColor().z, plight.getSpecularColor().w), 
    Vec4(plight.getPoint().x, plight.getPoint().y, plight.getPoint().z, 1.0),
    Vec4(plight.getAttenuation().x, plight.getAttenuation().y, plight.getAttenuation().z, 1.0)))

Same message:
Vec4(plight.getPoint().x, plight.getPoint().y, plight.getPoint().z, 1.0)
TypeError: a float is required

How do I provide the correct float4x4 inputs?

Since there is only one error at a time, I don’t know, which inputs will also have errors.

alight = render.attachNewNode(AmbientLight("Ambient"))
#...
plight = PointLight('plight')

alight is a NodePath to an AmbientLight (which is correct), plight is a PointLight (which is incorrect).

All these kinds of shader input must be provided as NodePaths to lights. I know, the manual could and should do a better job in the shaders section.

It goes like this:

# Python
setShaderInput("plight0", myPointLightNodePath)

# Cg
void fshader(
   uniform float4x4 plight_plight0_rel_view,
//...
float4x4 plight = plight_plight0_rel_view;

float4 lcolor = plight[0];
float4 lspec = plight[1];
float4 lpos = plight[2];
float4 latt = plight[3];

plight0 in setShaderInput maps to plight_plight0_rel_view.

Be sure to use NodePaths to lights, and not lights themselves.

I changed it to

        self.model.setShaderInput("plight0", plnp)
        self.model.setShaderInput("slight0", slnp)

Now I don’t get any errors, although I wonder why it’s not requesting any other missing inputs…
But the point and spot light both don’t affect the object. There is just ambient light in the scene.

So, I read a bit further and came across TextureBuffers. Now I have such a buffer, containing a card using my simple shader to blend two textures.
The getTexture()-output is then used as texture for the model, which uses the auto-shader for lightning and shadows. Looks fine.

But maybe you have suggestions on improving the following (shortened) code:

And why do I have to shift the card by 0.7 to have it centered?

        mybuffer = base.win.makeTextureBuffer("My Buffer", 2048, 2048)
        mytexture = mybuffer.getTexture()
        mybuffer.setSort(-100)
        mycamera = base.makeCamera(mybuffer)
        mylens = OrthographicLens()
        mylens.setFilmSize(2,2)        
        mycamera.node().setLens(mylens)
        myscene = NodePath("My Scene")
        mycamera.node().setScene(myscene)

        cm=CardMaker('')
        cm.setFrame(-1,1,-1,1)
        self.texCard = myscene.attachNewNode(cm.generate())
        self.texCard.setPos(0, 10, 0.7)

        shader = loader.loadShader("texBlend.sha")
        self.texCard.setShader(shader)

        self.texCard.setShaderInput("blendfactor", 0.0, 0.0, 0.0, 0.0)

        self.tex = loader.loadTexture("textures/img1.jpg")
        self.texCard.setShaderInput("tex_0", self.tex)

        self.tex = loader.loadTexture("textures/img2.jpg")
        self.texCard.setShaderInput("tex_1", self.tex)

        ...

        self.texLerp12 = LerpFunc(setBlendFactor, fromData=0, toData=1, duration=10, blendType='noBlend', extraArgs=[self.texCard], name=None)

		...

        self.model.setTexture(self.texStage, mybuffer.getTexture(), 1)