Shadows converted to glsl


#1

As I understand it Cg is on the way out so the old Cg examples are less useful. I rewrote the shadows example to use somewhat modern glsl:

main.py

#!/usr/bin/env python
# Modified to use glsl by jww

from panda3d.core import *
import sys
import os

from direct.showbase.ShowBase import ShowBase
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.actor import Actor
from random import *

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), scale=.05,
                        shadow=(0, 0, 0, 1), parent=base.a2dTopLeft,
                        pos=(0.08, -pos - 0.04), align=TextNode.ALeft)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=.07,
                        parent=base.a2dBottomRight, align=TextNode.ARight,
                        pos=(-0.1, 0.09), shadow=(0, 0, 0, 1))


class World(DirectObject):

    def __init__(self):
        # Preliminary capabilities check.

        if not base.win.getGsg().getSupportsBasicShaders():
            self.t = addTitle(
                "Shadow Demo: Video driver reports that shaders are not supported.")
            return
        if not base.win.getGsg().getSupportsDepthTexture():
            self.t = addTitle(
                "Shadow Demo: Video driver reports that depth textures are not supported.")
            return

        # creating the offscreen buffer.

        winprops = WindowProperties.size(512, 512)
        props = FrameBufferProperties()
        props.setRgbColor(1)
        props.setAlphaBits(1)
        props.setDepthBits(1)
        LBuffer = base.graphicsEngine.makeOutput(
            base.pipe, "offscreen buffer", -2,
            props, winprops,
            GraphicsPipe.BFRefuseWindow,
            base.win.getGsg(), base.win)
        self.buffer = LBuffer

        if not LBuffer:
            self.t = addTitle(
                "Shadow Demo: Video driver cannot create an offscreen buffer.")
            return

        Ldepthmap = Texture()
        LBuffer.addRenderTexture(Ldepthmap, GraphicsOutput.RTMBindOrCopy,
                                 GraphicsOutput.RTPDepthStencil)
        if base.win.getGsg().getSupportsShadowFilter():
            Ldepthmap.setMinfilter(Texture.FTShadow)
            Ldepthmap.setMagfilter(Texture.FTShadow)

        # Adding a color texture is totally unnecessary, but it helps with
        # debugging.
        Lcolormap = Texture()
        LBuffer.addRenderTexture(Lcolormap, GraphicsOutput.RTMBindOrCopy,
                                 GraphicsOutput.RTPColor)

        self.inst_p = addInstructions(0.06, 'P : stop/start the Panda Rotation')
        self.inst_w = addInstructions(0.12, 'W : stop/start the Walk Cycle')
        self.inst_t = addInstructions(0.18, 'T : stop/start the Teapot')
        self.inst_l = addInstructions(0.24, 'L : move light source far or close')
        self.inst_v = addInstructions(0.30, 'V : View the Depth-Texture results')
        self.inst_u = addInstructions(0.36, 'U : toggle updating the shadow map')
        self.inst_x = addInstructions(0.42, 'Left/Right Arrow : switch camera angles')
        self.inst_a = addInstructions(0.48, 'Something about A/Z and push bias')

        base.setBackgroundColor(0, 0, 0.2, 1)

        base.camLens.setNearFar(1.0, 10000)
        base.camLens.setFov(75)
        base.disableMouse()

        # Load the scene.

        floorTex = loader.loadTexture('maps/envir-ground.jpg')
        cm = CardMaker('')
        cm.setFrame(-2, 2, -2, 2)
        floor = render.attachNewNode(PandaNode("floor"))
        for y in range(12):
            for x in range(12):
                nn = floor.attachNewNode(cm.generate())
                nn.setP(-90)
                nn.setPos((x - 6) * 4, (y - 6) * 4, 0)
        floor.setTexture(floorTex)
        floor.flattenStrong()

        self.pandaAxis = render.attachNewNode('panda axis')
        self.pandaModel = Actor.Actor('panda-model', {'walk': 'panda-walk4'})
        self.pandaModel.reparentTo(self.pandaAxis)
        self.pandaModel.setPos(9, 0, 0)
        self.pandaModel.setShaderInput("scale", (0.01, 0.01, 0.01, 1.0))
        self.pandaWalk = self.pandaModel.actorInterval('walk', playRate=1.8)
        self.pandaWalk.loop()
        self.pandaMovement = self.pandaAxis.hprInterval(
            20.0, LPoint3(-360, 0, 0), startHpr=LPoint3(0, 0, 0))
        self.pandaMovement.loop()

        self.teapot = loader.loadModel('teapot')
        self.teapot.reparentTo(render)
        self.teapot.setPos(0, -20, 10)
        self.teapot.setShaderInput("texDisable", (1, 1, 1, 1))
        self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360))
        self.teapotMovement.loop()

        self.accept('escape', sys.exit)

        self.accept("arrow_left", self.incrementCameraPosition, [-1])
        self.accept("arrow_right", self.incrementCameraPosition, [1])
        self.accept("p", self.toggleInterval, [self.pandaMovement])
        self.accept("t", self.toggleInterval, [self.teapotMovement])
        self.accept("w", self.toggleInterval, [self.pandaWalk])
        self.accept("v", base.bufferViewer.toggleEnable)
        self.accept("u", self.toggleUpdateShadowMap)
        self.accept("l", self.incrementLightPosition, [1])
        self.accept("o", base.oobe)
        self.accept('a', self.adjustPushBias, [1.1])
        self.accept('z', self.adjustPushBias, [0.9])

        self.LCam = base.makeCamera(LBuffer)
        self.LCam.node().setScene(render)
        self.LCam.node().getLens().setFov(40)
        self.LCam.node().getLens().setNearFar(10, 100)

        # default values
        self.pushBias = 0.04
        self.ambient = 0.2
        self.cameraSelection = 0
        self.lightSelection = 0

        # setting up shader
        render.setShaderInput('light', self.LCam)
        render.setShaderInput('Ldepthmap', Ldepthmap)
        render.setShaderInput('ambient', (self.ambient, 0, 0, 1.0))
        render.setShaderInput('texDisable', (0, 0, 0, 0))
        render.setShaderInput('scale', (1, 1, 1, 1))

        # Put a shader on the Light camera.
        lci = NodePath(PandaNode("Light Camera Initializer"))
        lci.setShader(Shader.load(Shader.SL_GLSL, "caster.vert.glsl", "caster.frag.glsl"))
        self.LCam.node().setInitialState(lci.getState())

        # Put a shader on the Main camera.
        # Some video cards have special hardware for shadow maps.
        # If the card has that, use it.  If not, use a different
        # shader that does not require hardware support.

        mci = NodePath(PandaNode("Main Camera Initializer"))
        if base.win.getGsg().getSupportsShadowFilter():
            mci.setShader(Shader.load(Shader.SL_GLSL, "shadow.vert.glsl", "shadow.frag.glsl"))
        else:
            mci.setShader(Shader.load(Shader.SL_GLSL, "shadow-nosupport.vert.glsl", "shadow-nosupport.frag.glsl"))

        base.cam.node().setInitialState(mci.getState())

        self.incrementCameraPosition(0)
        self.incrementLightPosition(0)
        self.adjustPushBias(1.0)

    def toggleInterval(self, ival):
        if ival.isPlaying():
            ival.pause()
        else:
            ival.resume()

    def toggleUpdateShadowMap(self):
        self.buffer.active = not self.buffer.active

    def incrementCameraPosition(self, n):
        self.cameraSelection = (self.cameraSelection + n) % 6
        if (self.cameraSelection == 0):
            base.cam.reparentTo(render)
            base.cam.setPos(30, -45, 26)
            base.cam.lookAt(0, 0, 0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 1):
            base.cam.reparentTo(self.pandaModel)
            base.cam.setPos(7, -3, 9)
            base.cam.lookAt(0, 0, 0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 2):
            base.cam.reparentTo(self.pandaModel)
            base.cam.setPos(-7, -3, 9)
            base.cam.lookAt(0, 0, 0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 3):
            base.cam.reparentTo(render)
            base.cam.setPos(7, -23, 12)
            base.cam.lookAt(self.teapot)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 4):
            base.cam.reparentTo(render)
            base.cam.setPos(-7, -23, 12)
            base.cam.lookAt(self.teapot)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 5):
            base.cam.reparentTo(render)
            base.cam.setPos(1000, 0, 195)
            base.cam.lookAt(0, 0, 0)
            self.LCam.node().showFrustum()

    def incrementLightPosition(self, n):
        self.lightSelection = (self.lightSelection + n) % 2
        if (self.lightSelection == 0):
            self.LCam.setPos(0, -40, 25)
            self.LCam.lookAt(0, -10, 0)
            self.LCam.node().getLens().setNearFar(10, 100)
        if (self.lightSelection == 1):
            self.LCam.setPos(0, -600, 200)
            self.LCam.lookAt(0, -10, 0)
            self.LCam.node().getLens().setNearFar(10, 1000)

    def shaderSupported(self):
        return base.win.getGsg().getSupportsBasicShaders() and \
               base.win.getGsg().getSupportsDepthTexture() and \
               base.win.getGsg().getSupportsShadowFilter()

    def adjustPushBias(self, inc):
        self.pushBias *= inc
        self.inst_a.setText(
            'A/Z: Increase/Decrease the Push-Bias [%F]' % self.pushBias)
        render.setShaderInput('push', self.pushBias)

