Enhance constrast of edges on procedure texture

Hi !

I’m trying to display a ground profile and I’m not happy at all with the rendering :confused:
This ground profile only comprises random linear slopes and plateau. I would like to enhance the contrast of the edges where the slope is changing.

I’m recreating this ground profile using the following snippet of mine:

def make_height_map(height_map: np.ndarray) -> Geom:
    """Create height map.
    """
    # Compute the number of vertices
    num_vertices = int(np.prod(height_map.shape[:2]))

    # Define vertex format
    vformat = GeomVertexFormat.get_v3()
    vdata = GeomVertexData('vdata', vformat, Geom.UH_static)
    vdata.uncleanSetNumRows(num_vertices)
    vertex = GeomVertexWriter(vdata, 'vertex')

    # # Add grid points
    for i in range(height_map.shape[0]):
        for j in range(height_map.shape[1]):
            vertex.addData3(*height_map[i, j])

    # Make triangles
    prim = GeomTriangles(Geom.UH_static)
    for j in range(height_map.shape[1]):
        for i in range(height_map.shape[0] - 1):
            k = j * height_map.shape[0] + i
            if j < height_map.shape[1] - 1:
                prim.add_vertices(k + 1, k, k + height_map.shape[0])
            if j > 0:
                prim.add_vertices(k, k + 1, k + 1 - height_map.shape[0])

    # Create geometry object
    geom = Geom(vdata)
    geom.add_primitive(prim)

    return geom

model = GeomNode('floor')
node = self.render.attach_new_node(model)
model.add_geom(make_height_map(height_map))
material = Material()
material.set_ambient(Vec4(0.95, 0.95, 1.0, 1.0))
material.set_diffuse(Vec4(0.95, 0.95, 1.0, 1.0))
material.set_specular(Vec3(1.0, 1.0, 1.0))
material.set_roughness(0.4)
node.set_material(material, 1)
node.set_two_sided(True)

Reading the documentation, it seems to be related to not providing the normal information when I create the geometry. Is it possible to automatically deduce the normal without specifying it manually ?

Here is what I get by specifying the normal:

I’m not really satisfied either x) The linear slopes appear curvy and there is strange shadow at several places out of nowhere :confused:

Apparently increasing the resolution of the mesh is helping:

Yet there is still the strange shadow. I think it is coming from a directed light I added.

Try disabling the two-way model. This can affect the shadow.

You need to use pixel-by-pixel lighting that does not require a large resolution of the model to display the shadow. You can just call the auto shader.

Ok I see, thank you! I will have a look.

What about the strange shadow ? I don’t understand how to fix this :confused:

Well, have you tried serega’s suggestions? Those were, I believe, directed at solving the shadow issue.

If so, however, how are you generating your normals? I think that I recall that Panda’s shadow-mapping code uses the normals of geometry to which it’s applied in order to produce a surface-offset. Thus, if there’s a problem with those normals, I could see it causing a problem with the shadow-mapping.

A correct normal vector is necessary to achieve correct shading. (Two-sided rendering is also discouraged since it interferes with shadow mapping, however, I don’t think you are primarily looking for shadow mapping in this instance.)

You will need to calculate this normal vector. You can calculate it by determining the difference between the neighbouring height values in the X and Y direction (say, dx and dy). Like so:

dx = height_map[x + 1, y] - height_map[x - 1, y]
dy = height_map[x, y + 1] - height_map[x, y - 1]
v = Vec3(dx * 0.5, dy * 0.5, 1)
v.normalize()

You will need to scale the values appropriately based on the ratio of the distance between adjacent vertices and the vertical scale of the heightmap.

This is using bilinear sampling; a better result may be achieved using trilinear sampling (also taking the adjacent corner vertices into account). I will leave this as an exercise for the reader.

Thank you for this piece of code ! At the end I computed the analytical normal and it fixed the issue of weird rendering. Yet, I still have the weird big square shadow. I will try to disable the two-sided rendering to see if it helps but I need it :confused:

Ok I checked, two-sided is not helping and makes it worst, and it is not the root cause. I’m still facing the same issue without it, and rendering the ground profile with high density of vertices:
screenshot-2021-10-27-21-58-25

