Water Reflections

My water is comming along great, and I only have one major problem left; reflections.

I want to do reflections be creating a second camera and rendering its view to a texture. Then applying this texture to my water.

The problem is I have no idea were to place this camera, how to rotated, or the best way to apply the texture to the water.

Here is the basics of what I have done so far:

#Reflection
        self.reflectionBuffer = base.win.makeTextureBuffer("Reflection Buffer",512,512)

        self.reflectionCamera = base.makeCamera(self.reflectionBuffer)
        self.reflectionCamera.reparentTo(render)
        self.reflectionCamera.node().setLens(base.camLens)

        self.reflectionCamera.setPos(0,0,0)
        self.reflectionCamera.setHpr(0,0,0)
        self.reflectionTexture = self.reflectionBuffer.getTexture()
        self.reflectionTexture.setWrapU(Texture.WMClamp)
        self.reflectionTexture.setWrapV(Texture.WMClamp)
        self.reflectionTexture.setMinfilter(Texture.FTLinearMipmapLinear)

        self.reflectionImage = OnscreenImage(image=self.reflectionTexture,pos=(.5,0,0),scale =(.25,1,.25))

What should I do next?

I’ve made a Water class using stuff I found on this forum. Below I’ll copy the files I’m using. Note that this doesn’t manage refractions, only reflections :cry:. In order to use that, you only have to make an instance of the WaterNode class. This is more than what you asked, but I hope you can find the code useful in order to answer to your question.

Water.py

from pandac.PandaModules import CardMaker, Texture, TextureStage, Plane, \
  PlaneNode, TransparencyAttrib, CullFaceAttrib, RenderState, ShaderAttrib

class WaterNode():
  
  def __init__( self, x1, y1, x2, y2, z, anim, distort ):
    # anim: vx, vy, scale, skip
    # distort: offset, strength, refraction factor (0 = perfect mirror,
    #   1 = total refraction), refractivity

    if not hasattr( self, 'buffer' ):
      WaterNode.buffer = base.win.makeTextureBuffer( 'waterBuffer', 512, 512 )
    if not hasattr( self, 'watercamNP' ):
      WaterNode.watercamNP = base.makeCamera( WaterNode.buffer )

    maker = CardMaker( 'water' ) # Water surface
    maker.setFrame( x1, x2, y1, y2 )

    self.waterNP = render.attachNewNode( maker.generate() )
    self.waterNP.setPosHpr( ( 0, 0, z ), ( 0, -90, 0 ) )
    self.waterNP.setTransparency( TransparencyAttrib.MAlpha )
    self.waterNP.setShader( loader.loadShader( 'water.sha' ) )
    self.waterNP.setShaderInput( 'wateranim', anim )
    self.waterNP.setShaderInput('waterdistort', distort )
    self.waterNP.setShaderInput( 'time', 0 )
    
    self.waterPlane = Plane( ( 0, 0, z + 1 ), ( 0, 0, z ) ) # Reflection plane
    PlaneNode( 'waterPlane' ).setPlane( self.waterPlane )

    WaterNode.buffer.setClearColor( ( 0, 0, 0, 1 ) ) # buffer

    WaterNode.watercamNP.reparentTo( render ) # reflection camera
    cam = WaterNode.watercamNP.node()
    cam.getLens().setFov( base.camLens.getFov() )
    cam.getLens().setNearFar( 1, 5000 )
    cam.setInitialState( RenderState.make( CullFaceAttrib.makeReverse() ) )
    cam.setTagStateKey( 'Clipped' )
    cam.setTagState('True', RenderState.make(
      ShaderAttrib.make().setShader(
        loader.loadShader( 'splut3Clipped.sha' ) ) ) )

    tex0 = WaterNode.buffer.getTexture() # reflection texture, created in
                                         # realtime by the 'water camera'
    tex0.setWrapU( Texture.WMClamp ); tex0.setWrapV( Texture.WMClamp )
    self.waterNP.setTexture( TextureStage( 'reflection' ), tex0 )
    self.waterNP.setTexture( TextureStage( 'distortion' ),
      loader.loadTexture( 'water.png' ) ) # distortion texture
    
    self.task = taskMgr.add( self.update, 'waterUpdate', sort = 50 )
    
  def remove( self ):
    self.waterNP.removeNode()
    taskMgr.remove( self.task )

  def destroy( self ):
    base.graphicsEngine.removeWindow( WaterNode.buffer )
    base.win.removeDisplayRegion(
      WaterNode.watercamNP.node().getDisplayRegion( 0 ) )
    WaterNode.watercamNP.removeNode()

  def update( self, task ):
    self.waterNP.setShaderInput( 'time', task.time ) # time 4 H2O distortions
    WaterNode.watercamNP.setMat( # update matrix of the reflection camera
      base.camera.getMat() * self.waterPlane.getReflectionMat() )
    return task.cont

