How get height of shader terrain mesh for add grass/tree in map?

Hello i want to add in my terrain 3d grass and tree, i can greate 100000 tree with very good performance but how can i get height of terrain for add into terrain my 100000 grass and trees ?

i have this code :

from direct.showbase.ShowBase import ShowBase
from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
from panda3d.core import SamplerState

from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
from panda3d.core import Geom, GeomPoints, GeomNode, NodePath
from panda3d.core import TextureStage, TexGenAttrib, TransparencyAttrib
from panda3d.core import AmbientLight, DirectionalLight, Vec4
from random import uniform


class ShaderTerrainDemo(ShowBase):
    def __init__(self):

        # Load some configuration variables, its important for this to happen
        # before the ShowBase is initialized
        load_prc_file_data("", """
            textures-power-2 none
            gl-coordinate-system default
            window-title Panda3D ShaderTerrainMesh Demo
        """)

        # Initialize the showbase
        ShowBase.__init__(self)

        # Increase camera FOV as well as the far plane
        self.camLens.set_fov(90)
        self.camLens.set_near_far(0.1, 50000)

        # Construct the terrain
        self.terrain_node = ShaderTerrainMesh()

        # Set a heightfield, the heightfield should be a 16-bit png and
        # have a quadratic size of a power of two.
        self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png")

        # Set the target triangle width. For a value of 10.0 for example,
        # the terrain will attempt to make every triangle 10 pixels wide on screen.
        self.terrain_node.target_triangle_width = 10.0

        # Generate the terrain
        self.terrain_node.generate()

        # Attach the terrain to the main scene and set its scale. With no scale
        # set, the terrain ranges from (0, 0, 0) to (1, 1, 1)
        self.terrain = self.render.attach_new_node(self.terrain_node)
        self.terrain.set_scale(1024, 1024, 100)
        self.terrain.set_pos(-512, -512, -70.0)

        # Set a shader on the terrain. The ShaderTerrainMesh only works with
        # an applied shader. You can use the shaders used here in your own application
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain_tex_f.glsl")
        self.terrain.set_shader(terrain_shader)
        self.terrain.set_shader_input("camera", self.camera)

        # Shortcut to view the wireframe mesh
        self.accept("f3", self.toggleWireframe)
        
        self.setBackgroundColor(0.6, 0.65, 1.0)

        # render-to-texture stuff
        altBuffer=self.win.makeTextureBuffer("spritebuf", 256, 256)
        altRender=NodePath("alt render")
        altCam=self.makeCamera(altBuffer)
        altCam.reparentTo(altRender)
        altCam.setPos(0.25, -12, 0)
        teapot=loader.loadModel('models/jack')
        teapot.reparentTo(altRender)
        teapot.setPos(0, 0, -1)
        self.accept("v", self.bufferViewer.toggleEnable)
        self.bufferViewer.setPosition("llcorner")
        self.bufferViewer.setCardSize(1.0, 0.0)

        # lighting
        dlight = DirectionalLight('dlight')
        alight = AmbientLight('alight')
        dlnp = altRender.attachNewNode(dlight) 
        alnp = altRender.attachNewNode(alight)
        dlight.setColor(Vec4(0.8, 0.8, 0.5, 1))
        alight.setColor(Vec4(0.2, 0.2, 0.2, 1))
        dlnp.setHpr(0, -60, 0) 
        altRender.setLight(dlnp)
        altRender.setLight(alnp)

        # vertex writer
        vdata = GeomVertexData('points', GeomVertexFormat.getV3(), Geom.UHDynamic)
        vwriter = GeomVertexWriter(vdata, 'vertex')

        #randomly generated vertex coordinates
        number = 100000
        for i in range(number):
            vwriter.addData3f(uniform(-200,200), uniform(-200,200), uniform(0,0))

        # create geom
        points = GeomPoints(Geom.UHDynamic)
        points.addNextVertices(number)
        points.closePrimitive()
        geo = Geom(vdata)
        geo.addPrimitive(points)
        gnode = GeomNode('points')
        gnode.addGeom(geo)
        np = render.attachNewNode(gnode)

        # point sprite effect
        np.setTransparency(TransparencyAttrib.MDual)
        np.setTexGen(TextureStage.getDefault(), TexGenAttrib.MPointSprite)
        np.setTexture(altBuffer.getTexture())
        np.setRenderModeThickness(32)


ShaderTerrainDemo().run()

and i have this :

i need to modify this loop for :
for i in range(number):
vwriter.addData3f(uniform(-200,200), uniform(-200,200), uniform(0,0))

and replace -200 200 by the real height of terrain, but how ? thanks for advance

Your terrain uses a heightmap, just use the values from the heightmap

This should get you started:

#randomly generated vertex coordinates
number = 100000
peeker=self.terrain_node.heightfield.peek()
for i in range(number):
    x=uniform(0,1)
    y=uniform(0,1)
    z=VBase4()
    peeker.lookup(z, x, y) #rgba values will be stored in the 'z' variable
    #set the right scale, not sure what it should be in this case
    #I think the x,y should be from -512 to 512 and z from - 70 to 30 ?
    vwriter.addData3f((x*1024.0)-512.0, (x*1024.0)-512.0, z[0]*100.0-70)

for x and y it’s good, i have terrain with 512px
but z not work, i updated code and i send result and my heightmap

from direct.showbase.ShowBase import ShowBase
from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
from panda3d.core import SamplerState

from panda3d.core import *

