|
|
|
Return to Code Snippets
by pleopard » Thu Nov 09, 2006 5:59 pm
Yes, this uses some classes that are not included here but you should still be able to see how to use HeightFieldTesselator by perusing this code. Just know that SceneNavigator is my own class that moves the camera around in the scene FPS style. At any time you can still get the camera position and orientation using base.camera.getPos() and base.camera.getHpr().
This example renders a texture mapped, lit terrain model. Instead of re-tesselating each frame, I re-tesselate every 'self.mTerrainUpdateInterval' seconds.
Next update will include multitexturing.
Enjoy!
- Code: Select all
# Standard imports
import sys import math
# Panda imports
from direct.gui.OnscreenText import OnscreenText from direct.showbase import DirectObject from direct.showbase.DirectObject import DirectObject import direct.directbase.DirectStart from direct.task import Task from pandac.PandaModules import * from pandac.PandaModules import HeightfieldTesselator
# PHL imports
from PHL.Core.Properties import * from PHL.P3D.Utilities import CreateTextLabel from PHL.P3D.Shapes import P3DCreateGridXY,P3DCreateGridYZ,P3DCreateGridXZ from PHL.P3D.TerraViz02.SceneNavigator import SceneNavigator from PHL.Math.Bounds import BoundingVolume from PHL.Math.Utilities import *
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
class World(DirectObject): def getBoundingVolume(self): return BoundingVolume(\ 0,0,0,\ self.mTerrainWidth,\ self.mTerrainWidth,\ self.mTerrainHeight )
def postStatusMessage(self,msg): self.mTextDisplay2.setText(msg)
def toggleWireframe(self): base.toggleWireframe()
def toggleTexture(self): base.toggleTexture()
def joystick(self): return None
def environmentModel(self): return self
def __init__(self):
text = 'Panda3D HeightfieldTesselator Class Test Harness' color = (1,1,0,1) self.mTextDisplay1 = CreateTextLabel(text,color,1) self.mTextDisplay2 = CreateTextLabel(' ',(0,1,0,1),2)
base.setBackgroundColor(0,0.1,0.6,1)
# Setup world size self.mTerrainWidth = 5000 self.mTerrainHeight = 100
# Setup navigator base.camLens.setFar(10000.0) base.camLens.setFov(75, 95) self.__Nav = SceneNavigator(self) self.__Nav.setPitchGain(0.4) self.__Nav.setYawGain(0.4) self.__Nav.setRollGain(0.2) self.__Nav.setMotionGain(self.mTerrainWidth*0.0005)
# Setup lighting lightAttribute = LightAttrib.makeAllOff() dirLight = DirectionalLight('DirLight') dirLight.setColor(Vec4(0.6,0.6,0.6,1.0)) dirLightNP = render.attachNewNode(dirLight.upcastToPandaNode()) # crashes without upcast dirLightNP.setPos(Vec3(0.0,-10.0,10.0)) dirLightNP.setHpr(Vec3(0.0,-26.0,0.0)) lightAttribute = lightAttribute.addLight(dirLight) # add to attribute ambientLight = AmbientLight('ambientLight') ambientLight.setColor(Vec4(0.25,0.25,0.25,1.0)) ambientLightNP = render.attachNewNode(ambientLight.upcastToPandaNode()) lightAttribute = lightAttribute.addLight(ambientLight) render.node().setAttrib(lightAttribute)
# Prep terrain textures coverTextureFile = "Dirt/Ground1.png" self.mCoverTexture = loader.loadTexture(coverTextureFile) Assert(\ self.mCoverTexture != None,\ "Failed loading terrain cover texture "+coverTextureFile)
# Setup heightfield self.mHeightFieldTesselator = HeightfieldTesselator("Heightfield") fName = "HeightField.png" fileObj = Filename(fName) self.mHorizontalScale = self.mTerrainWidth/2048.0 self.mVerticalScale = self.mTerrainHeight self.mTerrainUpdateInterval = 0.1 self.mTerrainUScale = 0.001 self.mTerrainVScale = self.mTerrainUScale self.mHeightFieldTesselator.setHeightfield(fileObj) self.mHeightFieldTesselator.setVerticalScale(self.mVerticalScale) self.mHeightFieldTesselator.setHorizontalScale(self.mHorizontalScale) self.mLastTesselateTime = -1 self.mHeightFieldNode = None self.updateHeightField()
# Setup keyboard events self.setupKeyBindings()
# Done, setup a periodic update task taskMgr.add(self.timeUpdate,'TimeUpdate')
# Re-tesselate the heightfield def updateHeightField(self):
if self.mHeightFieldNode != None: self.mHeightFieldNode.removeNode() self.mHeightFieldNode = self.mHeightFieldTesselator.generate()
self.mHeightFieldNode.setTexGen(\ TextureStage.getDefault(), TexGenAttrib.MWorldPosition ) self.mHeightFieldNode.setTexture(\ TextureStage.getDefault(), self.mCoverTexture, 1 ) self.mTerrainUScale = 0.001 self.mTerrainVScale = 0.001 self.mHeightFieldNode.setTexScale(\ TextureStage.getDefault(), self.mTerrainUScale, self.mTerrainVScale );
self.mHeightFieldNode.reparentTo(render)
# Periodic update def timeUpdate(self,task): if (self.mLastTesselateTime == -1) or \ (task.time-self.mLastTesselateTime>self.mTerrainUpdateInterval): self.mLastTesselateTime = task.time cPos = base.camera.getPos() ix = int(round(cPos[0]/self.mHorizontalScale)) iy = int(round(-cPos[1]/self.mHorizontalScale)) print task.time,ix,iy self.mHeightFieldTesselator.setFocalPoint(ix,iy) self.updateHeightField()
self.__Nav.tickUpdate() return Task.cont
# Take a snapshot def snapShot(self): base.screenshot('Snap')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # Method name : setupKeyBindings # # Description: # # Load and register key bindings # # Input(s): # # None # # Output(s): # # None # def setupKeyBindings(self) : self.accept('h',self.__Nav.orientGodMode) self.accept('p',self.snapShot) self.accept('w',self.toggleWireframe) self.accept('t',self.toggleTexture) self.accept('m',self.__Nav.toggleAutoCentering) self.accept('escape',sys.exit) self.accept('.',self.__Nav.toggleDriving) self.accept('n',self.__Nav.nextNavigationMode) self.accept('+',self.__Nav.speedUp) self.accept('-',self.__Nav.slowDown) self.accept('home',self.__Nav.reset) self.accept('space',self.__Nav.turboBoostOn) self.accept('space-up',self.__Nav.turboBoostOff) self.accept('f11',self.__Nav.toggleSuperTurboBoost)
self.accept('d',self.__Nav.drivingForwardOn) self.accept('d-up',self.__Nav.drivingForwardOff) self.accept('e',self.__Nav.slidingUpOn) self.accept('e-up',self.__Nav.slidingUpOff) self.accept('v',self.__Nav.slidingDownOn) self.accept('v-up',self.__Nav.slidingDownOff) self.accept('f',self.__Nav.slidingRightOn) self.accept('f-up',self.__Nav.slidingRightOff) self.accept('s',self.__Nav.slidingLeftOn) self.accept('s-up',self.__Nav.slidingLeftOff) self.accept('c',self.__Nav.drivingBackwardOn) self.accept('c-up',self.__Nav.drivingBackwardOff)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
world = World()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Run the program
run()
-

