Two-part fragment shaders?

I’m somewhat inexperienced with shaders, and find myself uncertain as to how to achieve something; I had a shot at searching for the answer, but I’m honestly not sure of what terms to use, and didn’t find anything that looked promising in the search that I did perform.

I have a shader that handles both terrain texturing and some slightly-hacky lighting, and now want to split the process into two separate parts so that I can apply the terrain-texturing to only my terrain geometry, while still applying the lighting to everything, terrain included.

Is this feasible–can two fragment shaders be applied to a single piece of geometry, while using only one of them for other objects? If so, how do I go about this? My best guess is to separate the vertex and fragment shaders into separate files, then create two fragment shaders (one for the terrain-texturing and one for the lighting) and assign them via “setShader”–but how does that affect the shader inputs? Do the inputs get passed through from one fragment shader to the next?

To illustrate, this is the sort of thing that I have in mind:

vtx = loader.loadShader(vertexShader.cg)
lighting = loader.loadShader(lightingShader.cg)
terrainTex = loader.loadShader(terrainTextureShader.cg)

terrain = geometry.find("**/terrain")

geometry.setShader(vtx)
geometry.setShader(lighting, 1)

terrain.setShader(terrainTex, 0)

… I suppose that “terrainTextureShader.cg” would be responsible for generating the base colour of a given fragment, with “lightingShader.cg” then taking that and using light-direction, normals, etc. to shade it appropriately…

What you’re trying to do is impossible. This is fundamental to the way GPUs work: each shader describes the transformation of data between one stage and the next stage of the pipeline. You cannot have two fragment shaders (or shaders of any type) at the same time. Instead, you must compile a different shader for each combination of effects that you want to do: one with just terrain splatting, one with lighting, and one with both.

Anyway, one way to manage this is to split up your lighting and texturing shader code into separate functions, and putting those into separate shader source files. Then, your actual terrain shader would look something like this:

#include "myfuncs.cg"

void fshader(...) {
  my_texture_pipeline(o_color, ...);
  my_lighting_pipeline(o_color, ...);
}

For your other models that only need your lighting shader code, you would create a similar entry point, except it would only call my_lighting_pipeline.
So you would still have one entry point per different combination of shader effects, but at least you won’t have to duplicate code.

If your shader pipeline gets more complicated in the future, you could easily automate this by automatically generating the entry points procedurally, and associating a list of functions with each object. Of course, you should take care to only compile the variants that you will actually end up using and not to pool the compiled shaders, or you will cause what is known as “combinatorial shader explosion”, where the number of different compiled shaders becomes huge as the number of functions gets bigger.

(We have in mind a design for a high-level shader system in a future version of Panda3D that will appear to allow applying multiple shaders to the same node. Behind the scenes, though, Panda would simply end up combining and compiling the various permutations separately. Click here if you’re interested in reading about it. Of course, this is future-talk, so don’t wait for it.)

Ah, that looks like just the thing! Thank you, both for the answer and for the explanation. :slight_smile:

Okay, I seem to be doing something wrong, and again my searches are failing me. I’ve tried several approaches, but with no apparent progress.

As suggested, I’ve moved the code into two separate functions kept in a new file, and am attempting to include that file in my main shader file. Unforunately, it doesn’t seem to be finding the new functions, producing the following errors (edited down for brevity):

error C1008: undefined variable "terrainColour"
error C1008: undefined variable "lighting"

My searches did turn up at least one example of such importation that I presume worked, but I don’t see a relevant difference between the code used there and what I’ve done above.

This is what I have at the moment, again edited for brevity and clarity:

“ruinFuncs.cg” (which contains the functions that previously were in the fragment shader)

//Cg

#ifndef ruinFuncs
#define ruinFuncs

float4 terrainColour(<params>)
{
    <code here>
}

float4 lighting(<params>)
{
    <code here>
}

#endif

“terrainShader.cg” (The main shader file)

//Cg

#include "ruinFuncs.cg"

void vshader(<params>)
{
    <code here>
}

void fshader(<params>)
{
    o_color = terrainColour(texHori, texVert, l_normal, l_texcoord0);
    o_color = lighting(l_normal, l_screenVtxPos, l_color, mstrans_world, o_color);
}

And finally, the loading of the shader, in one of my Python files:

        shader = loader.loadShader("Adventuring/terrainShader.cg")
        
        rootNode.setShader(shader)

(I also set two shader inputs, but since I excluded them from the parameters above, I’ve excluded them from the code just shown, too.)

Strangely, it looks almost as though it’s not attempting to import “ruinFuncs.cg” at all: if I change that to something nonsensical (like “ruinCatFuncs.cg”), it doesn’t seem to notice, and produces the same errors.

What am I doing wrong? Should I have some sort of header file, perhaps?

To confirm whether this is indeed caused by the inclusion not being picked up, can you try copy-pasting the function definitions in place of the #include statement, and verify that that does work?

Ah, I’ve found the problem, it seems!

Remember my thread regarding relative importations? And that I ended up moving everything into a sub-directory? That caused the problem: prepending in the name of that sub-directory (thus producing ‘#include “Adventuring/ruinFuncs.cg”’) did the trick. That change is apparently still taking some getting used to… ^^;