Example of a pixel lighting Shader GLSL

from panda3d.core import Shader
from direct.showbase.ShowBase import ShowBase

def shader():
    return Shader.make(Shader.SL_GLSL,
vertex="""
#version 120

varying vec3 v_FragmentPosition;
varying vec3 v_FragmentNormal;

attribute vec2 p3d_MultiTexCoord0;
attribute vec4 p3d_Vertex;
attribute vec3 p3d_Normal;

uniform mat4 p3d_ModelViewProjectionMatrix;

void main( void )
{
    gl_Position    = p3d_ModelViewProjectionMatrix * p3d_Vertex;
    gl_TexCoord[0].xy = p3d_MultiTexCoord0;
    
    v_FragmentPosition  = p3d_Vertex.xyz;
    v_FragmentNormal  = p3d_Normal;
}

""",

fragment="""
#version 120

uniform sampler2D p3d_Texture0;
uniform vec3 pos_light;
uniform vec3 pos_camera;

varying vec3 v_FragmentPosition;
varying vec3 v_FragmentNormal;

const vec4  k_LightColor = vec4(1.0, 0.6, 0.3, 1.0);
const float k_Shininess = 64.0;

const float k_ConstAttenuation = 0.5;
const float k_LinearAttenuation = 0.05;
const float k_QuadricAttenuation = 0.001;

void main( void )
{
    vec3  L = pos_light - v_FragmentPosition;

    // ** Used in the calculation of light extinction
    float distance = length( L );

    L = normalize( L );
    vec3  N = normalize( v_FragmentNormal );

    // ** The H vector is used to calculate the gloss (specularity) of a fragment
    vec3  E = normalize( pos_camera - v_FragmentPosition );
    vec3  H = normalize( L + E ); // ** Half-vector

    // ** The calculation coefficient. attenuations
    float attenuation = 1.0 / ( k_ConstAttenuation + k_LinearAttenuation * distance + k_QuadricAttenuation * distance * distance );

    float diffuse = clamp( dot( L, N ), 0.0, 1.0 );
    float specular  = pow( clamp( dot( N, H ), 0.0, 1.0 ), k_Shininess );
  
    if( diffuse <= 0.0 ){
        specular = 0.0; 
        }
  
    vec4 diffuseColor  = diffuse * k_LightColor * attenuation;
    vec4 specularColor = specular * k_LightColor * attenuation;
    vec4 ambientColor = vec4(0.01, 0.01, 0.01, 0.01);
    vec4 emissionColor = vec4(0.0, 0.0, 0.0, 0.0);

    gl_FragColor = ( diffuseColor + specularColor + ambientColor + emissionColor) * texture2D(p3d_Texture0, gl_TexCoord[0].xy );
}
""")

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath

class TestPointlight(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        
        lihgt = NodePath("lihgt")
        lihg_m = loader.load_model("models/icosphere")
        lihg_m.reparent_to(lihgt)
        lihgt.setPos(-5, 30, 15)
        lihgt.reparent_to(render)
        
        self.room = loader.load_model("models/abstractroom")
        self.room.reparent_to(render)
        self.room.set_shader(shader())
        self.room.set_shader_input("pos_light", lihgt.getPos());
        
        taskMgr.add(self.update, 'camera.update')

    def update(self, task):
        self.room.set_shader_input("pos_camera", camera.getPos());
        return task.cont

demo = TestPointlight()
demo.run()

1 Like

Cool. I am learning shaders with Panda3D right now too. I’ve implemented some lighting models already. I advise to do the same, it’s useful for self-education.

I think I need to implement bump mapping and shadows for education. This is my next step, also to learn how to work with buffers and stencils. Although I know how to use buffers, I have had experience. For example, I need to cut off glare if the light source is not visible.

Option without a task for transmitting the camera position.

from panda3d.core import Shader
from direct.showbase.ShowBase import ShowBase

def shader():
    return Shader.make(Shader.SL_GLSL,
vertex="""
#version 120

varying vec3 v_FragmentPosition;
varying vec3 v_FragmentNormal;

attribute vec2 p3d_MultiTexCoord0;
attribute vec4 p3d_Vertex;
attribute vec3 p3d_Normal;

uniform mat4 p3d_ModelViewProjectionMatrix;

void main( void )
{
    gl_Position    = p3d_ModelViewProjectionMatrix * p3d_Vertex;
    gl_TexCoord[0].xy = p3d_MultiTexCoord0;
    
    v_FragmentPosition  = p3d_Vertex.xyz;
    v_FragmentNormal  = p3d_Normal;
}

""",

fragment="""
#version 120

uniform sampler2D p3d_Texture0;
uniform vec3 pos_light;

uniform mat4 p3d_ViewMatrixInverse;

varying vec3 v_FragmentPosition;
varying vec3 v_FragmentNormal;

const vec4  k_LightColor = vec4(1.0, 0.6, 0.3, 1.0);
const float k_Shininess = 64.0;

const float k_ConstAttenuation = 0.5;
const float k_LinearAttenuation = 0.05;
const float k_QuadricAttenuation = 0.001;

void main( void )
{
    vec3  L = pos_light - v_FragmentPosition;

    // ** Used in the calculation of light extinction
    float distance = length( L );

    L = normalize( L );
    vec3  N = normalize( v_FragmentNormal );

    // ** The H vector is used to calculate the gloss (specularity) of a fragment
    vec3  E = normalize( p3d_ViewMatrixInverse[3].xyz - v_FragmentPosition );
    vec3  H = normalize( L + E ); // ** Half-vector

    // ** The calculation coefficient. attenuations
    float attenuation = 1.0 / ( k_ConstAttenuation + k_LinearAttenuation * distance + k_QuadricAttenuation * distance * distance );

    float diffuse = clamp( dot( L, N ), 0.0, 1.0 );
    float specular  = pow( clamp( dot( N, H ), 0.0, 1.0 ), k_Shininess );
  
    if( diffuse <= 0.0 ){
        specular = 0.0; 
        }
  
    vec4 diffuseColor  = diffuse * k_LightColor * attenuation;
    vec4 specularColor = specular * k_LightColor * attenuation;
    vec4 ambientColor = vec4(0.01, 0.01, 0.01, 0.01);
    vec4 emissionColor = vec4(0.0, 0.0, 0.0, 0.0);

    gl_FragColor = ( diffuseColor + specularColor + ambientColor + emissionColor) * texture2D(p3d_Texture0, gl_TexCoord[0].xy );
}
""")

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath

class TestPointlight(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        
        lihgt = NodePath("lihgt")
        lihg_m = loader.load_model("models/icosphere")
        lihg_m.reparent_to(lihgt)
        lihgt.setPos(-5, 30, 15)
        lihgt.reparent_to(render)
        
        self.room = loader.load_model("models/abstractroom")
        self.room.reparent_to(render)
        self.room.set_shader(shader())
        self.room.set_shader_input("pos_light", lihgt.getPos());

demo = TestPointlight()
demo.run()

If I may, let me note that you can, I believe, also pass the “light” object itself in as a shader-input, and then in your shader use something like “<space>_light” (replacing “<space>” with the relevant space-identifier) to get the light’s position in the relevant space.

Space identifiers include things like “wspos” (world-space) and “mspos” (model-space). See here for more, noting that while that page lists the inputs as CG inputs, they are, I believe, also available in GLSL with a bit of syntax-reinterpretation.

This has the advantage that if the light’s position changes, the input should update, if I’m not much mistaken.

Something like this:

In your Python code:

myNodePath.setShaderInput("light", self.light)

In your shader:

uniform vec4 wspos_light; // The world-space position of the light

Thank you @rdb already reminded me about this in another topic. However, I planned to write my own class of color sources. But there is a problem to extend the Shader inputs for your classes, you need to program in C++. I don’t know how to do it yet. At the moment, I’m thinking that it’s easier to add properties to Panda classes than to bother with uniforms buffers.

Aah, fair enough! I’m glad that you have it covered, then! :slight_smile:

Hmm… You could have your custom classes internally add standard inputs, I imagine. But then, if you have a better idea in mind, I’ll leave you to it–I mention the former only against the chance that you hadn’t thought of it already.

The idea of using uniform blocks is faster. At this point, I should pass via: set_shader_input()

I don’t think the Panda API supports this type of binding.

Okay, neat! I didn’t know about those! Fair enough, then. :slight_smile:

Panda supports ShaderBuffer class as an argument type for set_shader_input. Maybe it’s possible this way, but there’s no info how to write data in a ShaderBuffer, except raw bytes:
https://docs.panda3d.org/1.10/python/reference/panda3d.core.ShaderBuffer

ShaderBuffer is for SSBOs. It’s mostly meant for shaders to communicate with each other, but not really for passing data to a shader.

We do not currently support passing in uniform blocks, but we’re planning on it.

1 Like