pleopard
-
- Posts: 202
- Joined: Tue Apr 25, 2006 1:33 pm
by enn0x » Fri Nov 10, 2006 11:15 am
Another way to update the terrain could be the following demo.
It uses a python generator construct, which checks every fifth frame if the camera position is more than threshhold away from the current focal point, and only if it is then it updates the focal points, and in the next frame generates a new mesh. This way re-generating the terrain is done only when needed.
The demo runs without additional code, but a greyscale heightfield (in my case 256x256 pixel) and a texture is required.
I hope this thread will go on for some time, and additional code is added. Perhaps someone knows how to write a terrain shader instead of a simple texture, which blends several tileable textures (e.g. grass, mud, gravel, rock) depending on an alpha-map or terrain normals.
- Code: Select all
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject from pandac.PandaModules import HeightfieldTesselator from pandac.PandaModules import Filename from pandac.PandaModules import TextureStage from pandac.PandaModules import TexGenAttrib from direct.task.Task import Task
import sys
def terrainGenerator( ): filename = Filename( 'models/elevation.png' ) # 256 x 256 pixel tex = loader.loadTexture( 'models/grass.png' )
x0 = -128 # Origin of terrain mesh, panda units y0 = 128 # Origin of terrain mesh, panda units
fx = -999 # Focal point, pixel units fy = -999 # Focal point, pixel units threshhold = 8 # Threshhold for moving the focal point, pixel units hscale = 1.0 vscale = 20.0
# Create the tesselator tesselator = HeightfieldTesselator( 'Terrain' ) tesselator.setHeightfield( filename ) tesselator.setHorizontalScale( hscale ) tesselator.setVerticalScale( vscale ) tesselator.setFocalPoint( fx, fy )
# Dummy node for first loop node = tesselator.generate( )
while 1: x = int( ( x0 - camera.getX( ) ) / -hscale ) y = int( ( y0 - camera.getY( ) ) / hscale ) if abs( x - fx ) > threshhold or abs( y - fy ) > threshhold: fx = x fy = y tesselator.setFocalPoint( fx, fy ) yield( )
node.removeNode( )
node = tesselator.generate( ) node.setTexGen( TextureStage.getDefault( ), TexGenAttrib.MWorldPosition ) node.setTexScale( TextureStage.getDefault( ), 1.0, 1.0 ) node.setTexture( tex ) node.reparentTo( render ) node.setPos( x0, y0, -50 ) yield( )
yield( ) yield( ) yield( ) yield( )
class World( DirectObject ):
def __init__( self ):
# Input self.accept( 'escape', self.exit ) self.accept( '1', self.toggleWireframe ) self.accept( '2', self.toggleTexture ) self.accept( '3', self.screenshot )
# Base setup base.setBackgroundColor( 0, 0, 0 )
# Terrain self.terrain = terrainGenerator( ) self.terrain.next( )
# Tasks taskMgr.add( self.move, 'moveTask' )
def move( self, task ): self.terrain.next( ) return Task.cont
def exit( self ): sys.exit( )
def toggleWireframe( self ): base.toggleWireframe( )
def toggleTexture( self ): base.toggleTexture( )
def screenshot( self ): base.screenshot( 'screenshot' )
w = World( ) run( )
-
enn0x
-
- Posts: 1267
- Joined: Wed Nov 08, 2006 1:39 am
- Location: Germany, Munich
by pleopard » Sat Nov 11, 2006 4:23 pm
Aersome, yeah let's keep this thread rolling.
-

