Lights inputs in GLSL shaders

Hello!

So far I was using Panda 1.8.1 and Cg shaders, and now I’d like to switch to GLSL shaders. I read that Panda 1.9.0 will have more extensive support for GLSL, so I tried the current development build. Seems to work nice, except that I’m not sure how to pass light data to GLSL shader (like alight_, dlight_, etc. in Cg). Are some special inputs for this available or planned, or should I do it in some other way?

Hi, welcome to the forums!

Currently, the only way to access this in GLSL is by using the built-in gl_LightSource parameter. Unfortunately, it is deprecated nowadays, so we have to make a proper replacement for it soon – great feature request, it’s on my todo list. :slight_smile:

Thanks for the welcome :slight_smile: It is a great forum, I was able to answer many doubts by searching it (and of course Panda doc).

Maybe I could give it a shot with inputs. I already tried adding something like p3d_AmbientLight_ into ShaderContext() in glShaderContext_src.cxx, and it worked. But maybe that’s not the best naming system or place to put it. So if you could say something about this, I could try to submit a patch.

Yes, it would be fairly easy to map something like p3d_Spotlights since we already have that model for the Cg inputs.

Ideally, however, I’d like to mirror the old OpenGL syntax more, so that it’s easier to port older shaders. So there’d be something like a p3d_LightSource[] struct array for all lights except ambient lights (which would be p3d_LightModel.ambient). Something like this, except with p3d_ prefix:
mew.cx/glsl_quickref.pdf

This may require additional changes to gobj/shader.h and shader.cxx to create the new types of ShaderMatInput, and changes to display/graphicsStateGuardian.cxx to be able to supply them based on the current graphics state. I’d be able to get around to doing this sometime soon, though.

Um, yeah, that does sound a bit more complicated :slight_smile:

But, wouldn’t the array input influence setShaderInput usage? For example, with Cg at one place I used named light inputs to slightly differently process two directional lights. To port this to GLSL with array light inputs, I would need to modify somehow setShaderInput invocations, to be sure which light is at which index. I would think that with Panda, ideally, the code outside of shaders should not need to depend on which shader language is chosen. Maybe provide both interfaces, array and named?

Oh, I was thinking of mapping to the default light interface, so that you could specify lights using setLight() rather than setShaderInput, similar to how it would work if you were using the fixed-function lighting interface. Would this not fit your use case well?

I’m not really 100% sure how we would allow binding a light via setShaderInput neatly, without resorting to the hacks we have in Cg such as “alight_mylight”. Maybe we can make a struct that is used like “uniform p3d_LightSourceParameters mylight” and filled in using setShaderInput(“mylight”, np), but I have a feeling OpenGL might not offer the required introspection methods for that.

p3d_LightSource[] looks more elegant, but it would be nice to have a way of telling what a light in the scene graph is numbered in the shader.
So if it’s hard to have both ‘indexed’ and ‘named’ lights, then maybe it would be easier to make a function that will return an index for a given node (eg. light.getGlslIndex(render))?
This way you can pass the returned into a shader

#python
render.setLight(light)
myLightIndex=light.getGlslIndex(render)
render.setShaderInput('myLightIndex', myLightIndex)
//glsl-ish?
uniform int myLightIndex;
void main(void) {
vec4 L = p3d_lightSource[myLightIndex].position;
//or something like it
}

Hmm, but that uses up additional uniforms, and it also can’t work because the exact indices aren’t known until the nodes have been collected for rendering. There might have been another light specified on a parent or child node, and getGlslIndex wouldn’t be able to know.

I also prefer not to mix the concept of “shader input” with “light attribute”. If you’re using a custom named object because you want custom behaviour for a particular light in your shader, it should be a setShaderInput call, and not a setLight call - similar to how we use p3d_Texture0 for textures passed via setTexture but custom named texture inputs are passed via setShaderInput.

Ideally I think this would be the most elegant:

struct p3d_LightSourceParameters {
  vec4 ambient;
  vec4 diffuse;
};

// myLight passed via setShaderInput
uniform p3d_LightSourceParameters myLight;

// This, if used, is the list of lights passed via setLight
uniform p3d_LightSourceParameters p3d_LightSource[];

void main() {
  out_color = myLight.ambient + myLight.diffuse * dot(N, L);
}

where myLight would be assigned like this:

someLight = render.attachNewNode(PointLight("sun"))
node.setShaderInput("myLight", someLight)

With the Cg system, I can uniformly use setShaderInput for all inputs, and access various “views” (e.g. wspos_ or mspos_) of set nodepath by prefixes and suffixes to the name. And in the shader I can do different stuff for inputs based on the name. I thought this was very neat system, didn’t realize it is considered a hack.

In one place I rely on this to have sun light and moon lights, both directional, but which are differently treated based on time of day. In another place, I have some sort of dynamic point light allocation, where nearest N lights are reflected on the object. This code relies on setShaderInput overriding behavior when a name is repeated. With only p3d_LightSource array for GLSL, I would have to rework this code, and somehow track array indices to differentiate same-kind inputs.

The p3d_LightSourceParameters example does look very nice, and retains named inputs. But if it is not possible to implement it, I think it would be a pity to not allow Cg-like inputs. BTW, is there any technical problem with Cg inputs, like performance or something?

