Nvidia Nature Demo

Impressive, and at least 50% faster. You are great :slight_smile:

About the clip plane problem. Objects seem to get clipped at geom level, and since the terrain is just one geom it is clipped as a whole block. To make it better I would have to split the terrain into smaller chunks, or at least two chunks, one for everything above the surface, and one for everything below.

:open_mouth: great :mrgreen:

thats something Panda3D can show-off with :smiley:

Regards, Bigfoot29

It only happens when the node is rendered using shader, otherwise it’s at the standard pixel level. To clip shader rendered nodes, the clipping process must be done in the shader too, I think, haven’t tried it.
Serious problem :
setting initial state to the camera seems disables all clip planes on that camera.
Try this :

from pandac.PandaModules import *
loadPrcFileData('', 'show-buffers 1')
import direct.directbase.DirectStart, sys
from direct.showbase.DirectObject import DirectObject

DO=DirectObject()
DO.accept('escape',sys.exit)
base.camLens.setFar(10000)
base.setBackgroundColor(0,0,0,1)
base.trackball.node().setForwardScale(.05)

boxes=render.attachNewNode('')
for z in range(10):
    for x in range(10):
        for y in range(10):
            b1=loader.loadModel('misc/rgbCube')
            b1.reparentTo(boxes)
            b1.setPos(x*1.7, y*1.7, z*1.7)
            b1.setColor(.2+y*.08, .2+y*.08, .2+y*.08, 1)
boxes.setPosHpr(-5,40,-10, 45,45,0)

grassPlane = Plane( Vec3( 0, -1, 0 ), Point3( 0, 50, 0 ) )
grassPlaneNode = PlaneNode( 'grassPlane' )
grassPlaneNode.setPlane( grassPlane )
grassPlaneNP = base.cam.attachNewNode( grassPlaneNode )
grassPlaneNP.show()

waterPlane = Plane( Vec3( 0, 0, 1 ), Point3( 0, 0, -7 ) )
waterPlaneNode = PlaneNode( 'waterPlane' )
waterPlaneNode.setPlane( waterPlane )
waterPlaneNP = base.cam.attachNewNode( waterPlaneNode )
waterPlaneNP.show()

buffer = base.win.makeTextureBuffer( '', 256, 256 )
buffer.setClearColor( Vec4( 0, 0, 0, 1 ) )
additionalCameraNP = base.makeCamera( buffer )
additionalCameraNP.reparentTo( base.cam )

# cpa1 = ClipPlaneAttrib.make()
# cpa1.removePlane( waterPlaneNode )
# cpa1.addOnPlane( grassPlaneNP )
# rs1 = RenderState.make( cpa1,1 )
# base.cam.node().setInitialState(rs1)

render.setClipPlane(waterPlaneNP)
render.setClipPlane(grassPlaneNP)

run()

uncomment those lines to see…

Hmm… setting override to 0 or leaving this argument away seems to fix this problem for me:

rs1 = RenderState.make( cpa1,0 )

But I’m not sure if I got you right here.

Time for an update. Thanks to you all and ynjh_jo in particular I was able to improve the demo.

I have added clipping to the grass pixel shader (using cg discard instruction), giving a much smoother grass clipping than with the clip plane alone. And there are no longer artifacts from underwater ‘hills’. The trick has been to use two different shader attributes for the terrain NodePath, one that renders the terrain in a normal way, and one that clips away everything below the water level (see here: panda3d.org/manual/index.php/Multi-Pass_Rendering). Finally, superposing several sine waves make the grass movement a little bit more realistic.

Screenshot:
http://www.dachau.net/users/pfrogner/nature.jpg

Download:
http://www.dachau.net/users/pfrogner/nature.zip

Nice update & tricks, … but without shader, is it impossible ?

the goal is to use only 1 clip plane for the buffer, but they’re used at all or not at all.
I tried setTagState & key too, it works on other attribs, except ClipPlaneAttrib. Seems that setClipPlane has the top priority set over the planes (and I’ve used it at the boxes level, not render level).
…and you use texture filter only for the grass ? By default, there is no filter at all for manually loaded textures.

Man this is georgeous, incredible useful, and very informative … how did I miss this? Awesome work.

Awesome work!

It runs fine on my computer, for one thing, it runs just at 2 fps.

Is there any way to make it faster?

And, I discovered a bug:

The water is overlapping the grass .

It runs 5 fps on my GFFX5200. At the first glance, it looks like transparency issue to me, but it doesn’t happen here, so it’s not a bug. Maybe that’s one of your graphic card’s weaknesses.

Ooops, wrong answer. It happened to me too, only when looking from the hills, right ? I haven’t able to fix it.