pleopard
-
- Posts: 202
- Joined: Tue Apr 25, 2006 1:33 pm
by Cyan » Sat Nov 11, 2006 4:35 pm
Any ideas to prevent popping?
-

Cyan
-
- Posts: 215
- Joined: Fri Jul 14, 2006 12:26 pm
- Location: Utah
by ThomasEgi » Sat Nov 11, 2006 6:15 pm
most elegant way would be to use a vertex shader for it. fewest cpu usage. but i'm not shure if it works if the vertex have different horizontal resolutions (well it might but i'm pretty shure it requires quite some work).
except of updating and resolution you dont have much choices (at least if i understood how this algorithm works).
well other algorithm may have fewer screen-space errors but this would require to change the terrain-algorithm.
-

ThomasEgi
-
- Posts: 2147
- Joined: Fri Jul 28, 2006 10:43 am
- Location: Germany,Koblenz
by Neophoenix » Sun Nov 12, 2006 7:21 am
I tried both code snipplets but each time panda gives me the same message which is Terrain mech is 0 triangles. What should I do?
-
Neophoenix
-
- Posts: 1
- Joined: Sun Nov 12, 2006 7:17 am
by enn0x » Sun Nov 12, 2006 3:37 pm
@Neophoenix
Hello,
one thing you could do is check that the path to your grayscale heightfield image is valid. I have checked, there is no error message if the file can not be found. Unlike when loading a mesh you can't leave away the file extension.
There is no heightfield image included in the distribution, so you have to create your own.
Hope this helps,
enn0x
-
enn0x
-
- Posts: 1267
- Joined: Wed Nov 08, 2006 1:39 am
- Location: Germany, Munich
by ThomasEgi » Sun Nov 12, 2006 6:44 pm
ah for the preventing the popping stuff and terrain in general. i'd like to point once more to the ranger mkII (plz david dont cut off my head)
http://web.interware.hu/bandi/ranger.html
and dont be deceived by the nice bumpmap. just switch over to wireframe mode and you'll see the truth. but even there is still room for improvement, first one: write it as vertex shader so the cpu wont be loaded with it. and second. a second vertex shader (or include it all into one) to smooth the vertex-movement.
the whole algorithm seems to be not all too different from the current one in panda (except that it is a real circle) and i think some more stuff.
just wanted to remind the people to not foreget about this one =)
anyway. a small vertex shader which keeps the "old" and the "updated" terrain-mesh in memory and smoothes the new-placed vertices along the zaxis to their new position (the movement should only be done when the camera is moveing) it should be able to prevent popping quite nice while keeping the current algorithm=) so any shaderspecialists volunteering?
-

