WIP - Fast offscreen soft particles

I’m trying to implement this : http://http.developer.nvidia.com/GPUGems3/gpugems3_ch23.html

I think it’s almost working, can someone help me?
I don’t know if the depth buffer is really linear.
I don’t know if it should be the expected result.

PS: I can’t copy paste the code from gedit properly, it loses the correct indentation, please use the code in the files.

FILES
http://www.sendspace.com/file/m7173i

GUIDE

DETAILS
[color=indigo]Application class

#####################################################
##             Fast Offscreen Particles            ##
#####################################################

### Imports ###
from panda3d.core import *
loadPrcFileData('', 'dump-generated-shaders 1')
loadPrcFileData('', 'compressed-textures 0')
loadPrcFileData('', 'show-buffers 1')
loadPrcFileData('', 'basic-shaders-only 0')
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from direct.particles.ForceGroup import ForceGroup
from direct.filter.FilterManager import *

### Class constants ###
GREY = Vec4(.5, .5, .5, .5)
BLACK = Vec4(0, 0, 0, 1)
WHITE = Vec4(1, 1, 1, 1)
DIVIDOR = 1 # 1 for full details
PARTICLE_EFFECT = Filename("steam.ptf") # "fireish.ptf" "steam.ptf"

class Application(DirectObject):

    def __init__(self):
	self.setup_scene()
	self.setup_particles()
	self.setup_buffers()
	self.setup_cameras()
	self.setup_passes()
        
    def setup_scene(self):

	self.particles = render.attachNewNode("particles")
        self.scene = render.attachNewNode("scene")

	sphere = loader.loadModel("charbon.egg")
	sphere.setScale(2)
	tex_sphere = loader.loadTexture('charbon.jpg')
	sphere.setTexture(tex_sphere)
	sphere.reparentTo(self.scene)

    def setup_buffers(self):
	
	self.particles_map = Texture() # particles albedo buffer
	self.particles_depth_map = Texture() # particles linear depth buffer

	self.scene_depth_map = Texture() # scene linear depth buffer
	self.scene_color_map = Texture() # scene albedo buffer

        self.z_buffer_particles = base.win.makeTextureBuffer('z_particles', base.win.getXSize()/DIVIDOR, base.win.getYSize()/DIVIDOR, self.particles_depth_map)
	self.z_buffer_scene = base.win.makeTextureBuffer('z_scene', base.win.getXSize(), base.win.getYSize(), self.scene_depth_map)
	self.color_buffer_scene = base.win.makeTextureBuffer('color_scene', base.win.getXSize(), base.win.getYSize(), self.scene_color_map)
        self.color_buffer_particles = base.win.makeTextureBuffer('particles', base.win.getXSize()/DIVIDOR, base.win.getYSize()/DIVIDOR, self.particles_map)

    def setup_cameras(self):

	# Cameras creation
        self.scene_cam = base.makeCamera(self.color_buffer_scene)
	self.displayRegion_scene = base.win.makeDisplayRegion()
	self.displayRegion_scene.setCamera(self.scene_cam)
        self.particles_cam = base.makeCamera(self.color_buffer_particles)
        self.particles_cam.reparentTo(base.cam)
	self.z_scene_cam = base.makeCamera(self.z_buffer_scene)
        self.z_scene_cam.reparentTo(base.cam)
	self.z_particles_cam = base.makeCamera(self.z_buffer_particles)
        self.z_particles_cam.reparentTo(base.cam)

	# Clear colors
	base.win.setClearColorActive(True)
	base.win.setClearColor(GREY)
	self.z_particles_cam.node().getDisplayRegion(0).setClearColor(WHITE)
        self.z_particles_cam.node().getDisplayRegion(0).setClearColorActive(1)
	self.z_scene_cam.node().getDisplayRegion(0).setClearColor(WHITE)
        self.z_scene_cam.node().getDisplayRegion(0).setClearColorActive(1)
	self.particles_cam.node().getDisplayRegion(0).setClearColor(GREY)
        self.particles_cam.node().getDisplayRegion(0).setClearColorActive(1)
        self.scene_cam.node().getDisplayRegion(0).setClearColor(BLACK)
        self.scene_cam.node().getDisplayRegion(0).setClearColorActive(1)

	# Masks
	scene_mask = BitMask32(1)
        particles_mask = BitMask32(2)
        self.scene_cam.node().setCameraMask(scene_mask)
        self.particles_cam.node().setCameraMask(particles_mask)
	self.z_scene_cam.node().setCameraMask(scene_mask)
	self.z_particles_cam.node().setCameraMask(particles_mask)
        self.scene.hide(particles_mask)
	self.particles.hide(scene_mask)
	
	# Cameras activation
	base.cam.node().setActive(0)
        self.scene_cam.node().setActive(1)

    def setup_particles(self):
	
	# Particles creation
        base.enableParticles()
        self.p = ParticleEffect()
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(PARTICLE_EFFECT)
        self.p.start(self.particles)
        self.p.setPos(Vec3(0,0,0))
        self.p.setScale(2)

    def setup_passes(self):

	z_shader = ShaderAttrib.make()
        z_shader = z_shader.setShader(Shader.load("depth.cg"))

	self.z_scene_cam.node().setTagStateKey("z buffer pass")
        self.z_scene_cam.node().setTagState("True", RenderState.make(z_shader))

	self.z_particles_cam.node().setTagStateKey("z buffer pass")
        self.z_particles_cam.node().setTagState("True", RenderState.make(z_shader))

	self.filterMan = FilterManager(base.win, self.scene_cam)
        self.original_map = Texture()
       
  	# Z buffer pass
 	self.scene.setTag("z buffer pass", "True")
	self.particles.setTag("z buffer pass", "True")
	self.particles.setShaderInput("particles", loader.loadTexture('fire.png'))
	self.scene.setShaderInput("particles", self.particles_map)
	self.scene.setShaderInput("active", 0)
	self.particles.setShaderInput("active", 1)

        # Particles compositing pass
        particles_comp_map = Texture()
        final_quad = self.filterMan.renderSceneInto(colortex = self.original_map)
        final_quad.setShader(loader.loadShader("particles_comp.cg"))
        final_quad.setShaderInput("color", self.original_map)
	final_quad.setShaderInput("pe", self.p )
	final_quad.setShaderInput("camera", self.particles_cam )
        final_quad.setShaderInput("particles", self.particles_map)
        final_quad.setShaderInput("particles_depth", self.particles_depth_map)
        final_quad.setShaderInput("scene_depth", self.scene_depth_map)
    
    ### Update per frame ###
    def update(self,task):

        dt = globalClock.getDt()
        return task.cont