[EDIT]
alright, it’s definitely transparency problem. The waterplane is simply a geometry created by cardmaker, so it only has 2 triangles, and if compared to the grasses’ triangles, it’s so damn huge.
I used my own very well tesselated plane and no more problem.

Yes, a problem with z ordering caused by the 2 triangle plane.

If spending more polygons for the water plane then it should be possible to modify the water shader, and replace the transparency factor that is read from the water depth-map with a fresnel term, for more realistic water reflections.

Hmm… when I have some time I will add this too. But I am afraid this won’t be before in two or three weeks.

enn0x

there are lots ordering issues inside the gras/ground. switching the transparency to “DUAL” solves this but makes the grass looking pretty ugly :stuck_out_tongue:.

This is quite possibly the most important example I have seen on these forums yet. I am studying it trying to understand how it all works. I have run across a few snags … hope someone can answer these questions for me:

  1. In the following code there are 3 arguments to setTexture. Obviously the arguments are (textureStage, texture, priority) however, I am unable to find any documentation with more than 2 arguments in it. I experimented with setting the priorities to 0, -1, and other values but it had no effect. What is going on here?
    def _setupTerrain( self ):
        tex0 = loader.loadTexture( 'models/dirt.png' )
        tex1 = loader.loadTexture( 'models/fungus.png' )
        tex2 = loader.loadTexture( 'models/grass.png' )

        ts0 = TextureStage( 'dirt' )
        ts1 = TextureStage( 'fungus' )
        ts2 = TextureStage( 'grass' )

        np = loader.loadModel( 'models/terrain' )
        np.reparentTo( render )
        np.setPos( 0, 0, 0 )
        np.setTexture( ts0, tex0, 10 )
        np.setTexture( ts1, tex1, 20 )
        np.setTexture( ts2, tex2, 30 )
        np.setTag( 'Normal', 'True' ) 
        np.setTag( 'Clipped', 'True' ) 
        np.setShaderInput( 'terrain', _terrain )
  1. In the above code segment, what is the logic for determining which level the textures are assigned to? It appears that the highest level (>66) is loaded first, the lowest level (<16) second, and the mid level (16 to 66) third.

  2. What do the ‘setTag(…)’ calls do? In the documentation it says that setTag allows the user to set tags to be stored by a node but that tese values are meaningless to Panda. I search the code and find nothing referencing these tags but if I comment them out, I get nothing rendered.

  3. In the following code (in _setupCamera()) can someone explain to me what is going on?

        cam1.setTagStateKey( 'Clipped' ) 
        cam1.setTagState( 'True', RenderState.make( sa ) ) 

Thanks!
Paul

I try to explain:

(1)
This has been my first experience with shaders, and I did work with try and error at first. Right in the beginning I had problems with passing multiple textures to a shader, so I tried several thing, among them setting texture priorities. But the problem has been somewhere else.

The priorities are still from this try & error phase, and they are useless. What is important is the order of adding texture stages. This manual page gives some info on what priorities are good for:
http://panda3d.org/manual/index.php/Texture_Order

(2)
I admit it would have been nicer to add the textures in one oder or another, e.g. in the oder of the elevation levels they are used for.

Please have a look at the terrain shader code:

...
              in uniform sampler2D tex_0 : TEXUNIT0,
              in uniform sampler2D tex_1 : TEXUNIT1,
              in uniform sampler2D tex_2 : TEXUNIT2,

              out float4 o_color : COLOR )
{
    float4 dirtSample = tex2D( tex_0, l_texcoord0 );
    float4 fungusSample = tex2D( tex_1, l_texcoord0 );
    float4 grassSample = tex2D( tex_2, l_texcoord0 );

    // texture blending
    o_color = dirtSample;
    o_color = o_color * l_blend.y + ( 1.0 - l_blend.y ) * grassSample;
    o_color = o_color * l_blend.x + ( 1.0 - l_blend.x ) * fungusSample;
...

The first texture (“dirt.png”) is used in the shader as variable “tex_0” (bound to TEXUNIT0). Then I sample this texture I use explicit names again: “dirtSample”. And so on for the other two textures.

Then I blend the three samples, and since I have explicit names I didn’t realize that the textures are not in the same oder as the elevation ranges.

Hmm… by the way, now that I see the code again I think using lerp( ) would be faster than blending by hand.

(3)
Now this is a bit tricky. At first the water reflections had some flaws, among them the fact that parts of the terrain that are below the water surface have been reflected too.

My first approach to this problem has been to use an additional clip plane on the camera that renders the reflection. This would have worked, if I hadn’t used shaders for the terrain. ynij_jo found out that when using shaders clipping seems to happen at geom level and not at pixel level.

So if I want to clip away the underwater parts of the terrain I would have to divide the mesh into two parts, one over-water and one under-water. Not nice :frowning:

The work-around is in the shaders again. There are two shaders for the terrain: “terrainNormal.sha” and “terrainClipped.sha”. The only difference are these lines in “shaderClipped”:

    // clipping
    if ( l_mpos.z < 36.0f ) discard;

If the interpolated position of a pixel is below z=36 then it is not rendered. (Hmm… again something that should be fixed: hard-coding the water level in the shader to 36.0f is bad)

Fine so far, but now I have another problem: The main camera has to render the terrain using “shaderNormal.sha”, and the reflection camera has to render the terrain using “terrainClipped.sha”. This is what the tags are for.

If you look at the part of the demo where the terrain NodePath is created you will notice that I assign no shader to this NodePath, like I did for grassNO, skyNP and so on. But I assign two tags, “normal” and “clipped”. Now, whenever a camera sees the terrain NodePath, it assigns the render state that is associated with these tags. And in _setupCamera( ) is equip the two cameras with proper render states. The main camera (cam0) gets a render state containing the shader “terrainNormal.sha” associated with the tag “normal”, and the reflection camera (cam1) gets another render state containing the shader “terrainClipped.sha” associated to the tag “clipped”.

A very mighty instrument in my opinion. This manual page explains it too:
http://panda3d.org/manual/index.php/Multi-Pass_Rendering

I hope I was able to help with understanding the code.
enn0x

Yes, that helps a lot. Thanks for taking the time to explain it.

I now understand the ordering of the textures. I changed the python code to this:

		tex0 = loader.loadTexture( 'Resources/level0.png' )
		tex1 = loader.loadTexture( 'Resources/level1.png' )
		tex2 = loader.loadTexture( 'Resources/level2.png' )

		ts0 = TextureStage( 'level0' )
		ts1 = TextureStage( 'level1' )
		ts2 = TextureStage( 'level2' )

And the shader code to this :

    float4 level0 = tex2D( tex_0, l_texcoord0 );
    float4 level1 = tex2D( tex_1, l_texcoord0 );
    float4 level2 = tex2D( tex_2, l_texcoord0 );

    // texture blending
    o_color = level2;
    o_color = o_color * l_blend.y + ( 1.0 - l_blend.y ) * level1;
    o_color = o_color * l_blend.x + ( 1.0 - l_blend.x ) * level0;

Now I can rename the textures accordingly and it makes sense (level0.png is the fungus, level1.png is the grass, level2.png is the dirt)

Thx,
Paul

In my case this demo doesn’t work. It is very pity since many people on the forum refer to it. I have Panda 1.4.2, PyPE 2.8.8, Python 2.4.4, wxPython 2.8.6.1, PyXML 0.8.4, PIL 1.1.6.
Here is the log:

Encoding set to UTF8
DirectStart: Starting the game.
Warning: DirectNotify: category 'Interval' already exists
Known pipe types:
  wglGraphicsPipe
(3 aux display modules not yet loaded.)
:util(warning): Adjusting global clock's real time by 1.04179 seconds.
Traceback (most recent call last):
  File "Demo-Nature.py", line 302, in ?
    world = World( )
  File "Demo-Nature.py", line 64, in __init__
    self._setupBase( )
  File "Demo-Nature.py", line 104, in _setupBase
    props.setCursorHidden( True )
TypeError: Cannot call WindowProperties.setCursorHidden() on a const object.

**** End of process output ****

Right, doesn’t work anymore. This demo has been written for an earlier version of Panda3D.

To make it work again you have to change this method:

    def _setupBase( self ):
        self.centerx = base.win.getProperties( ).getXSize( ) / 2
        self.centery = base.win.getProperties( ).getYSize( ) / 2
        base.win.movePointer( 0, self.centerx, self.centery )

        props = WindowProperties( )
        props.setCursorHidden( True )
        base.win.requestProperties( props )

        base.disableMouse( )
        base.setBackgroundColor( 0, 0, 0 )

enn0x

It doesn’t seem to work for me, erroring both on the props.setCursorHidden(True) and on the loader.loadModel( ‘models/sky’ ). Is this because the bam file was made in a different version of panda?

This could be. I used .bam because of the smaller size. Not a wise choice.

But notice that there also has been a change in how Panda3D finds the models. You now have to give the filename including extension. Otherwise Panda3D will search for the default extension only (.egg):

        self.skyNP = loader.loadModel( 'models/sky.bam' )

Also for anything else loaded via “loader.loadXYZ”. Well, the code is rather old and obsolete by now.

enn0x