if __name__ == '__main__':
    base = ShowBase()
    w = World()
    base.run()

caster.vert.glsl

#version 150

in vec4 p3d_Vertex;
uniform mat4x4 p3d_ModelViewProjectionMatrix;
uniform vec4 scale;

out vec4 l_pos;

void main() 
{
  vec4 position = p3d_Vertex * scale;
  l_pos = p3d_ModelViewProjectionMatrix*position;
  gl_Position = l_pos;
}

caster.frag.glsl

#version 150

in vec4 l_pos;

out vec4 color;

void main()
{
  float z = (l_pos.z / l_pos.w) * 0.5 + 0.5;
  color = vec4(z,z,z,1);
}

shadow.vert.glsl

#version 150

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

uniform mat4x4 trans_model_to_clip_of_light;
uniform mat4x4 p3d_ModelViewProjectionMatrix;
uniform vec4 mspos_light;
uniform vec4 ambient;
uniform vec4 scale;
uniform float push;

out vec4 l_position;
out vec2 l_texcoord0;
out vec4 l_shadowcoord;
out float l_smooth;
out vec4 l_lightclip;

void main()
{
    vec4 position = p3d_Vertex * scale;

    // vertex position
    l_position = p3d_ModelViewProjectionMatrix*position;
    gl_Position = l_position; 

    // Pass through texture coordinate for main texture.
    l_texcoord0 = p3d_MultiTexCoord0;

    // Calculate the surface lighting factor.
    l_smooth = clamp(dot(p3d_Normal, normalize(mspos_light.xyz - position.xyz)),0,1);

    // Calculate light-space clip position.
    vec4 pushed = position + vec4(p3d_Normal * push, 0);
    l_lightclip = trans_model_to_clip_of_light * pushed;

    // Calculate shadow-map texture coordinates.
    l_shadowcoord = l_lightclip * vec4(0.5, 0.5, 0.5, 1.0) +
                    l_lightclip.w * vec4(0.5, 0.5, 0.5, 0.0);
}