app = Application()
run()

[color=indigo]Linear depth shader

//Cg

void vshader(in float4 vtx_position : POSITION,
	     in float4 vtx_texcoord0 : TEXCOORD0,
	     uniform sampler2D k_particles : TEXUNIT0,
             uniform float4x4 mat_modelproj, 
	     out float2 l_texcoord : TEXCOORD1,
             out float4 l_position : POSITION,
	     out float4 l_depth : TEXCOORD0)
{

    l_texcoord = vtx_texcoord0;
    l_position = mul(mat_modelproj, vtx_position);
    l_depth.x = (l_position.z/l_position.w);
   
}

void fshader( in float4 l_depth : TEXCOORD0,
              in float2 l_texcoord : TEXCOORD1,
	      uniform float3 k_active,
	      uniform sampler2D k_particles : TEXUNIT0,
              out float4 o_color0 : COLOR0)
{
   
    float4 particles = tex2D(k_particles, l_texcoord);

    o_color0 = float4(l_depth.x,l_depth.x,l_depth.x,particles.a);
    if(k_active.x==0)o_color0 = float4(l_depth.x,l_depth.x,l_depth.x,1);

}

[color=indigo]Particles compositing shader

//Cg

void vshader(in float4 vtx_position : POSITION,
             uniform float4 texpad_color,
             uniform float4x4 mat_modelproj,
	     out float4 l_position : POSITION,
             out float2 l_texcoord : TEXCOORD0,
	     out float4 l_screenpos : TEXCOORD1)
{
    l_position = mul(mat_modelproj, vtx_position);
    l_texcoord = (vtx_position.xz * texpad_color.xy) + texpad_color.xy;
    l_screenpos = l_position;
}

void fshader(in float2 l_texcoord : TEXCOORD0,
	     in float4 l_screenpos  : TEXCOORD1,
             uniform sampler2D k_color : TEXUNIT0,
             uniform sampler2D k_particles : TEXUNIT1,
             uniform sampler2D k_particles_depth : TEXUNIT2,
             uniform sampler2D k_scene_depth : TEXUNIT3,
	     uniform float4 vspos_pe,
	     uniform float4 vspos_camera,
             uniform float4 texpix_color,
             uniform float4 texpad_color,
             out float4 o_color : COLOR)
{

    float scene_depth = tex2D(k_scene_depth, l_texcoord);
    float particles_depth = tex2D(k_particles_depth, l_texcoord);

    float4 scene = tex2D(k_color, l_texcoord);
    float4 particles = tex2D(k_particles, l_texcoord);
    
    float fade = saturate(scene_depth - particles_depth) * distance(vspos_pe,vspos_camera);
    fade =  clamp( fade, 0.0, 1.0 );
  
    float4 output = (fade * particles)  + ((1.0 - fade) * scene);
    o_color = output ;

}

Hi Manou,

I haven’t had a chance to run your program yet, but the depth buffer is not linear. So if you’re relying on a linear (worldspace metric) to do your fade, you should convert your depths to linear space before doing the comparison.

The firefly example has some nice code which illustrates how to do this. It should be something like

proj = rtmcam.node().getLens().getProjectionMat()
proj_x = 0.5 * proj.getCell(3,2) / proj.getCell(0,0)
proj_y = 0.5 * proj.getCell(3,2)
proj_z = 0.5 * proj.getCell(3,2) / proj.getCell(2,1)
proj_w = -0.5 - 0.5*proj.getCell(1,2)
npLight.setShaderInput("proj",Vec4(proj_x, proj_y, proj_z, proj_w))
	float depth = tex2D(k_texdepth, texcoords).r;
float3 viewPos = (float3(screen.x, 1, screen.y) * k_proj.xyz) / (depth + k_proj.w);

The linear depth that you want is viewPos.y so it should boil down to something like

linear depth = 1.*k_proj.xyz/(depth + k_proj.w)

[/code]

Ok thanks for your detailled answer. I’ll try it and maybe we can have nice fog in panda scene now!

Is the projection matrix from this code the same as the projection matrix in CG ? Or they are not related to each other ?

Hey Manou. I’m afraid you won’t be able to use panda’s fog with custom shaders. Actually, it seems the ShaderGenerator doesn’t support it as well.
You could perhaps implement one as a FilterManager shader yourself.

The projection matrix is the camera lens’ projection matrix. Something like

proj = rtmcam.node().getLens().getProjectionMat()