Shaderless Shadows

Hi all,

Someone at the IRC asked how to implement shadows entirely without shaders (even not with the shader generator). Well, that’s fairly easy, using the ARB_shadow OpenGL extension. (It might also be possible to make it work without using some hacking, but I didn’t bother to do that.)

Since it uses TexGen and TexMatrix, it is not compatible with the Shader Generator. It is compatible with lighting and texturing though, it only takes up one texture stage. :slight_smile:

Here’s the modified and simplified shadows sample. Sorry for the mess, it was written rather quickly.

from pandac.PandaModules import *
import sys,os
loadPrcFileData("", "prefer-parasite-buffer #f")

import direct.directbase.DirectStart
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 *

font = loader.loadFont("cmss12")

# Function to put instructions on the screen.
def addInstructions(pos, msg):
  return OnscreenText(text=msg,style=1,fg=(1,1,1,1),mayChange=1,
  font=font,pos=(-1.3, pos), align=TextNode.ALeft, scale = .05,
  shadow=(0,0,0,1), shadowOffset=(0.1,0.1))

# Function to put title on the screen.
def addTitle(text):
  return OnscreenText(text=text,style=1,fg=(1,1,1,1),font=font,
  pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

class World(DirectObject):
  def __init__(self):
    # Preliminary capabilities check.
    
    if (base.win.getGsg().getSupportsDepthTexture()==0):
      self.t=addTitle("No Depth Textures")
      return
    if (base.win.getGsg().getSupportsShadowFilter()==0):
      self.t=addTitle("No ARB_shadow")
      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)
  
    if (LBuffer == None):
       self.t=addTitle("Buffer failed to setup")
       return

    Ldepthmap = Texture()
    LBuffer.addRenderTexture(Ldepthmap,
      GraphicsOutput.RTMBindOrCopy,
      GraphicsOutput.RTPDepthStencil)
    Ldepthmap.setWrapU(Texture.WMClamp)
    Ldepthmap.setWrapV(Texture.WMClamp)
    Ldepthmap.setMinfilter(Texture.FTShadow)
    Ldepthmap.setMagfilter(Texture.FTShadow)
    
    self.inst_t=addInstructions(.94,'T: stop/start Teapot')
    self.inst_x=addInstructions(.88,'Left/Right: cam angle')
  
    base.setBackgroundColor(0,0,0.2,1)
  
    base.camLens.setNearFar(1.0,150)
    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.teapot=loader.loadModel('teapot')
    self.teapot.reparentTo(render)
    self.teapot.setPos(0,-20,10)
    self.teapot.setTwoSided(True)
    self.tMovement=self.teapot.hprInterval(50,Point3(0,360,360))
    self.tMovement.loop()
  
    self.accept('escape',sys.exit)
  
    self.accept("arrow_left", self.incCamPos, [-1])
    self.accept("arrow_right", self.incCamPos, [1])
    self.accept("t", self.toggleInterval, [self.tMovement])
    self.accept("T", self.toggleInterval, [self.tMovement])
    self.accept("v", base.bufferViewer.toggleEnable)
    self.accept("V", base.bufferViewer.toggleEnable)
    self.accept("o", base.oobe)
  
    self.LCam=base.makeCamera(LBuffer)
    self.LCam.node().setScene(render)
    self.LCam.node().getLens().setFov(40)
    self.LCam.node().getLens().setNearFar(10,100)
    self.LCam.node().showFrustum()
    self.LCam.setPos(0,-40,25)
    self.LCam.lookAt(0,-10,0)
    # Reparent a normal spotlight to it, sharing the same lens
    self.Light=self.LCam.attachNewNode(Spotlight("Light"))
    self.Light.node().setLens(self.LCam.node().getLens())
    render.setLight(self.Light)

    # default values
    self.cameraSelection = 0
  
    # Enable texture projection of the depthmap onto the scene
    ts = TextureStage("shadow")
    render.projectTexture(ts, Ldepthmap, self.Light)
    
    self.incCamPos(0)

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

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

World()
run()

Works great!
But: on my card (ATI X1600, most recent driver v9.3) I have to comment out this piece:

    if (base.win.getGsg().getSupportsShadowFilter()==0):
      self.t=addTitle("No ARB_shadow")
      return

Then it works.
base.win.getGsg().getSupportsShadowFilter() always returns False although the card supports this kind of shadows without any problems.

EDIT: No, it is more complicated. I didn’t notice the console output:

:display:gsg:glgsg(error): at 2639 of c:\p\panda3d-1.5.4\panda\src\glstuff\glGraphicsStateGuardian_src.cxx : GL error 1282
...
:display(error): Deactivating wglGraphicsStateGuardian.

This error is repeated many times. But shadows are there, on the screen…

About the error: I ran into the exact same error before (at the same location). There’s a big chance it has already been fixed in 1.6.0.

Woah. I found this in the Panda source:

  if (_gl_vendor.substr(0,3)=="ATI") {
    // ATI drivers have never provided correct shadow support.
    _supports_shadow_filter = false;
  }

