setShaderInput is not working

A very long story:

I have recently delivered a new version of demomaster, which added a gui so that it can run on system without wxPython installed.

I then find that the grass demos not working. I used a shader input called ‘grass’ , which is varying according to time. In the shader, the grass vertexes are moved according to this variable. There is a task that keep updating this shader input.

However, if I use this new GUI, sometimes this shader input is not available when the demo is started. So I added a line to give it a default after the shader is set:

        self.grassNP.setShader( loader.loadShader( self.model.shaderfile ) )
        ## just a default, will be updated later anyway
        self.grassNP.setShaderInput( 'grass', Vec4(0,0,0,0))

grassNP is the root node of all the grass blades.

Everything seems ok. But I just find that the grass is not moving in the new demos. After some more tests, I find the scenario is like this:

My grass demo algorithm:

  1. Create a root node (grassNP)
  2. set up the root node as:
        self.grassNP.setTwoSided( True )
        self.grassNP.setTransparency( TransparencyAttrib.MAlpha )
        self.grassNP.setDepthWrite( False )
        ##self.grassNP.setTransparency( TransparencyAttrib.MMultisample )
        self.grassNP.setShader( loader.loadShader( self.model.shaderfile ) )
        self.grassNP.setShaderInput( 'grass', Vec4(0,0,0,0))  ## Line A
  1. Add the grass blade one by one to the root node
  2. call self.grassNP.flattenMedium() ## Line B
  3. A task will keep update the ‘grass’ shader input.

If Line A or Line B is removed, the shader input seems passed successfully to the shader.

If both lines are there, the shader seems not receive the updated shader input.

I am wondering if it is a possible bug in Panda or it is a program bug ?

There’s a whole system in place to track changes and re-issue shader parameters if something has changed, so it is possible that under some circumstances, the GSG fails to re-issue the shader inputs.

Can you strip down your code to its essentials (a few lines or so) and send it to me?


In the meantime, I find that if I do not call setShaderInput before the nodes are flattened, everything is ok. I am now putting the call after flattenMedium.

Oh, hmm. Maybe the flatten call somehow removes the shader inputs? Can you try calling getShaderInput after the flatten call?

I use Azraiyl’s shader tutorial to test this issue.
Press 1 and 2 to change colors.
Then press f, to flatten.
Now press 1 and 2, color is not changed any more.

I am on Windows XP, 1.6.2, OpenGL.

In this example we define our own uniforms, we like to send to the GPU.

import sys
import math

from direct.interval.LerpInterval import LerpFunc
import direct.directbase.DirectStart
from pandac.PandaModules import Vec3,Vec4

base.setBackgroundColor(0.0, 0.0, 0.0)

base.camLens.setNearFar(1.0, 50.0)

camera.setPos(0.0, -20.0, 10.0)
camera.lookAt(0.0, 0.0, 0.0)

root = render.attachNewNode("Root")

modelCube = loader.loadModel("cube.egg")

cubes = []
for x in [-3.0, 0.0, 3.0]:
    cube = modelCube.copyTo(root)
    cube.setPos(x, 0.0, 0.0)
    cubes += [ cube ]

shader = loader.loadShader("5.sha")

This is the only new line here. If you comment/remove this line Panda3D sees
that there is a problem. Why? The shader in this example still references to an
uniform, therefore the uniform needs to be set at least once.
root.setShaderInput("panda3drocks", 1.0, 0.0, 1.0, 1.0)

If you enable to following two lines, without modifying the shader, you have one
more debug utility that may help in some circumstances. With setTransparency
Panda3D instruct the GPU to not only overwrite the color buffer. If the fragment
shader was calculating a color, the GPU reads the old value in the color buffer
back and merges it with the new color. This process is called alpha blending and
most often it is used for transparency. But if transparency is enabled, Panda3D
has to reorder all visible nodes, so they are drawn from back to front (or else
transparency looks not correct). You have to remember this if you "debug" a
scene like this.

