Hardware instancing: shadows on instances

scripts i used to make this work
from this post from rdb Texture Buffers for hardware instanced geometry - #8 by rdb
wezu’s shadows made easy

import random
from panda3d.core import *
loadPrcFileData("init-ms-meter", """
show-frame-rate-meter true
frame-rate-meter-ms-text-pattern %0.2f ms
""")

from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

#vertex shader:
v_shader='''#version 330

struct p3d_LightSourceParameters {
  vec4 color;
  vec3 spotDirection; 
  sampler2DShadow shadowMap; 
  mat4 shadowViewMatrix; // Use shadowViewMatrix for view space
}; 

uniform p3d_LightSourceParameters my_light;
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat3 p3d_NormalMatrix;
uniform mat4 p3d_ModelViewMatrix;
uniform bool isInstanced;

in vec4 p3d_Vertex;
in vec3 p3d_Normal;
in vec2 p3d_MultiTexCoord0;
in vec4 offset;

out vec2 uv;
out vec4 shadow_uv;
out vec3 normal;
out vec4 fragPos;

void main()
{
    vec4 vertexPosition = p3d_Vertex;
    if (isInstanced) {
        vertexPosition += offset;
    }
    //position    
    gl_Position = p3d_ModelViewProjectionMatrix * vertexPosition;
    //normal      
    normal = p3d_NormalMatrix * p3d_Normal;
    //uv
    uv = p3d_MultiTexCoord0;
    //shadows
    shadow_uv = my_light.shadowViewMatrix * (p3d_ModelViewMatrix * vertexPosition); // Use shadowViewMatrix
    //frag position
    fragPos = p3d_ModelViewMatrix * vertexPosition; // Add this line
}'''
#fragment shader
f_shader='''#version 330

struct p3d_LightSourceParameters {
  vec4 color;
  vec3 spotDirection; 
  sampler2DShadow shadowMap; 
  mat4 shadowViewMatrix; // Use shadowViewMatrix for view space
}; 

uniform p3d_LightSourceParameters my_light;
uniform sampler2D p3d_Texture0;
uniform vec3 camera_pos;
uniform float shadow_blur;
uniform vec4 ambientLightColor;
uniform vec4 fogColor;
uniform float fogStart;
uniform float fogEnd;

in vec2 uv;
in vec4 shadow_uv;
in vec3 normal;
in vec4 fragPos;

out vec4 color;

float textureProjSoft(sampler2DShadow tex, vec4 uv, float bias, float blur)
{
    float result = textureProj(tex, uv, bias);
    result += textureProj(tex, vec4(uv.xy + vec2( -0.326212, -0.405805)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(-0.840144, -0.073580)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(-0.695914, 0.457137)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(-0.203345, 0.620716)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.962340, -0.194983)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.473434, -0.480026)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.519456, 0.767022)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.185461, -0.893124)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.507431, 0.064425)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(0.896420, 0.412458)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(-0.321940, -0.932615)*blur, uv.z-bias, uv.w));
    result += textureProj(tex, vec4(uv.xy + vec2(-0.791559, -0.597705)*blur, uv.z-bias, uv.w));
    return result/13.0;
}    

void main()
{
    //base color
    vec3 ambient = ambientLightColor.rgb; // Use the ambient light color from the uniform
    //texture        
    vec4 tex = texture(p3d_Texture0, uv);        
    //light ..sort of, not important
    vec3 light = my_light.color.rgb * max(dot(normalize(normal), -my_light.spotDirection), 0.0);
    
    //shadows
    //float shadow = textureProj(my_light.shadowMap, shadow_uv); //meh :|
    float shadow = textureProjSoft(my_light.shadowMap, shadow_uv, 0.0001, shadow_blur); //yay! :)
    
    //make the shadow brighter
    shadow = 0.5 + shadow * 0.5;
    
    //fog calculation
    float fogFactor = clamp((fogEnd - length(fragPos.xyz)) / (fogEnd - fogStart), 0.0, 1.0);
    vec4 foggedColor = mix(fogColor, vec4(tex.rgb * (light * shadow + ambient), tex.a), fogFactor);
    
    color = foggedColor;
}'''                    


def setup_instancing(nodefunc, positionlist=[], seed=29, fromvalue=0, tovalue=250, new_location=(0, 0, 0), zlevel=True, total_instances=0, poswriter=None):
    gnode = nodefunc.find("**/+GeomNode").node()

    # Check if the "offset" column already exists
    vdata = gnode.modifyGeom(0).modifyVertexData()
    format = GeomVertexFormat(gnode.getGeom(0).getVertexData().getFormat())
    if not format.hasColumn("offset"):
        iformat = GeomVertexArrayFormat()
        iformat.setDivisor(1)
        iformat.addColumn("offset", 4, Geom.NT_stdfloat, Geom.C_other)
        format.addArray(iformat)
        format = GeomVertexFormat.registerFormat(format)
        vdata.setFormat(format)

    if zlevel == None:
        sorted_positions = positionlist
    else:
        sorted_positions = sorted(positionlist, key=lambda pos: pos[2], reverse=zlevel)
    
    if poswriter is None:
        poswriter = GeomVertexWriter(vdata.modifyArray(2), 0)
    
    for i in range(fromvalue, tovalue):
        x, y, z = sorted_positions[i]
        poswriter.add_data3(x + new_location[0], y + new_location[1], z + new_location[2])

    total_instances += (tovalue - fromvalue)

    nodefunc.setInstanceCount(total_instances)
    nodefunc.node().setBounds(OmniBoundingVolume())
    nodefunc.node().setFinal(True)
    return total_instances, poswriter