That’s odd. Maybe we should remove those lines, but they probably are there for a reason. drwr, do you know more about why this is here?

for the chronicles the same of birukoff goes for me and my old radeon
I’ll wait faithfully the 1.6 then!

I guess the only hope for us is integrated shadow mapping pro-rsoft works on… :smiley:

Hmm, try the updated code, with the getSupportsShadowFilter check commented out, see if the error still appears. If it does, wait for 1.6.0 and the crash might be gone, or wait for 1.7 where you will see the true shadows. :slight_smile:

pro_rsoft=shadowman! :smiley:

PS: why no shadow traces on the floor? ('cos is WIP I suppose)

Yeah, I failed to phase out the push bias correctly.

Wow, the error is gone. It is actually gone after removing:

    Lcolormap = Texture()
    LBuffer.addRenderTexture(Lcolormap,
      GraphicsOutput.RTMBindOrCopy,GraphicsOutput.RTPColor)

First I thought that errors are related to shadow extension check, but now it seems to be related to GraphicsOutput.RTPColor.

Yeah, I know. I’m pretty sure it will be fixed in 1.6.0.

Just to make sure… Is it what you see on your system?

I find that shadow is not very pronounced. Is there any way to control it?

No, I see a pitch black shadow here (it only lightens when I add an ambient light.) That’s weird. Maybe your card really doesn’t support ARB_shadow well, in which case I can understand the hacky code in glGSG.
You might be able to do some tricks with combine modes to get it blacker.

Yeah, this is what I worried about. ATI sucks… :cry:

Yeah, I don’t know anything about why that special exception was there, but it sounds like you might have figured it out. :wink:

David

You forgot to mention that it cannot create self shadow.

Yep, sorry, I forgot to mention.

I was playing around with lighting just to see how it could look, and tried out this script with a controllable actor. It doesn’t seem to project quite the same effect for me for some reason. The code is the same as the example, except I’ve made the actor respond to w-a-s-d commands and the terrain is a loaded model with some height variations.

Could you post your code?

class World(DirectObject):
    def __init__(self):
        base.camLens.setNearFar(1.0,10000)
        base.camLens.setFov(75)
        base.cam.setHpr(180,-20,0)
        
        #Load the first environment model
        self.environ = loader.loadModel("models/D_Landscape01")
        self.environ.reparentTo(render)
        self.environ.setPos(2,4,-2)
        self.environ.setScale(2)
        
        #Create primary Actor
        self.dChar_Create("dChar",Point3(0,0,0), Vec3(0,0,0))
        
        #Define acceptable key inputs
        self.acceptKeys()
        
        #Add function that controls character movement to the task manager
        taskMgr.add(self.prepareMove, "prepareMoveTask")
        self.task_prevtime = 0
        base.disableMouse()
        
        #Function to adjust camera's position and angle based on character movement
        self.cCamCorrect()
        
        ###===============Lighting Effects==================####
   
        if (base.win.getGsg().getSupportsDepthTexture()==0):
          self.t=addTitle("No Depth Textures")
          return
        if (base.win.getGsg().getSupportsShadowFilter()==0):
          self.t=addTitle("No ARB_shadow")
          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)
     
        if (LBuffer == None):
           self.t=addTitle("Buffer failed to setup")
           return
    
        Ldepthmap = Texture()
        LBuffer.addRenderTexture(Ldepthmap,
          GraphicsOutput.RTMBindOrCopy,
          GraphicsOutput.RTPDepthStencil)
        Ldepthmap.setWrapU(Texture.WMClamp)
        Ldepthmap.setWrapV(Texture.WMClamp)
        Ldepthmap.setMinfilter(Texture.FTShadow)
        Ldepthmap.setMagfilter(Texture.FTShadow)
       
        self.inst_t=addInstructions(.94,'T: stop/start Teapot')
        self.inst_x=addInstructions(.88,'Left/Right: cam angle')
     
        base.setBackgroundColor(0,0,0.2,1)
     
        base.camLens.setNearFar(1.0,150)
        base.camLens.setFov(75)
        
        self.LCam=base.makeCamera(LBuffer)
        self.LCam.node().setScene(render)
        self.LCam.node().getLens().setFov(40)
        self.LCam.node().getLens().setNearFar(10,100)
        self.LCam.node().showFrustum()
        self.LCam.setPos(20,10,25)
        self.LCam.lookAt(20,10,0)
        # Reparent a normal spotlight to it, sharing the same lens
        self.Light=self.LCam.attachNewNode(Spotlight("Light"))
        self.Light.node().setLens(self.LCam.node().getLens())
        render.setLight(self.Light)
    
        # default values
        self.cameraSelection = 0
     
        # Enable texture projection of the depthmap onto the scene
        ts = TextureStage("shadow")
        render.projectTexture(ts, Ldepthmap, self.Light)
        ###=================================================####

Here is the main bit of the program for setting up the models and lighting effects.

Darn, I can’t run that. Have something simpler with the default models?