Terrain insert object with map

Hello i want to insert repeat object (like grass.egg, rock.egg, tree.egg) in my terrain with use similiar technical like heightmap

I create terrain with heightmap
I want to insert in my terrain repeat tree.egg when rockmap.png is black

for example this is my terrain :


this is my tree map :

i want that left map have lot of trees and right map nothing, I’m not very good at glsl
is there any code in panda3d that could help me do this or who does this?

You might want to do this on the CPU and not the GPU (e.g., with GLSL). I would probably do something like the following when loading the scene:

  • Define a way to sample the tree map. This would likely be a 2D uniform grid over the terrain.
  • For each sample point, do a lookup into the texture/image using PNMImage
  • Based on the result, place an instance of the object.

This could result in a lot of objects, which could have pretty noticeable performance impacts. To fix this, I would probably use some of the NodePath.flatten_*() functions. In particular, flatten_strong().

EDIT: Doing this on the GPU would likely be a lot faster to render, but it gets a lot more complicated. It would likely involve the use of a compute shader and multi-draw indirect type features (not show how/if these are exposed in Panda). This would also limit the type of graphics hardware that could run the game. So, I would only look toward the GPU if the CPU solution is still too slow.

I’ve done it the way @Moguri suggests using GeoMipTerrain.

https://www.panda3d.org/reference/python/panda3d.core.GeoMipTerrain.php

Some other details that might help:

I had a dictionary of (R,G,B) keys that corresponded to colors in a PNMImage. The value held something like {‘path’: ‘path/to/model_or_picture’, ‘is_billboard’: True }. That way you have the path, and you can switch between loading a model and making an image into a billboard (CardMaker). Of course, you could just check the extension.

Then everything was under a dummy node, so I dummyNP.findAllMatches("**/*" + self.modelEnding + self.instanceEnding) and removed all models outside a given radius.

If you use GeoMipTerrain, don’t forget to play with setBlockSIze and setNearFar for performance.

I don’t know if I said it right, I’d like something like that :

when i move camera tree or grass appear
I unfortunately can’t find the code

i foud maybe code :


and maybe terrain code :

but terrain have no grass/tree and the project seems to be abandoned

I’ve written some sample code to show how to place many copies of the same object on a heightmap terrain, here I’m using ~21.5k (hardware) instances of grass on a ShaderMeshTerrain, but the principle is the same for trees, rocks or whatever you need:


(you may want to clone/download the whole repo to get the models and textures if you want to run it)

1 Like

thanks wezu for your sample it’s work.

I try to add grass lod with panda LODNode like this :

grass_lod = LODNode('grass_lod')
grass_model = NodePath(grass_lod)

lod2 = NodePath('empty_3d_grass')
lod2.reparentTo(grass_model)
grass_lod.addSwitch(1000, 100)

lod1 = self.loader.load_model('../../models/grass')
lod1.reparentTo(grass_model)
grass_lod.addSwitch(100, 0)

but all 3d grass object appears or disappears at the same time, I think it’s because panda3d loads only 1 object
do I have to go through the glsl files for the lod or is it possible to use LODNode ?

If you want both LOD and hardware instancing you will need to divide the objects into smaller chunks and decide based on the distance to camera what model (lod level) use for all the objects in that chunk.
Arranging the chunks in a octree/quadtree may also be a good idea.

I used a grass model made from 8 triangles so lod is kind of pointless in thiscase, overdraw is probably the bottleneck here not vertex count.

yes I don’t want to make lod but limited 3d grass display distance
I thought lod was the best way to do it in panda3d

There are a few options if you just want to disable far away objects:

You can discard fragments in the fragment shader based on distance (but I’d suggest doing a benchmark to see of that has any performance benefits for your scene/hardware).

You can make degenerated triangles (set all vertex coordinates to 0.0) in the vertex shader - just make sure you do it for all verts of a triangle. Using gl_ClipDistance could work better for this, but this feature isn’t supported at the moment.

You can finally use a geometry shader to cull primitives - but you’d have to use a geometry shader, and the first rule of using geometry shaders is ‘don’t use geometry shaders’.

Okay, thank you for your answer.
I’ll look at the first solution, don’t display the fragments too far away, I don’t think it’s complicated.

I’m trying to understand how the glsl works
i have some question :

  1. Why in your code you have grass_f.glsl and grass_v.glsl, what is difference between this 2 files ?
  2. Can i print glsl variable in console ?
  3. How can i have get panda3d camera position, i not found this in manual :slight_smile:https://www.panda3d.org/manual/?title=List_of_GLSL_Shader_Inputs

For hide far 3d grass maybe i create this code (for example with only x coordinate)
if player_position.x - grass3d.x < 50 {
print 3d grass
}