Looking at that latest screenshot, I don’t see an obviously-incorrect shadow: there’s a shadow cast on the ground by the character, and narrow strips of shadow cast, I presume, by the ground itself.

Now, those narrow strips do only exist in a limited region–but I wonder whether that might not indicate the limits of your shadow-buffer.

So, how are you setting up your shadow-casting light?

Now, those narrow strips do only exist in a limited region

Exactly ! This is the issue.

but I wonder whether that might not indicate the limits of your shadow-buffer.

The shadow-buffer is limited to a given area ?!

So, how are you setting up your shadow-casting light?

I’m doing this:

light = AmbientLight('Ambient Light')
light.set_color(Vec3(0.5, 0.5, 0.5))
node = self.render.attach_new_node(light)
node.get_node(0).set_color((0.5, 0.5, 0.5, 1.0))

light = DirectionalLight('Directional Light')
light.set_color(Vec3(0.5, 0.5, 0.5))
light.set_shadow_buffer_size((1024, 1024))
lens = light.get_lens()
lens.set_film_size(5.5, 5.5)
lens.set_near_far(10, 30)
node = self.render.attach_new_node(light)
node.set_pos(8.0, 8.0, 10.0)
node.look_at(0.0, 0.0, 0.0)
node.get_node(0).set_color((0.5, 0.5, 0.5, 1.0))

I must admit I’m not understanding 100% of what I’m doing :sweat_smile:

I mean, it’s a texture–it has limited size.

Now, you can project it onto an arbitrarily-sized space via the camera-lens, I do believe–but the larger the space, the lower the quality of the shadow, as each shadow-pixel is stretched out over a larger region.

Hmm… I might suggest increasing your film-size–I suspect that (5.5, 5.5) is fairly small.

(Note that, when dealing with very large scenes, there are more advanced techniques that allow some degree of coverage of such broad spaces. However, let’s not worry about that until it seems called for!)

That’s fair! I daresay that I’ve had my experiences of fiddling with elements that I don’t yet have a full grasp on! ^^;

I mean, it’s a texture–it has limited size. Now, you can project it onto an arbitrarily-sized space via the camera-lens, I do believe–but the larger the space, the lower the quality of the shadow, as each shadow-pixel is stretched out over a larger region.

Yes that’s what I had I mind.

Hmm… I might suggest increasing your film-size–I suspect that (5.5, 5.5) is fairly small.

Ah you are right ! It is the property controlling the area of the shadow ! Now it works as expected. The resolution is not amazing but this is another issue.

1 Like

Well, you can perhaps increase the resolution of your shadow-buffer, which I see that you currently have as (1024, 1024).

(I’m not sure offhand of what the highest “safe” value for this would be, let me caution.)

I already tried to play with it and it is already the maximum I can afford to run smoothly on all the machines I need to support. I’m quite limited because some of them are notebooks with integrated graphics only… But thank you for the advice! Anyway it is good enough. I will see if I can detect the configuration and use something higher if I detected a dedicated GPU.

Aah, fair enough! Yes, if you’re dealing with low-end machines, then you are likely to be stuck with less-impressive settings, I do fear.

(Again, there are some more-advanced techniques that might help–but then, I don’t know whether they’d work well on low-end machines either.)

You can use show_frustum() to make the frustum appear visually, which may help in tweaking it.
Also see this post, it touches on frustum adjustment methods:

1 Like

Wow it likes neat and easy ! I will definitely implement this. Thank you !

I adapted adjust_static to my showbase and it does wonder ! It is exactly what I need :slight_smile: Just the size of the film could be smaller if the frustum was aligned the rendering area, thereby increasing the resolution of the shadows without additional cost. But I bet it is not possible / hard to do ? I will give a try to node.set_hpr we will see if it works. But I’m a bit surprise it is not aligned by default, since the floor itself is aligned with x/y axes.

image

EDIT: Ok i understand, I stupid x) it is just oriented according to the direction of the light / shadow. So there is nothing we can do about it (like having different orientation for frustum and shadow.

You can use adjust_dynamic to have it match which area the camera is currently rendering, in case the camera is generally zoomed in on some part of the scene. However, the algorithm used is suboptimal and will generally create a too large frustum (but sometimes still better than nothing).