Planet sphere tessellation and displacmenet

you can find the math module and its textures in the game engine manual - they have enough parameters to generate surfaces without drawn height textures - but this means that you will have to control the scale, noise octaves, and other parameters very precisely to get a good quality landscape if you decide to use only mathematical seamless textures

The RGBA values are not normalized, they are ranged 0-255, dividing the blue element by 255.0 will match the displace modifier strength to VERT_SCALAR, as long as the scale of the object is 1.0.

I dont understand, Im not sure the script are meant to be the exactly like displace modifier, the vertscalar just seems to be divided by scalar values to me.
Should I change the rgba functions?

rgba = cont.owner['img_data'][id_num:id_num+4]
    if not isinstance(rgba, list):
        print("ERROR on loc:", x, y) 
        return[0,0,0,0]
    return rgba

I was mainly talking as how VERT_SCALAR needs a value different from the displace modifier in order to achiever similar displacement. This ‘fix’ will produce similar height with the same value:

vert.z = get_closest_pixel(cont, tex.source, vert_x, vert_y)[0]/255.0 * VERT_SCALAR

Divided by 255.0 because the RGBA are ranged 0-255.

The issue on the margins is solved by changing extension to Extend, in Image Mapping, leaving the cropping values to the defaults, (0.0 min, 1.0 max).
Also, the displace modifier uses the interpolation filtering if enabled, so you should disable Interpolation in the Image Sampling section, you need to change the engine to Blender Render to see this option.
With those changes this is what I get:


The shaded mesh is the displace modifier (Midlevel = 0.0, Strength = 1.0 ), the red wire is the script (VERT_SCALAR = 1.0).

The modifier is different so the change is not worked.
My displace modifier has strength to 160 and midlevel 0.5, the interpolation does’nt even matter only minor effect, and 2048 pxl heightmap image is used.
The vert z has 80% accuracy on the edges and 20% on the center of the entire map image.

The divided rgba only makes the plane totally flat so I did the opposite which is Multiply it to 2.5 etc but still don’t find 100% match or closer.
But I found another theory that the midlevel should added on the script, that makes the vert.z is inaccurate
But most of the issues is the values is opposite between edges and center of the map, its just switched/swaped.
I just need to switch the values effect from edges to the center of the map.
I will gave my heightmap files here:


So use this on flat plane with 73x73 faces total 5.329 faces, however the dimension is huge 233.238 dimension size
You can use it on displace modifier as well, with global texture coordinates, mid level 0.500 and 160 strength

I would suggest you using values that represent the displace modifier closely. I also had to figure out the crop maximum values that you are using. I changed the script like this:

# initial values
displace_modifier_strength = 160.0
displace_modifier_midlevel = 0.5

SCALAR = 0.001224 # = Image Mapping Crop Maximum
VERT_SCALAR = displace_modifier_strength
VERT_MIDLEVEL = VERT_SCALAR * displace_modifier_midlevel
    # height calculation
    heightValue = get_closest_pixel(cont, tex.source, vert_x, vert_y)[0]
    heightValue /= 255.0 # normalize the 8-bit value
    vert.z = heightValue * VERT_SCALAR - VERT_MIDLEVEL

This is what I get:
Displace-comparison

It is not 100% perfect, but really close, maybe the modifier samples the height in-between pixels while the script is ‘flooring’ the sampling location.

Wait…there is something wrong with the image format after I chage it to jpg is the same result with the script :face_with_monocle:

So this is the new files re-created from scratch it was the same result just a bit of number to play with
Terrain New TEST.rar (749.0 KB)

However…the Displace Modifier is not the result what I want to, I believe there is something with the “uncompressed” png format that I use for so long.
Then the script won’t work on such bigger files, I can’t Upload it because the size is 8mb to upload in this topic.

The Z location of the meshes of the file are not the same, that’s why the are so apart. Change the Z location so they are the same and try it with this script:

import bge
import math
import mathutils

midlevel = 0.5
SCALAR = 0.001224
VERT_SCALAR = 160.0 # displace modifier strength
IMAGE_PATH = bge.logic.expandPath('//Aldenia Height Map 2048 Pxl.jpg')