shadow.frag.glsl

#version 150

in vec4 l_position;
in vec2 l_texcoord0;
in vec4 l_shadowcoord;
in float l_smooth;
in vec4 l_lightclip;

uniform sampler2D p3d_Texture0;
uniform sampler2DShadow Ldepthmap;
uniform vec4 ambient;
uniform vec4 texDisable;

out vec4 color;

void main()
{
    vec3 circleoffs = vec3(l_lightclip.xy / l_lightclip.w, 0);
    float falloff = clamp(1.0 - dot(circleoffs, circleoffs),0,1);
    vec4 baseColor = clamp(texture(p3d_Texture0, l_texcoord0) + texDisable,0,1);
    
    float shade = textureProj(Ldepthmap, l_shadowcoord);
    color = baseColor * (falloff * shade * l_smooth + ambient.x);
}

shadow-nosupport.vert.glsl

#version 150

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

uniform mat4x4 trans_model_to_clip_of_light;
uniform mat4x4 p3d_ModelViewProjectionMatrix;
uniform vec4 mspos_light;
uniform vec4 ambient;
uniform vec4 scale;
uniform float push;

out vec4 l_position;
out vec2 l_texcoord0;
out vec4 l_shadowcoord;
out float l_smooth;
out vec4 l_lightclip;

void main()
{
    vec4 position = p3d_Vertex * scale;

    // vertex position
    l_position = p3d_ModelViewProjectionMatrix*position;
    gl_Position = l_position; 

    // Pass through texture coordinate for main texture.
    l_texcoord0 = p3d_MultiTexCoord0;

    // Calculate the surface lighting factor.
    l_smooth = clamp(dot(p3d_Normal, normalize(mspos_light.xyz - position.xyz)),0,1);

    // Calculate light-space clip position.
    vec4 pushed = position + vec4(p3d_Normal * push, 0);
    l_lightclip = trans_model_to_clip_of_light * pushed;

    // Calculate shadow-map texture coordinates.
    l_shadowcoord = l_lightclip * vec4(0.5, 0.5, 0.5, 1.0) +
                    l_lightclip.w * vec4(0.5, 0.5, 0.5, 0.0);
}

shadow-nosupport.frag.glsl

#version 150

in vec4 l_position;
in vec2 l_texcoord0;
in vec4 l_shadowcoord;
in float l_smooth;
in vec4 l_lightclip;

uniform sampler2D p3d_Texture0;
uniform sampler2D Ldepthmap;
uniform vec4 ambient;
uniform vec4 texDisable;

out vec4 color;

void main()
{
    vec3 circleoffs = vec3(l_lightclip.xy / l_lightclip.w, 0);
    float falloff = clamp(1.0 - dot(circleoffs, circleoffs),0,1);
    vec4 baseColor = clamp(texture(p3d_Texture0, l_texcoord0) + texDisable,0,1);
    
    vec4 proj = l_shadowcoord / l_shadowcoord.w;
    float mapval = texture(Ldepthmap, proj.xy).r;
    float shade = (mapval > proj.z) ? 1 : 0;
    color = baseColor * (falloff * shade * l_smooth + ambient.x);
}