ThomasEgi
-
- Posts: 2147
- Joined: Fri Jul 28, 2006 10:43 am
- Location: Germany,Koblenz
by enn0x » Mon Nov 13, 2006 6:24 am
@ThomasEgi
From what I see Ranger Mk2 is based on the SOAR algorithm, which is indeed a great way to render huge terrain with great detail. With SOAR there is no need to care for "popping", since SOAR creates a new mesh from scratch every frame (for example see http://web.interware.hu/bandi/ranger.html, the section about technology).
SOAR is quite different from the current Panda3d approach. These two screenshots show why:
http://www-static.cc.gatech.edu/~lindstro/papers/visualization2001a/fig11b.jpg
http://www-static.cc.gatech.edu/~lindstro/papers/visualization2001a/fig11c.jpg
(both from "Visualization of Large Terrains Made Easy" by Peter Lindstrom, Valerio Pascucci)
First, SOAR has high detail inside a clipping area, and not around a (x,y) position, and second, SOAR increases detail where the terrain is "uneven", and uses few polygons where the terrain is "flat". In short: Given a camera position and orientation SOAR optimizes the terrain mesh for polygon count, and in a very effective way.
The terrain tesselator that comes with Panda3d is much simpler (sorry, no offence meant). High detail when close to the focal point, and low detail when far way, even is the terrain is only a plane.
There are numerous ways to do terrain, and each way has it's own advantages and disadvantages. SOAR is in my opinion good for rendering large terrain from bird's eye view, for example when creating a flight simulator. When running around FPS style one usually doesn't cares about what is behind the next hill, since this part of the terrain it is not visible.
For now I am happy that Panda3d comes with a builtin way to do terrain, even though it may not be suited for every kind of application. For those who really need SOAR have a look at Delta3d ( http://delta3d.org/), an open source game engine backed by the US armed forces, duh. It aims at simulations software mainly, has python bindings, but is Windows.
-
enn0x
-
- Posts: 1267
- Joined: Wed Nov 08, 2006 1:39 am
- Location: Germany, Munich
by Josh Yelon » Mon Nov 13, 2006 12:01 pm
For the last game that I was a developer on, I actually wrote a terrain system that creates a new mesh every frame. It prevented popping by gradually morphing new detail in. That looked okay, but a little weird --- in some ways, it's better for the terrain to pop than for it to look like it's moving.
However, I bet I can help you reduce the appearance of popping. If you're using vertex lighting to light the terrain, then every time the terrain changes, the lighting changes too. That magnifies the appearance of popping. My suggestion would be to experiment with either a fixed lightmap, or a shader that computes per-pixel lighting based not on the current shape of the terrain, but rather, on the "true" shape of the terrain as defined by the original raw heightfield.
Josh Yelon, Teacher, Carnegie Mellon Entertainment Technology Center
-
Josh Yelon
-
- Posts: 1360
- Joined: Wed Mar 30, 2005 8:30 pm
by ThomasEgi » Mon Nov 13, 2006 12:23 pm
shure they work different. but if you you look at the demo and at the current panda terrain. it is very simmilar. especialy if you look from the top down onto the terrain. so my guess is that either the demo is not really what was described in the paper or just looks like it isn't.
because popping even occurs in the ranger demo =)
either way. a vertex shader should be able to prevent popping in the current panda approach.
-

ThomasEgi
-
- Posts: 2147
- Joined: Fri Jul 28, 2006 10:43 am
- Location: Germany,Koblenz
by chombee » Mon Feb 12, 2007 8:28 am
enn0x -- I tried your code with a 256*256 greyscale image for heightfield and a corresponding 256*256 colour image for texture. Problem is that the texture is not drawn once but many times over the terrain model. Is this what you intended, use of a small repeating texture?
A problem is that if you repeat a small texture that many times over a big model it looks all psychedelic when the whole model is viewed from far away. What you really need is a big texture that repeats just once over the entire model, and a smaller detail texture that repeats many times but is only shown on parts of the terrain that are close to the camera. This is what I was doing with the terrain code I was writing some time ago. Auto-generating or varying either of these textures based on properties of the model would be good also.
A good texturing example would be much appreciated! pleopard maybe? Or the Panda dev that added this heightfield code?
-
chombee
-
- Posts: 244
- Joined: Fri Jan 13, 2006 10:09 am
by enn0x » Mon Feb 12, 2007 10:22 am
It's been some time since I did this example, and to be honest I didn't pay much attention to texturing here, since the goal was only to show how HeightfieldTesselator could be used.
But you are right, applying the same small grass tile again and again over a large terrain doesn't look good.
Using a colormap and a detailmap is certainly one way to do a good looking terrain texture, and a cheap one, since not much memory is required for textures. Since I am a fan of RPG's I would prefer to use splattering techniques. That is, have a few base textures tiles, e.g. grass, dirt, cobbles, sand, rock. And then blend them into each other with alpha-maps of the same resolution as the heightfield.
I would also appreciate examples of how to do state-of-art terrain texturing & lighting with Panda3D.
enn0x
-
enn0x
-
- Posts: 1267
- Joined: Wed Nov 08, 2006 1:39 am
- Location: Germany, Munich
by rdb » Tue May 01, 2007 7:22 am
enn0x, if you don't mind, i made a big update to your code.
You can access it here
How to use it:
- Code: Select all
self.myTerrain=terrainGenerator("heightmap.png",loader.loadTexture("texture.png")) taskMgr.add(self.myTerrain.next,"terrainUpdate")
-
rdb
-
- Posts: 8575
- Joined: Mon Dec 04, 2006 5:58 am
- Location: Netherlands
-
Return to Code Snippets
Who is online
Users browsing this forum: No registered users and 0 guests
| | |