water.sha

//Cg
//Cg profile arbvp1 arbfp1


//
// OUT
// texcoord0: projective mapping for the reflection texture
// texcoord1: texture coords into the distortion map, scaled and animated
//
void vshader( 	in float4 vtx_position : POSITION,
                in float3 vtx_normal : NORMAL,
		in float2 vtx_texcoord0 : TEXCOORD0,
		in uniform float4 k_time,
		in uniform float4 k_wateranim,
                in uniform float4x4 mat_modelproj,
                in uniform float4x4 mat_modelview,
                in uniform float4x4 mat_projection,

                out float4 l_position : POSITION,
                out float4 l_texcoord0 : TEXCOORD0,
                out float4 l_texcoord1 : TEXCOORD1)

{
	
	// transform vertex position by combined view projection matrix
   	l_position = mul(mat_modelproj, vtx_position);


    	// projective matrix (MR)
    	float4x4 scaleMatrix = { 0.5f, 0.0f, 0.0f, 0.5f,
        	                 0.0f, 0.5f, 0.0f, 0.5f,
                                 0.0f, 0.0f, 0.5f, 0.5f,
                                 0.0f, 0.0f, 0.0f, 1.0f };
   	float4x4 matMR = mul(scaleMatrix, mat_modelproj);

	// transform the vertex position by the projective
   	// texture matrix and copy the result into homogeneous
   	// texture coordinate set 0
   	l_texcoord0 = mul(matMR, vtx_position);

	// water distortion map
    	// animate and scale distortions
    	l_texcoord1.xy = vtx_texcoord0.xy * k_wateranim.z + k_wateranim.xy * k_time.x;

}

//
// IN
// tex_0: reflection texture as produced by reflection camera
// tex_1: distortion texture
//
void fshader( 	in float4 l_texcoord0 : TEXCOORD0,
		in float4 l_texcoord1 : TEXCOORD1,
              	in uniform sampler2D tex_0 : TEXUNIT0,
		in uniform sampler2D tex_1 : TEXUNIT1,
		in uniform float4 k_waterdistort,
		out float4 o_color : COLOR)
{
	// calculate distortion from distortion map
	float4 distortion = normalize(tex2Dproj(tex_1, l_texcoord1) - k_waterdistort.x) * k_waterdistort.y;

	// projectively sample the 2D reflection texture
   	// o_color.rgb = tex2Dproj(tex_0, l_texcoord0).rgb;
   	float4 reflection  = tex2Dproj(tex_0, l_texcoord0+distortion);

	// refraction factor: smaller numbers make the water appear more reflective ("shinier")
    	float factor = k_waterdistort.z;

	// refraction (1.0 = perfect mirror, 0.0 total refraction)
	float ref = k_waterdistort.w;
	float4 refraction = float4(ref, ref, ref, 0.0f);

	// calculate fragment color
	o_color = lerp( reflection, refraction, factor );

   	// optionally set alpha component to transparency,
   	// a constant value in this simple example
   	// o_color.a = 0.6;
}

splut3Clipped.sha

//Cg
//Cg profile arbvp1 arbfp1

// shader loosely based on pro-rsoft's from the panda3d forums
// shader assumes 6 texture stages:
// 0-2 textures
// 3-5 alpha maps
// vertex shader accepts unit 0 and 3 texture coords as input 
// and outputs unit 0 scaled and unit 3 unchanged
// fragment shader uses unit 0 coordinates to access textures
// and unit 3 coordinates (un-scaled) to access alpha maps
// gsk, june-2008