from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
from panda3d.core import Geom, GeomPoints, GeomNode, NodePath
from panda3d.core import TextureStage, TexGenAttrib, TransparencyAttrib
from panda3d.core import AmbientLight, DirectionalLight, Vec4
from random import uniform


class ShaderTerrainDemo(ShowBase):
    def __init__(self):

        # Load some configuration variables, its important for this to happen
        # before the ShowBase is initialized
        load_prc_file_data("", """
            textures-power-2 none
            gl-coordinate-system default
            window-title Panda3D ShaderTerrainMesh Demo
        """)

        # Initialize the showbase
        ShowBase.__init__(self)

        # Increase camera FOV as well as the far plane
        self.camLens.set_fov(90)
        self.camLens.set_near_far(0.1, 50000)

        # Construct the terrain
        self.terrain_node = ShaderTerrainMesh()

        # Set a heightfield, the heightfield should be a 16-bit png and
        # have a quadratic size of a power of two.
        self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png")

        # Set the target triangle width. For a value of 10.0 for example,
        # the terrain will attempt to make every triangle 10 pixels wide on screen.
        self.terrain_node.target_triangle_width = 10.0

        # Generate the terrain
        self.terrain_node.generate()

        # Attach the terrain to the main scene and set its scale. With no scale
        # set, the terrain ranges from (0, 0, 0) to (1, 1, 1)
        self.terrain = self.render.attach_new_node(self.terrain_node)
        self.terrain.set_scale(1024, 1024, 100)
        self.terrain.set_pos(-512, -512, -70.0)

        # Set a shader on the terrain. The ShaderTerrainMesh only works with
        # an applied shader. You can use the shaders used here in your own application
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
        terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain_tex_f.glsl")
        self.terrain.set_shader(terrain_shader)
        self.terrain.set_shader_input("camera", self.camera)

        # Shortcut to view the wireframe mesh
        self.accept("f3", self.toggleWireframe)
        
        self.setBackgroundColor(0.6, 0.65, 1.0)

        # render-to-texture stuff
        altBuffer=self.win.makeTextureBuffer("spritebuf", 256, 256)
        altRender=NodePath("alt render")
        altCam=self.makeCamera(altBuffer)
        altCam.reparentTo(altRender)
        altCam.setPos(0.25, -12, 0)
        teapot=loader.loadModel('models/jack')
        teapot.reparentTo(altRender)
        teapot.setPos(0, 0, -1)
        self.accept("v", self.bufferViewer.toggleEnable)
        self.bufferViewer.setPosition("llcorner")
        self.bufferViewer.setCardSize(1.0, 0.0)

        # lighting
        dlight = DirectionalLight('dlight')
        alight = AmbientLight('alight')
        dlnp = altRender.attachNewNode(dlight) 
        alnp = altRender.attachNewNode(alight)
        dlight.setColor(Vec4(0.8, 0.8, 0.5, 1))
        alight.setColor(Vec4(0.2, 0.2, 0.2, 1))
        dlnp.setHpr(0, -60, 0) 
        altRender.setLight(dlnp)
        altRender.setLight(alnp)

        # vertex writer
        vdata = GeomVertexData('points', GeomVertexFormat.getV3(), Geom.UHDynamic)
        vwriter = GeomVertexWriter(vdata, 'vertex')

        #randomly generated vertex coordinates
        """number = 100000
        for i in range(number):
            vwriter.addData3f(uniform(-200,200), uniform(-200,200), uniform(0,0))"""
            
        #randomly generated vertex coordinates
        number = 10000
        peeker=self.terrain_node.heightfield.peek()
        for i in range(number):
            x=uniform(-512,512)
            y=uniform(-512,512)
            z=VBase4()
            peeker.lookup(z, x, y) #rgba values will be stored in the 'z' variable
            #set the right scale, not sure what it should be in this case
            #I think the x,y should be from -512 to 512 and z from - 70 to 30 ?
            #vwriter.addData3f((x*1024.0)-512.0, (x*1024.0)-512.0, z[0]*100.0-70)
            vwriter.addData3f(x, y, z[0]*100.0-70)

        # create geom
        points = GeomPoints(Geom.UHDynamic)
        points.addNextVertices(number)
        points.closePrimitive()
        geo = Geom(vdata)
        geo.addPrimitive(points)
        gnode = GeomNode('points')
        gnode.addGeom(geo)
        np = render.attachNewNode(gnode)

        # point sprite effect
        np.setTransparency(TransparencyAttrib.MDual)
        np.setTexGen(TextureStage.getDefault(), TexGenAttrib.MPointSprite)
        np.setTexture(altBuffer.getTexture())
        np.setRenderModeThickness(32)


ShaderTerrainDemo().run()

i don’t undestand why use this operation :
z[0]*100.0-70

normally your solution should work since I have the field has -70 and I increase the height not 100, so z*100-70, but why doesn’t it work?

The texture peeker peeker.lookup(pixel,u,v,w) expects the u,v, (w) coordinates in 0-1 range just like texture coordinates. If you pick a random point using random.uniform(-512,512) you’ll get something like 134.5678,-23.456 if you use that to look up a texture point the integer part will be more or less ignored and you’ll get the pixel at uv 0.5678, -0.456 and that’s not the point you want.

ok,I’ll test tonight.
I have other question, it’s possible to modify uv coordinate of terrain ?

for example in coordinate (0,0,0) modify to (-1,0,40) ? with use peeker or other function ?

it’s work thanks for your help,
i repeat my last question, what is function for edit/modify uv coordinate of terrain ?