in your code grass3d.x is vertex.x no ?

  1. grass_v.glsl is the vertex shader, grass_f.glsl is the fragment shader.
  2. You can’t print from glsl to the console, the shader code is run on the gpu, not the cpu (I suppose if you try very, very hard you could).
  3. The best way I can think of of getting the camera position:
    -set a shader input with the camera node:
    self.grass.set_shader_input('camera', self.camera)
  • now in the shaders you can use uniform vec3 wspos_camera; to get the world pos of the camera node (maybe there’s also one for view space? idk, I’ll go ask on discord…)
  • use the p3d_ModelMatrix to transform the vertex into world space
  • use the distance() function to get the distance from camera to the vertex.
  • use the discard; statement to discard fragments.

I’m not sure where’s the best place to add this code, you can calculate the world space position in the vertex shader and pass it to the fragment shader or calculate the distance on the vertex and pass just a 0.0 or 1.0 float to the fragment shader telling it to either draw the fragments or discard.

If you’ll have problems with this, let me know, I’ll add the code to do that in the sample I linked earlier.

EDIT:
Actually, some of what I’ve written above is stupid. Yeah, it should work, but the easy way to get the distance to the camera on the vertex shader is just: float distance_to_camera= -vec4(p3d_Vertex * p3d_ModelViewMatrix).z;

is there a way to debug glsl code easily ?
i create this variable
float distance_to_camera= -vec4(p3d_Vertex * p3d_ModelViewMatrix).z;
but i can not print this variable i don’t know if this code works (if my code is bad I have glsl error in console)

i update the code with your solution with distance_to_camera, maybe i need edit grass_v.glsl :
//GLSL
#version 150

in vec4 p3d_Vertex;
in vec4 p3d_ModelViewMatrix;
in vec2 p3d_MultiTexCoord0;

uniform samplerBuffer grass_buf_tex;
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform float osg_FrameTime;

out vec2 UV;


void main()
    {
    vec3 pos_offset= texelFetch(grass_buf_tex, gl_InstanceID).xyz;
    vec4 vertex=p3d_Vertex;
    float distance_to_camera= -vec4(p3d_Vertex * p3d_ModelViewMatrix).z;

    if (distance_to_camera < 5) {
        float anim_co=vertex.z*0.2;
        float animation =sin(0.7*osg_FrameTime+float(gl_InstanceID))*sin(1.7*osg_FrameTime+float(gl_InstanceID))*anim_co;

        vertex.xy += animation;
        vertex.xyz+=pos_offset;
        gl_Position = p3d_ModelViewProjectionMatrix *vertex;

        UV = p3d_MultiTexCoord0;
    }
    }

but it’s not work, all 3d grass appear, I think I didn’t understand.

There are some errors in the shader and you need to change both the vertex and fragment shaders. Not writing to gl_Position will not make the polygons disappear, it will probably fail to compile.
Try this (not tested, I’m currently on mobile):
grass_v.glsl:

//GLSL
#version 150
in vec4 p3d_Vertex;
// p3d_ModelViewMatrix is a uniform matrix, not a vertex attribute
// in vec4 p3d_ModelViewMatrix;
uniform mat4 p3d_ModelViewMatrix;
in vec2 p3d_MultiTexCoord0;

uniform samplerBuffer grass_buf_tex;
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform float osg_FrameTime;

out vec2 UV;
// we can not discard a vertex, so we will pass the distance to camera
// to the fragment shader, because it can discard fragments
out float distance_to_camera;

void main()
    {
    vec3 pos_offset= texelFetch(grass_buf_tex, gl_InstanceID).xyz;
    vec4 vertex=p3d_Vertex;
    distance_to_camera= -vec4(p3d_Vertex * p3d_ModelViewMatrix).z;
    //the verex shader *must* write to gl_Position
    //without that the position of the vertex is undefined and probably should raise an error
    //if (distance_to_camera < 5) {
        float anim_co=vertex.z*0.2;
        float animation =sin(0.7*osg_FrameTime+float(gl_InstanceID))*sin(1.7*osg_FrameTime+float(gl_InstanceID))*anim_co;

        vertex.xy += animation;
        vertex.xyz+=pos_offset;
        gl_Position = p3d_ModelViewProjectionMatrix *vertex;

        UV = p3d_MultiTexCoord0;
    //}
    }

grass_f.glsl:

#version 150
in vec2 UV;
//distance to camera, value passed from the vert shader
in float distance_to_camera;
 
out vec4 final_color;

uniform sampler2D p3d_Texture0;

void main()
 {
  //the value here probably needs to be tweaked    
  if (distance_to_camera > 5.0) 
    {
    //do not write the fragment using the discard statement.    
    discard;
    }
  vec4 color = texture(p3d_Texture0,UV);
  final_color=color;
}

For the record, I’m working on a feature in a development branch of Panda that makes automatic instancing in conjunction with LOD easily possible.

I’ve made a few errors myself. The updated, tested shaders are over on github. I just want to note that on my PC, with this scene, these distances, this amount of grass, these models, discarding the grass based on distance has no significant performance effect.

thanks wezu i try your new code and it’s work !
rdb: yes it’s good idea