GLSL vs ClipPlane?

I bumped into a strange problem with rendering water.

In my scene I have a water plane rendered with a Cg shader and a sky dome rendered with a GLSL shader. Each one works ok on his own, but as soon as I put them both in a scene the water shader goes nuts.

I’m using the code for water that was reposted here on the forum again and again, not even sure who the original author was (drwr?)

#Create the plane geometry and add it to the scene
        maker = CardMaker("grid")
        maker.setFrame( 0, 2000, 0, 2000)
        self.waterNP = NodePath('waterSurface')
        node = self.waterNP.attachNewNode(maker.generate())
        node.setHpr(0,-90,0)
        node.setPos(-1000, -1000, 0)
        self.waterNP.reparentTo(render)
        #Add a buffer and camera that will render the reflection texture
        wBuffer = base.win.makeTextureBuffer("water", 512, 512)
        wBuffer.setClearColorActive(True)
        wBuffer.setClearColor(base.win.getClearColor())
        self.wCamera = base.makeCamera(wBuffer)
        self.wCamera.reparentTo(render)
        self.wCamera.node().setLens(base.camLens)
        self.wCamera.node().setCameraMask(BitMask32.bit(1))
        self.waterNP.hide(BitMask32.bit(1))
       
        #Create this texture and apply settings
        wTexture = wBuffer.getTexture()
        wTexture.setWrapU(Texture.WMClamp)
        wTexture.setWrapV(Texture.WMClamp)
        wTexture.setMinfilter(Texture.FTLinearMipmapLinear)
       
        #Create plane for clipping and for reflection matrix
        self.wPlane = Plane(Vec3(0, 0, 1), Point3(0, 0, self.waterNP.getZ()))
        wPlaneNP = render.attachNewNode(PlaneNode("water", self.wPlane))
        tmpNP = NodePath("StateInitializer")
        tmpNP.setClipPlane(wPlaneNP)
        tmpNP.setAttrib(CullFaceAttrib.makeReverse())
        self.wCamera.node().setInitialState(tmpNP.getState())
        self.waterNP.projectTexture(TextureStage("reflection"), wTexture, self.wCamera)

But the moment I add my sky shader the clip plane stops working. Some screens:

No shader, clip plane works:

Shader on, clip plane works not:

The code is simplish, so are the shaders (attaching it all to this post, comment out line 22 to get what’s on screen #1). What can I do to make it work?
mini.zip (885 KB)

Clipping planes are a feature of the fixed-function pipeline. If you’re using a shader, you have to do the clipping yourself. You can look at the generated shader by the ShaderGenerator to get an idea how to do that. That said, you might get better results with gl_ClipDistance or gl_CullDistance nowadays; not really sure about those, though, since I’ve never used either.

In the old GLSL versions, you could access the clip planes via gl_ClipPlane[i]. This would probably be fairly straightforward to add via a p3d_ClipPlane input as well.

I don’t get it. I should add a clip plane to all the shaders in my scene? And I don’t want them clipped when viewed by the default camera, just the reflection cam.

In the demo I have a panda model with no shaders at all and it gets clipped ok only if I don’t use glsl shaders on other objects in the scene.

What videocard you used? I read somewhere that on Nvidia you should add
gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
in Vertex shader

I’m on ATI, but where would I put that line of code? The panda model in my code isn’t even using shaders.

Normally you need to handle clip planes in the shader, so you need something like this in your fragment shader:

  if (dot(gl_ClipPlane[0], vpos) < 0) {
    discard;
  }

I’ve just pushed a change that adds an equivalent p3d_ClipPlane vec4 uniform variable.

But are you saying that clip planes don’t work on a node without shaders? If so, that might be a bug in Panda, but it seems very odd, because FFP clip planes are set using a different mechanism that doesn’t have anything to do with shaders.

There’s a (not)working example in the first post. Download, unzip, run, exit, put # on line 22 and run again. All will be clear.
But it looks like it’s a moot point if the clipping is done in the shader, I’d need a 1:1 copy of my scenegraph rendered again with new shader with the clip plane - might as well try screen space reflections and somehow mix in sky reflections for rays outside the screen. Can’t be much slower then what I have now (it’s too slow anyway).
I was really thinking the clipping is done on the cpu before sending anything to the gpu.

Edit:
I’ve re-written the shader in cg

//Cg
void vshader(
    in float4 vtx_texcoord0 : TEXCOORD0,
    in float4 vtx_position : POSITION, 
	in float4 vtx_normal : TEXCOORD1,            
    uniform float time,
    uniform float horizont,
    uniform float cloudTile,
    uniform float cloudSpeed,
    uniform float4x4 mat_modelproj,               
    out float4 l_position : POSITION,
    out float4 l_texcoord0 : TEXCOORD0,
    out float4 l_texcoord1 : TEXCOORD1,
    out float l_blend: TEXCOORD2,
    out float l_h: TEXCOORD3)
   {   
   l_position = mul(mat_modelproj, vtx_position);
   l_texcoord0 = vtx_texcoord0*cloudTile-time*cloudSpeed;
   l_texcoord1 = vtx_texcoord0*cloudTile*0.5-time*cloudSpeed*0.5; 
   l_blend=saturate((vtx_position.z-horizont)/horizont);
   l_h=saturate((vtx_position.z-(horizont*0.5))/(horizont));   
   }

void fshader(
    in float4 l_texcoord0 : TEXCOORD0,
    in float4 l_texcoord1 : TEXCOORD1,
    in float l_blend: TEXCOORD2,
    in float l_h: TEXCOORD3,
    uniform sampler2D tex_0 : TEXUNIT0,
    uniform float4 cloudColor,
    uniform float4 sky,
    uniform float4 fog,
    out float4 o_color : COLOR0)
  {   
    float4 tex1=tex2D(tex_0, l_texcoord0.xy);
    float4 tex2=tex2D(tex_0, l_texcoord1.xy);
    o_color=lerp(fog, sky, l_h);   
    o_color+=((cloudColor-o_color)*(tex1.r*tex2.r)*l_blend)*cloudColor.a;
    o_color.a=1.0;
  }

And the panda rendered with the fixed-function pipeline gets cut correctly - so there is some bug with the GLSL/ClipPlane/ATI/Windows/Driver thing.
I’ve checked the generated shaders, and there are in fact two of them for each model, one of them with a clip plane attribute and this line of code:

if (l_world_position.x * clipplane_0.x + l_world_position.y * clipplane_0.y + l_world_position.z * clipplane_0.z + clipplane_0.w <= 0) {
	 discard;
	 }

I could probably rewrite all my shaders to cg once 1.9 is out (I need vtf and instancing on ati/amd), and that would solve the problem with clipping but not the problem as a whole…unless there’s a way to apply different shaders (or just uniforms) to objects based on the camera viewing these objects.

Edit2:
oh, wait I can just add a dumy clip plane to the default camera and even if there is no clip plane input in glsl for 1.8, I just need the Z coord, water will always be in the xy-plane. It should work.

The solution to not having to use two shaders is to simply enable clipping for all nodes, but use the plane equation (0, 0, 0, 0) when rendering the scene normally. (This is what p3d_ClipPlane does - if you use p3d_ClipPlane[0] when rendering without clip planes, it’ll return (0, 0, 0, 0), and the clip will never discard.)
(The shader generator is a little bit suboptimal in that it generates two shaders here, and it could be improved, but it’s a bit tricky, and it’s not immediately obvious in this case whether it’s faster to have two different shaders or to have the same shader with two different sets of inputs.)

(Also, if you don’t plan on doing refraction or underwater rendering, you could just use the same clip plane for all your geometry.)

You also don’t need a second copy of the scene graph just to apply clipping. Clip planes and shader inputs are just render states. The code you showed already shows how to render the same scene twice with two different states, once with and once without clip planes applied, so I’m not sure why you think that you’d need to copy the scene graph for anything - you haven’t had to do so for your existing clip plane set-up either.

There’s really nothing fundamentally different for the case where you have a shader do the clipping instead of letting OpenGL do the clipping. A clip plane is just a render state as much as a shader assignment or a shader input.

Now, I’m trying to understand your original situation since there does seem to be some bug here. If I understand correctly, the shader is applied to the clouds not the Panda. If the panda model doesn’t have shaders, it should be clipped just fine via the FFP mechanisms. (I was confused at first since you didn’t mention in your original post that your panda doesn’t have shaders.)

I was able to download and run the code for myself, and when the cloud shader is on, the panda is correctly clipped against the plane. In your screenshot, it seems that this is not the case. Since our findings are discrepant, it would seem that there is a genuine bug here.

Are you using a recent development build of Panda, or an old version?

(In any case, I doubt that the bug is actually related to the use of GLSL rather than Cg. Rather, perhaps some left-over graphics state from a previous draw call interfered with clip plane operation. In fact, I suspect that the Cg runtime itself might be to blame, which would explain why I’m not getting these issues, as many issues related to Cg were fixed recently.)

It’s as you say, the panda model with no shaders didn’t get cliped, but only when I had one object with a glsl shader and one with a cg shader, if both used cg, all was fine (didn’t try what would be without any cg shaders).
I’m using the ‘old’ 1.8.1, an 3000 series ATI card and Windows XP.
If you want me to try a dev version, I’d need to have something pre-build or a week (or two) to build the sdk after learning how to build it :wink:

I was thinking I needed a copy of the scene so I’d have one scene with shaders using the clip plane and one without - but I can just as well use the shaders with the clip and just add another ‘empty’ plane for the default camera. Simple really, but well, it is monday evening, and I wasn’t all that smart to start with anyway:P

Can’t get it to work after all ;(

Can’t set any attributes on the default camera with base.cam.node().setInitialState(tmpNP.getState()), but I also can’t get gl_ClipPlane to clip anything. In cg clipplane_0 is in world space but gl_ClipPlane is in outer space (well maybe in eye/clip? space) but whatever I put in the plane equation it has no relation to what the shader reads from gl_ClipPlane[0] (or [1] or [7]). When debuging with a shader that outputs the clip plane to rgba the color changes with the camera pos and goes black when the camera is fliped upside down.

I’m giving up on the clip planes. I send the water level to the shaders as a color (water_z/max_map_z). I set it as the initial state on the reflection camera - as a color scale attrib… but I have to wait for 1.9 to have color scale in glsl.
I don’t see a way to make it work in 1.8.

Why not switch to 1.9 now? As I understand - 1.9 close to release and more or less stable.

I’m not using 1.9 because:
-couldn’t find a pre-build version of in for 32 bit windows xp
-makepanda from the git ‘Couldn’t find Windows SDK v7.1’ and I also couldn’t find windows sdk v7.1 for win xp (the closes I got was a ‘platform sdk’ for windows 2003 server, and a windows phone sdk - installed both but no go)
-I hope the 1.9 will be out soon so I don’t have to explore the wonderful world of compiling :mrgreen:

If you wish I can build 32 bit version on Win 7 and upload on dropbox, I think it should work on 32 bit XP.

If you have time for that, then sure, I’d like that very much.

The gl_ClipPlane/p3d_ClipPlane inputs are in eye space (or “apiview” space, as it’s called by Panda’s Cg inputs system). This is different from the Cg equivalents, which are in world space.

Here’s a small sample program demonstrating how to use the gl_ClipPlane or p3d_ClipPlane input in GLSL. Sorry that the code is so ugly; I had quickly hacked it up to test the p3d_ClipPlane behaviour when I implemented it. It should get the point across, though.

rdb.name/clip_plane_glsl.zip

Thanks, the example is clear, no problem there… yet if I set a clip plane (and that’s the only clip plane I have) on my water camera like this:

        self.wPlane = Plane(Vec3(0, 0, 1), Point3(0, 0, 26))
        wPlaneNP = render.attachNewNode(PlaneNode("water", self.wPlane))
        tmpNP = NodePath("StateInitializer")
        tmpNP.setClipPlane(wPlaneNP)
        tmpNP.setAttrib(CullFaceAttrib.makeReverse())        
        self.waterCamera.node().setInitialState(tmpNP.getState()) 

All the objects seen by that camera get clipped, but they also get clipped when rendering with the default camera (as if I set the clip plane for ‘render’). Screen below:

(The tree is using the color scale hack I wrote about earlier, and it doesn’t use the clip plane in the shader )

If I put a clip plane on the main camera, that won’t clip anything then that plane is also used by the water camera.

In other words - I can’t set a per-camera clip plane.

If anyone wants to test my code, I can push it to the git (this is for my Koparka level editor).

Panda3D 1.9. Build options >makepanda\makepanda.bat --everything --installer --no-eigen
also fftw, opencv and awesomium excluded from build
dropbox.com/s/x7obmsd3kyv3a … 0.exe?dl=0

Thanks, downloaded, installed, tried to run, got “ImportError: DLL load failed: The specified procedure could not be found:cry:

Have you installed VC runtimes (2008 and 2010)?

I’ve uninstalled all my Panda3D skd and also all my pythons, re-installed the vc 2008 and 2010 and then installed the 1.9 sdk - and all that this got me is a new error message (as a pop-up this time) saying “The procedure entry point RegisterTouchWindow could not be located in the dynamic link library USER32.dll