void vshader( in float4 vtx_position : POSITION,
	      in float3 vtx_normal : NORMAL,
              in float2 vtx_texcoord0 : TEXCOORD0,
              in float2 vtx_texcoord3 : TEXCOORD3,
              in uniform float4x4 mat_modelproj,
	      in uniform float4x4 trans_model_to_world,
	      in uniform float4 k_lightvec,
	      in uniform float4 k_lightcolor,
	      in uniform float4 k_ambientlight,
	      in uniform float4 k_tscale, 
	      out float l_brightness,
	      out float4 l_mpos,
              out float2 l_texcoord0 : TEXCOORD0,
              out float2 l_texcoord3 : TEXCOORD3,
              out float4 l_position : POSITION)
{

  // worldspace position, for clipping in the fragment shader
  l_mpos = mul(trans_model_to_world, vtx_position);

  l_position=mul(mat_modelproj,vtx_position);
  l_texcoord0=vtx_texcoord0*k_tscale;
  l_texcoord3=vtx_texcoord3;

  // lighting
  float3 N = normalize( vtx_normal );
  float3 L = normalize( k_lightvec.xyz );
  l_brightness = (max( dot( -N, L ), 0.0f )*k_lightcolor)+k_ambientlight;


}

void fshader( in float4 l_position : POSITION,
              in float2 l_texcoord0 : TEXCOORD0,
              in float2 l_texcoord3 : TEXCOORD3,
	      in float  l_brightness,
	      in float4 l_mpos,
	      in uniform float4 k_waterlevel,
              in uniform sampler2D tex_0 : TEXUNIT0,
              in uniform sampler2D tex_1 : TEXUNIT1,
              in uniform sampler2D tex_2 : TEXUNIT2,
              in uniform sampler2D tex_3 : TEXUNIT3,
              in uniform sampler2D tex_4 : TEXUNIT4,
              in uniform sampler2D tex_5 : TEXUNIT5,
              out float4 o_color : COLOR )
{
  // clipping
  if ( l_mpos.z < k_waterlevel.z) discard;

  // alpha splatting and lighting
  float4 tex1=tex2D(tex_0,l_texcoord0);
  float4 tex2=tex2D(tex_1,l_texcoord0);
  float4 tex3=tex2D(tex_2,l_texcoord0);
  float alpha1=tex2D(tex_3,l_texcoord3).z;
  float alpha2=tex2D(tex_4,l_texcoord3).z;
  float alpha3=tex2D(tex_5,l_texcoord3).z;
  o_color =tex1*alpha1;
  o_color+=tex2*alpha2;
  o_color+=tex3*alpha3;
  o_color=o_color*(l_brightness);
  o_color.a=1.0;
}

water.png

Thanks, but that uses shaders, and thus is not “Shader-less” water.

Any other ideas?

Without shaders, you’ll have to use projectTexture on the water surface to project the result of the water camera render onto the water surface.