base = ShowBase()

node = Actor('panda-model', {'walk' : 'panda-walk4'})
node.loop('walk')
node.setScale(0.01)

shadershadows = Shader.make(Shader.SL_GLSL,v_shader, f_shader)
node.setShader(shadershadows)
node.reparentTo(render)

def position_gen(value=1000, seed=29, area_width=486, area_height=486, height_map=None):
    random.seed(seed)
    occupied_positions = []

    for i in range(value):
        x = random.uniform(0, area_width)
        y = random.uniform(0, area_height)
        z = 0.01
        pos = (int(x), int(y), z)
        occupied_positions.append(pos)
    return occupied_positions

positionlist = position_gen(1000, 12, 486, 486)
sun = Spotlight("Spot")
sun_path = render.attachNewNode(sun)
sun_path.node().set_shadow_caster(True, 512, 512)
sun_path.node().set_color((0.9, 0.9, 0.8, 1.0))
sun_path.node().showFrustum()
sun_path.node().get_lens().set_fov(20)
sun_path.node().get_lens().set_near_far(0.1, 400)
sun_path.setHpr(0,-90,0)
sun_path.setPos(0,0,90)

node.setShaderInput("isInstanced", True)

# Create a texture to store the shadow map
render.setShaderInput("ambientLightColor", (0.1, 0.1, 0.1, 1.0))
render.set_shader_input('my_light',sun_path)
render.set_shader_input('shadow_blur',0.5)
render.setShaderInput("fogColor", (0.5, 0.5, 0.5, 1.0)) # Set the fog color
render.setShaderInput("fogStart", 50.0) # Set the fog start distance
render.setShaderInput("fogEnd", 450.0) # Set the fog end distance

total_instances = 0
poswriter = None
total_instances, poswriter = setup_instancing(node, positionlist, seed=29, fromvalue=0, tovalue=250, new_location=(0,0,0), zlevel=None, total_instances=total_instances, poswriter=poswriter)
total_instances, poswriter = setup_instancing(node, positionlist, seed=29, fromvalue=250, tovalue=500, new_location=(486,486,0), zlevel=None, total_instances=total_instances, poswriter=poswriter)

# Adding more instances to the existing node
total_instances, poswriter = setup_instancing(node, positionlist, seed=29, fromvalue=500, tovalue=750, new_location=(0,0,900), zlevel=None, total_instances=total_instances, poswriter=poswriter)

base.trackball.node().set_pos(4.7, 112.7, -9.7)
base.trackball.node().set_hpr(61.5281, 12.0915, -18.2124)

base.run()

Do I understand correctly that you’re specifically trying to use out-of-the-box shadows with objects that have a custom shader applied?

If so, then, to the best of my knowledge, that doesn’t work: Panda’s out-of-the-box shadows are rendered via an automatic shader–which is then overridden by the custom shader.

(You’re also not applying Panda’s auto-shader, which I think is required for out-of-the-box shadows. But as noted, even if you were, it would be overridden by the custom shader.)

Instead, I think that it’s expected that if you’re making your own shaders, you then further implement shadows in those shaders.

(It’s possible that one of the non-out-of-the-box rendering modules, like simplepbr, is capable of applying shadows to objects that have custom shaders–but I don’t know whether any do in fact have that capacity.)

1 Like

yeah thought that would be case i have explored using camera depth buffer stuff to get shadows casts but no luck with that / not great performance, i think may end going with just ambient light on the custom shader and some trickery by replacing the instances with panda3d’s intanceTo when the player gets close, so i can use the auto shader

1 Like

Depending on the distance to your models, you could also perhaps explore a reduction in the fidelity of the shadows, or “cascaded shadow maps”, which employ lower-quality shadows at range.

1 Like

i made little mistake the performance problem wasn’t do to the depth buffer camera it was just to many instances at once, if i ever figure out how to get shadows casted on to the instances ill look into to those

1 Like

has been solved edited with solution

there is still a warning message not sure if its a big deal or not
:display:gsg:glgsg(warning): light.shadowMatrix inputs are deprecated; use shadowViewMatrix instead, which transforms from view space instead of model space.

1 Like

Ah, I’m glad that you got it working! Well done! :slight_smile:

1 Like

Yes, you should deal with the shadowMatrix warning, or you will get poor performance, and this member will be removed soon. Instead use shadowViewMatrix and use the view-space position, ie. fragPos in your case.

1 Like

alright it is fixed