Some facts: The new panda3drocks uniform, you can see below, has an alpha
component with a value lesser than 1.0. The background in this scene is black.
Back facing triangles are not drawn. What we conclude from this. If the GPU has
to draw the first cube, the only two colors on the screen are black (0.0, 0.0,
0.0) and a dark purple (0.1, 0.0, 0.1). If the GPU has to draw the second cube,
and they are not side by side, a new purple (theoretically 0.19, 0.0, 0.19,
practically ~0.18, 0.0, ~0.18) appears that is brighter than its predecessor.
The more triangles are on top of one another the brighter the scene. Or in other
words, the brighter the scene the more the fragment shader needs to be called.
Something you should try to avoid.
#root.setShaderInput("panda3drocks", 1.0, 0.0, 1.0, 0.1)

base.accept("escape", sys.exit)
base.accept("o", base.oobe)

def animate(t):
    c = abs(math.cos(math.radians(t)))
    root.setShaderInput("panda3drocks", c, c, c, 1.0)

    Uncomment only one line at a time and see how the scene graph propagates
    shader inputs.

    As an aside: The setHpr method of a NodePath accepts angles in degrees. But
    Python and Cg internally work with radians (Every FPU known to more than
    0xff people internally works with radians).
    #r = abs(math.cos(math.radians(t + 0.0)))
    #g = abs(math.cos(math.radians(t + 10.0)))
    #b = abs(math.cos(math.radians(t + 20.0)))
    #cubes[0].setShaderInput("panda3drocks", r, 0.0, 0.0, 1.0)
    #cubes[1].setShaderInput("panda3drocks", 0.0, g, 0.0, 1.0)
    #cubes[2].setShaderInput("panda3drocks", 0.0, 0.0, b, 1.0)

interval = LerpFunc(animate, 5.0, 0.0, 360.0)

base.accept("i", interval.start)

def move(x, y, z):
    root.setX(root.getX() + x)
    root.setY(root.getY() + y)
    root.setZ(root.getZ() + z)

base.accept("d", move, [1.0, 0.0, 0.0])
base.accept("a", move, [-1.0, 0.0, 0.0])
base.accept("w", move, [0.0, 1.0, 0.0])
base.accept("s", move, [0.0, -1.0, 0.0])
base.accept("e", move, [0.0, 0.0, 1.0])
base.accept("q", move, [0.0, 0.0, -1.0])

def setColor(color):
    root.setShaderInput('panda3drocks', color)
    print root.getShaderInput('panda3drocks')

def flatten():
    print root.getShaderInput('panda3drocks')

base.accept("1", setColor, [Vec4(1,1,1,1)]) # set color to white
base.accept("2", setColor, [Vec4(0,1,0,1)]) # set color to green
base.accept("f", flatten)

We were using setShaderInput in the Python code. setShaderInput does nothing
more than assign a float4 (there is no possibility to assign a float2 e.g.) to a
shader uniform. Every manually provided input needs a k_ prefix therefore the
panda3drocks shader input has to be written as k_panda3drocks. If you manually
define an uniform in shader, you must at least call setShaderInput on an
appropriate NodePath that uses this shader.
void vshader(
    uniform float4x4 mat_modelproj,
    uniform float4 k_panda3drocks,
    in float4 vtx_position : POSITION,
    out float4 l_my : TEXCOORD0,
    out float4 l_position : POSITION)
    l_position = mul(mat_modelproj, vtx_position);
    l_my = k_panda3drocks;

This example is a bad idead how to waste a TEXCOORD unit. k_panda3drocks is a
constant assigned to l_my, when l_my it is passed from the vertex shader to
fragment shader it is lineraly interpolated. But a linear interpolated constant,
is a constant. In this sample, it would make more sense if we define our uniform
in the fragment shader than in the vertex shader.
void fshader(
    in float4 l_my : TEXCOORD0,
    out float4 o_color : COLOR)
    o_color = l_my;
    //o_color = float4(1,0,1,1);