VERT_MIDLEVEL = VERT_SCALAR*midlevel
VERT_SCALAR /= 255.0 # this will normalize the 8-bit height value

#RECALC_NORMALS = False
RECALC_PHYSICS = True

PLAYER = 'Player'

def init(cont):
    #Load Heightmap
    obj = cont.owner
    mat_id = bge.texture.materialID(obj, "MA" + "LandscapeMainMaterial Global")
    tex = bge.texture.Texture(obj, mat_id)
    tex.source = bge.texture.ImageFFmpeg(IMAGE_PATH)
    
    cont.owner['tex'] = tex
    tex.refresh(True)
    
    #Do the first draw of the land all in one frame
    redraw_land(cont)
    recalc_physics(cont)
    #recalc_normals(cont)

    cont.script = __name__+'redraw_land'

def recalc_physics(cont):
    if RECALC_PHYSICS != False:
        cont.owner.reinstancePhysicsMesh(cont.owner, cont.owner.meshes[0])

# Face normal not working after post-render in most game engine -_-
#def recalc_normals(cont):
#    if RECALC_NORMALS != True:
#        return
#    mesh = cont.owner.meshes[0]
#    mat_id = bge.texture.materialID(cont.owner, "MA" + "LandNormal")
#    for poly_id in range(mesh.numPolygons):
#        poly = mesh.getPolygon(poly_id)
#        v1 = mesh.getVertex(mat_id, poly.v1)
#        v2 = mesh.getVertex(mat_id, poly.v2)
#        v3 = mesh.getVertex(mat_id, poly.v3)
#        if poly.v4 == 0:
#            normal = mathutils.geometry.normal(v1.getXYZ(), v2.getXYZ(), v3.getXYZ())
#            v1.setNormal(normal)
#            v2.setNormal(normal)
#            v3.setNormal(normal)
#        else:
#            v4 = mesh.getVertex(mat_id, poly.v4)
#            normal = mathutils.geometry.normal(v1.getXYZ(), v2.getXYZ(), v3.getXYZ(), v4.getXYZ())

def redraw_land(cont):
    obj = cont.owner
    tex = obj['tex']
    mat_id = bge.texture.materialID(obj, "MA" + "LandscapeMainMaterial Global")
    mesh = obj.meshes[0]
    obj_x, obj_y = obj.worldPosition.xy
    for vert_id in range(mesh.getVertexArrayLength(mat_id)):
        vert = mesh.getVertex(mat_id, vert_id)
        vert_x = (obj_x + vert.x)*SCALAR/2
        vert_y = (obj_y + vert.y)*SCALAR/2
        height = get_closest_pixel(cont, tex.source, vert_x, vert_y)[0]
        vert.z = height * VERT_SCALAR - VERT_MIDLEVEL
        
def get_closest_pixel(cont, img, x, y):
    '''x and y should be floats from 0 to 1'''
    
    if 'img_data' not in cont.owner:
        #It is time consuming to convert the image to a list, but we can store it
        cont.owner['img_data'] = img.image
    
    
    def get_id(x, y, img):
        size = img.size[0]
        return math.floor(x*size)*4 + math.floor(y*size)*4*size
        
    x = wrap(x, 0, 0.9999)
    y = wrap(y, 0, 0.9999)
    size = img.size[0]
    
    id_num = get_id(x,y,img)
        
    rgba = cont.owner['img_data'][id_num:id_num+4]
    if not isinstance(rgba, list):
        print("ERROR on loc:", x, y) 
        return[0,0,0,0]
    return rgba
    #print(vert_x, vert_y, rgba, id_num)
    #vert.z = rgba[0]*VERT_SCALAR
def wrap(num, mi=0, ma=1):
    while num > ma:
        num -= abs(ma - mi)
    while num < mi:
        num += abs(ma - mi)
    return num

Again, I added the division by 255.0, so you just input the strength from the displace modifier, and I also added a midlevel option.

I going to fix the “height value only” for now, on JPEG image format or perhaps re-create the new heightmap, then we can continued later.

The project now being halted, thanks for helping me out.