This is some shaderless water code I wrote a long time ago for the instancing test I posted on the blog. It’s not commented, but I hope it can be of some help.

        # Water + reflection
        
        cm = CardMaker("water")
        cm.setFrame(-1, 1, -1, 1)
        self.water = render.attachNewNode(cm.generate())
        self.water.setScale(4096)
        self.water.lookAt(0, 0, -1)
        self.water.setZ(62.365)
        self.water.setShaderOff(1)
        self.water.setLightOff(1)
        self.water.setAlphaScale(0.5)
        self.water.setTransparency(TransparencyAttrib.MAlpha)
        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.water.hide(BitMask32.bit(1))
        fir.hide(BitMask32.bit(1))
        wtexture = wbuffer.getTexture()
        wtexture.setWrapU(Texture.WMClamp)
        wtexture.setWrapV(Texture.WMClamp)
        wtexture.setMinfilter(Texture.FTLinearMipmapLinear)
        self.wplane = Plane(Vec3(0, 0, 1), Point3(0, 0, self.water.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.water.projectTexture(TextureStage("reflection"), wtexture, self.wcamera)
        #base.bufferViewer.toggleEnable()

    def update(self, task):
        self.wcamera.setMat(base.cam.getMat(render) * self.wplane.getReflectionMat())
        return task.cont

The reflections work great, there are only a few problems;

  1. The reflection has a slight delay.

  2. The bump map I had applied to the water model no longer appears.

Here is my code:

class Water():

    def __init__(self):

        taskMgr.add(self.update,"Water Task",10)

        #Define some var's:
        self.offset = 0

        #Model:
        self.model = main.loader.loadModel("models/plane.egg")
        self.model.setScale(1024)
        self.model.setPos(0,0,world.waterHeight)

        self.model.reparentTo(render)


        #Reflection
        self.reflectionBuffer = base.win.makeTextureBuffer("Reflection Buffer",512,512)
        self.reflectionBuffer.setClearColorActive(True)
        self.reflectionBuffer.setClearColor(base.win.getClearColor())

        self.reflectionCamera = base.makeCamera(self.reflectionBuffer)
        self.reflectionCamera.reparentTo(render)
        self.reflectionCamera.node().setLens(base.camLens)
        self.reflectionCamera.node().setCameraMask(BitMask32.bit(1))
        self.model.hide(BitMask32.bit(1))


        self.reflectionTexture = self.reflectionBuffer.getTexture()
        self.reflectionTexture.setWrapU(Texture.WMClamp)
        self.reflectionTexture.setWrapV(Texture.WMClamp)
        self.reflectionTexture.setMinfilter(Texture.FTLinearMipmapLinear)

        self.plane = Plane(Vec3(0,0,1), Point3(0, 0,self.model.getZ()))
        self.planeNp = render.attachNewNode(PlaneNode("water,self.plane"))

        self.tmpnp = NodePath("State")
        self.tmpnp.setClipPlane(self.planeNp)
        self.tmpnp.setAttrib(CullFaceAttrib.makeReverse())

        self.reflectionCamera.node().setInitialState(self.tmpnp.getState())


        #Texture:
        self.textureStage = TextureStage("Texture Stage")
        self.bumpStage = TextureStage("Bump Stage")
        self.reflectStage = TextureStage("Reflect Stage")

        self.texture = loader.loadTexture('gfx/water/water.jpg')
        self.height = loader.loadTexture('gfx/water/waterHeight.jpg')

        self.model.setTexture(self.textureStage,self.texture)
        self.model.setTexScale(self.textureStage,100,100)

        self.bumpStage.setMode(TextureStage.MHeight)
        self.model.setTexture(self.bumpStage,self.height)
        self.model.setTexScale(self.bumpStage,100,100)

        self.model.projectTexture(self.reflectStage,self.reflectionTexture,self.reflectionCamera)

        self.model.setTransparency(TransparencyAttrib.MAlpha)
        self.model.setAlphaScale(.5)

        #Sound:
        self.waterSound = loader.loadSfx("sound/water.wav")
        self.waterSound.setLoop(True)
        self.waterSound.play()
        self.waterSound.setVolume(0)

        #Physics:
        self.node = BulletMotionNode('plane')
        self.node.setTransform(TransformState.makePos((0,0,world.waterHeight+1)))

        self.shape = BulletPlaneShape(Vec3(0,0,1),-1)

        self.body = BulletRigidBody(0,self.node,self.shape)
        self.body.setFriction(1)
        physics.world.addRigidBody(self.body)


    def update(self,task):

        #Texture animation:
        self.offset += .001

        self.model.setTexOffset(self.bumpStage,self.offset,self.offset);
        self.model.setTexOffset(self.textureStage,-self.offset,-self.offset);


        if (main.camera.getZ() - world.waterHeight) < 10:

            self.waterSoundVolume = (((player.np.getZ() - world.waterHeight) / 10) -1) * -1

            self.waterSound.setVolume(self.waterSoundVolume)

        self.reflectionCamera.setMat(base.cam.getMat(render) * self.plane.getReflectionMat())

        return Task.cont

Set the priority of the update task higher, so that the final render includes a pre-rendered water texture from the same frame.

You can’t have bump maps without shaders. Only reason it works out of the box without setting up anything is because of the ShaderGenerator. Without shaders you won’t get any pleasing water effects.
Here’s my best shaderless approach: nemesis13.de/tmp/screenshot-Sa-S … 0-6992.jpg

Still, good luck.

I tried setting the task priority to 2, and I still have the same problem.

But Panda has the built in shader generator, which was working just fine till I added the projected water texture.

Whats your framerate? Doesnt it go way down when you enable the shader generator?

30 fps. No, not really.

There is a bug/known limitation right now in the shadergenerator, if you have state changes for your texture, like animating the UV, or moving the projection, a new shader will be generated each frame during that process, which will really slow things down.

Yes, I have noticed that with my animated water. (Using texture offset.)

However, this still does not tell me why the bump mapping is not working.

It could be that the texture which is projected on the nodepath is applied on top of all the other textures (including the normal map).
Maybe if you change the projected textureStage mode to something else from here then it would work? panda3d.org/manual/index.php/Texture_Modes
I don’t know