Shadow Buffers with Hardware Instancing not showing up with custom camera shader

Hi, I was looking into Hardware instancing: shadows on instances and how shadows affect instances and have kind of hit a roadblock where I don’t really know how to pass. Currently for my own custom implemented lights, I’m applying a shader to the light camera node like how simplepbr does as I saw some decent performance gains out of doing this. It has worked completely flawlessly until now when I have tried to implement instancing. When I apply the shader, Instances are no longer included in the shadow buffer. When I don’t apply the shader, the hardware instances are visible but there is a performance hit.

Examples:

without the shader:

with the shader:

Note that the middle white cube is not instanced and it just there to prove that shadows are working. I mean when we compare the milliseconds 0.56 milliseconds with the shader compared to 0.75 milliseconds without the shader isn’t actually that large but it can scale is there are more lights in the scene. Also, I would just love to squeeze as much performance as I can.

Is this some funny thing to do with the shader generator? Is it maybe a render attribute I missed and didn’t add to the camera?

To understand your setup correctly, you have added a custom shader to the light camera node? Does it override the hardware instancing shader applied to the object? You can only have one shader applied to an object at a time.

You can set the hardware instancing shader with an override value so that it overrides what’s on the light.

The hardware instance shader is imbedded into my render pipeline. I just call setInstanceCount() to activate the instancing, then I feed a texture buffer with some transform information.

I have a simple shader that does indeed override any other attribute / shader on the node through using camera.setInitalState(state)

Okay, so the solution is clear, you have to do one of these things:

  1. Support hardware instancing in the shader applied to your light camera
  2. Set the shader on the node to override the one on the light camera
  3. Apply a new shader that combines both your light shader and your hardware instancing shader which is conditionally applied upon the node using a tag state

i have never figured out how to get the custom shaders instances to work with panda3d’s shaders
so its one or the other
you can apply the custom shader to all objects
and just use

plane.setShader(shadershadows)

plane.setShaderInput(“isInstanced”, False) #use this for unique objects you don’t intend on instancing
add the custom shader to both the plane and white cube and set isInstanced to False

i think the problem being the instances just don’t exist to panda3d’s shader system

Option 1: I don’t think will be possible. I supply the instance information per object so each instance object gets unique data.

Option 3: As with option one, It won’t be possible due to the uniqueness of each shader instance data input.

Option 2: I think this may be the only option that will work with the current system I have. When you say overide, you mean just a higher sort value?

Yea I have something similar to this. I just set the render pipeline shader on every node and have a value of is_instance where by default, it is set to 0 (off).

Yes, option 2 seems reasonable. Yes, what does your code look like to apply the shader to the node and to the camera? Applying it to the node will just be obj.setShader(shader, 10) or something.

I’m using states derived from shaders so it will be like this:


render_pipline_attribute = shaderAttrib.make(pipline_shader)

#this also means the every node should get a copy of this attribute
render.set_attrib(render_pipline_attribute, 2)

shadow_shader_attrib = shaderAttrib.make(camera_shader)

state = RenderState.makeEmpty()
state.add_attrib(shadow_shader_attrib, 1)
light_camera.setInitalState(state)

I’m using Attributes because I also included hardware skinning for animated models and need to set the flag for it as true.

if you want the lights shadow camera glsl code. I can also give it to you:

//vertex

#version 120

uniform mat4 p3d_ModelViewProjectionMatrix;
#ifdef ENABLE_SKINNING
uniform mat4 p3d_TransformTable[100];
#endif

attribute vec4 p3d_Vertex;
attribute vec4 p3d_Color;
attribute vec2 p3d_MultiTexCoord0;
#ifdef ENABLE_SKINNING
attribute vec4 transform_weight;
attribute vec4 transform_index;
#endif


varying vec4 v_color;
varying vec2 v_texcoord;

void main() {
#ifdef ENABLE_SKINNING
    mat4 skin_matrix = (
        p3d_TransformTable[int(transform_index.x)] * transform_weight.x +
        p3d_TransformTable[int(transform_index.y)] * transform_weight.y +
        p3d_TransformTable[int(transform_index.z)] * transform_weight.z +
        p3d_TransformTable[int(transform_index.w)] * transform_weight.w
    );
    vec4 vert_pos4 = skin_matrix * p3d_Vertex;
#else
    vec4 vert_pos4 = p3d_Vertex;
#endif

    v_color = p3d_Color;
    v_texcoord = p3d_MultiTexCoord0;
    gl_Position = p3d_ModelViewProjectionMatrix * vert_pos4;
}

//fragment

#version 120

#ifdef USE_330
    #define texture2D texture
#endif

uniform struct p3d_MaterialParameters {
    vec4 baseColor;
} p3d_Material;

uniform vec4 p3d_ColorScale;

uniform sampler2D p3d_TextureBaseColor;
varying vec4 v_color;
varying vec2 v_texcoord;

#ifdef USE_330
out vec4 o_color;
#endif

void main() {
    vec4 base_color = p3d_Material.baseColor * v_color * p3d_ColorScale * texture2D(p3d_TextureBaseColor, v_texcoord);
#ifdef USE_330
    o_color = base_color;
#else
    gl_FragColor = base_color;
#endif
}

Note that it’s just a direct copy of simplepbrs camera shader (as I found that it worked quite well before).

This code won’t actually work. state.add_attrib returns a new RenderState and leaves state untouched. So if you’re using this, then you haven’t actually been applying a shader to the light camera.

You can specify an override parameter to the add_attrib call and you can specify a priority parameter to the ShaderAttrib.make() call. I’m not 100% sure which actually does what we need, but you could specify both to be on the safe side.

looks like I found a better solution:

self._light.setShaderAuto()
state = RenderState.makeEmpty()
state = state.addAttrib(DepthTestAttrib.make(DepthTestAttrib.MLessEqual))
state = state.addAttrib(DepthWriteAttrib.make(True))
state = state.addAttrib(ColorWriteAttrib.make(False))
state = state.addAttrib(LightAttrib.makeAllOff())
state = state.addAttrib(FogAttrib.makeOff())
state = state.addAttrib(MaterialAttrib.makeOff())
state = state.addAttrib(ColorAttrib.makeOff())
#state = state.addAttrib(TransparencyAttrib.makeOff())
#state = state.addAttrib(AlphaTestAttrib.makeOff())
self._light.camera.setInitialState(state)

I just disabled a bunch of attributes on the camera and it gave much bigger performance boost than any shader I tried.

For my directional light test (on a minimal scene with some simple geometry), without disabling the attributes I got 1.5 milliseconds but when I disable attributes, it goes down to a much nicer 0.3 milliseconds while also still being able to shadow cast onto instances. NICE!

Its really confusing though because when I print light.camera.getInitalState() or light.camera.getState() It always came back as “empty” but it looks like in the background, these attributes still get applied (or are applied later when a node with a certain attribute is added). Maybe a missed something in the documentation that explains how camera render states work.

Anyway, I will mark this as the solution.

It’s probably especially the ColorWriteAttrib. We actually assign this by default as a light’s initial state. But it’s not surprising that other attributes may help as well.

getState is not the same as getInitialState (plain old getState/setState don’t affect anything for a camera - that would just apply to any models that were parented to the camera). Using getInitialState after setInitialState shouldn’t show an empty state.