No, there isn’t. I’m just being overly fussy, that’s all. :slight_smile:

OK, I just checked in support for custom light shader inputs. You can now do something like this (just a hacked-up test shader, don’t take too many lessons from this):

light = PointLight('abc')  # Or Spotlight, or DirectionalLight
light.setColor(...)
light_path = render.attachNewNode(light)
render.setShaderInput("customLight", light_path)
// Custom light shader input, works on all light types except AmbientLight
uniform gl_LightSourceParameters customLight;

uniform struct {
  // Sum of all ambient lights
  vec4 ambient;
} p3d_LightModel;

uniform struct {
  vec4 ambient;
  vec4 diffuse;
  vec3 specular;
  float shininess;
} p3d_Material;

in vec3 vpos;
in vec3 norm;

void main() {
  gl_FragColor = p3d_LightModel.ambient * p3d_Material.ambient;
  vec3 diff = customLight.position.xyz - vpos * customLight.position.w;
  vec3 L = normalize(diff);
  vec3 E = normalize(-vpos);
  vec3 R = normalize(-reflect(L, norm));
  vec4 diffuse = clamp(p3d_Material.diffuse * customLight.diffuse * max(dot(norm, L), 0), 0, 1);
  vec4 specular = vec4(p3d_Material.specular, 1) * customLight.specular * pow(max(dot(R, E), 0), gl_FrontMaterial.shininess);
  float spotEffect = dot(normalize(customLight.spotDirection), -L);
  if (spotEffect > customLight.spotCosCutoff) {
    gl_FragColor += (diffuse + specular) / (customLight.constantAttenuation + customLight.linearAttenuation * length(diff) + customLight.quadraticAttenuation * length(diff) * length(diff));
  }
}

If you don’t want to use the built-in definition of the gl_LightSourceParameters struct you can just define your own:

struct p3d_LightSourceParameters {
  // Ambient is always vec4(0, 0, 0, 1)
  vec4 ambient;
  // light.get_color()
  vec4 diffuse;
  // light.get_specular_color()
  vec4 specular;
  // In case of Spotlight/PointLight, this is the light position in view space, with fourth component 1
  // In case of DirectionalLight, this is actually (-dir) with the fourth component set to 0
  vec4 position;
  // This is the only one that's not actually available
  vec4 halfVector;
  // Etc blah blah same as OpenGL FFP equivalents
  // You can just omit their declaration if you don't need them, they are bound by name
  vec3 spotDirection;
  float spotExponent;
  float spotCutoff;
  float spotCosCutoff;
  float constantAttenuation;
  float linearAttenuation;
  float quadraticAttenuation;
};

uniform p3d_LightSourceParameters customLight;

For compactness, you can just define what you need:

uniform struct {
  vec4 position;
  vec4 diffuse;
} customLight;

Please let me know if this is sufficient for your needs. I can add more attributes if desired. I will be adding p3d_LightSource[i] in the future as a way to access lights that were set via setLight() rather than via setShaderInput().

(The shader input code is a bit messy at the moment, this might go a bit more easily once I start merging in some of the improvements I’ve been working on regarding the shader system, but that’ll have to wait a while.)

// This is the only one that's not actually available
  vec4 halfVector;

I know a half vector isn’t hard to calculate (should be something like normalize(customLight.position.xyz+ vpos) right?), so I’m asking out of pure curiosity - Why did the halfVector get the sack?

No, that would no longer make it uniform. It’s actually defined as normalize(normalize(lightsource.position) + vec3(0, 0, 1)). That’s pretty useless, but there you go.

I just checked in support for it for completeness’ sake. :slight_smile:

Ok, got that. Sorry to cause you extra work.

That was fast, thanks a bunch :slight_smile: Seems to work as explained, except that I can’t figure out the coordinate system of the position attribute. When I replaced dlight_someLight_to_model[2] from Cg with someLight.position, I got different (strange) result.

(Another little thing I noticed in the meantime is that while p3d_ColorScale input is available, p3d_Color is missing.)

In Panda terms, it’s in apiview space rather than view space. This is more useful for OpenGL lighting.

However, directional lights are a special case. They don’t have a position (you could argue they are infinitely far away) so it actually contains a vector, like: vec4(-direction.xyz, 0). This follows the OpenGL lighting model.

There is p3d_Color. It is a vertex attribute containing the vertex color.

I could also make it recognised as a uniform for when a flat color is applied. You wouldn’t be able to use it at the same time as vertex colors, but calling setColor on a node overrides vertex colors anyway, so maybe the ambiguity makes sense.

The p3d_Color is the per vertex color attribute, just like the gl_Color was in the old glsl versions.

In cg you do have vtx_color and a attr_color uniform. I think it’s easy to manualy add the color as a shader input (node.setShaderInput(‘glsl_color’, node.getColor()) if you need it, but maybe it also should be provided? Can’t say.

…anyway, I’m writin’ to slow on my phone, rdb was faster.

Ok, now I multiply position with trans_apiview_to_model, and get the expected result with directional and point lights. But… should I be doing this? Or if I should not do lighting computations in model space, then I would have to transform input vertices, normals, etc.

For p3d_Color, it slipped my mind that it is also vertex attribute. I used it as uniform in a fragment shader for some text. Now that I look more carefully, it was precisely to override the vertex colors, i.e. change text color.