TokisanGames / Terrain3D

A high performance, editable terrain system for Godot 4.
MIT License
2.02k stars 115 forks source link

Option to use noise in a height blending algorithm instead of a texture's heightmap #467

Open NectoT opened 2 weeks ago

NectoT commented 2 weeks ago

Description

The height blending algorithm make overlay textures blend in much more naturally, but they rely on the texture's heightmap to support this behaviour.

heightmap_no_height_blending A patch of grass without height blending

heightmap_height_blending Same patch of grass with height blending

If there are no packed heightmaps in textures, this feature does practically nothing, aside from lowering or raising the opacity of the overlay:

no_heightmap_no_height_blending A patch of grass without heightmaps, no height blending

no_heightmap_height_blending Same patch of grass, height blending

Here is the code responsible for the feature (if I understand it correctly):

function

vec4 height_blend(vec4 a_value, float a_height, vec4 b_value, float b_height, float blend) {
    if(height_blending) {
        float ma = max(a_height + (1.0 - blend), b_height + blend) - (1.001 - blend_sharpness);
        float b1 = max(a_height + (1.0 - blend) - ma, 0.0);
        float b2 = max(b_height + blend - ma, 0.0);
        return (a_value * b1 + b_value * b2) / (b1 + b2);
    } else {
        float contrast = 1.0 - blend_sharpness;
        float factor = (blend - contrast) / contrast;
        return mix(a_value, b_value, clamp(factor, 0.0, 1.0));
    }
}

calls to the function

void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out Material out_mat) {
    // other stuff...

    // Blend overlay and base
    albedo_ht = height_blend(albedo_ht, albedo_ht.a, albedo_ht2, albedo_ht2.a, out_mat.blend);
    normal_rg = height_blend(normal_rg, albedo_ht.a, normal_rg2, albedo_ht2.a, out_mat.blend);

    // other stuff...
}

The proposal

Instead of just adding an additional modifier to overlay's blend vlaue when height_blending is checked off, we could instead use a noise-based blending:

function

vec4 height_blend(vec4 a_value, float a_height, vec4 b_value, float b_height, float blend) {
    float ma = max(a_height + (1.0 - blend), b_height + blend) - (1.001 - blend_sharpness);
    float b1 = max(a_height + (1.0 - blend) - ma, 0.0);
    float b2 = max(b_height + blend - ma, 0.0);
    return (a_value * b1 + b_value * b2) / (b1 + b2);
}

calls to the function

void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out Material out_mat) {
    // other stuff...

    float a_height;
    // Blend overlay and base
    if (height_blending) {
    a_height = albedo_ht.a;
    } else {
    a_height = texture(noise_texture, base_uv*noise3_scale).r;
    }
    albedo_ht = height_blend(albedo_ht, a_height, albedo_ht2, albedo_ht2.a, out_mat.blend);
    normal_rg = height_blend(normal_rg, a_height, normal_rg2, albedo_ht2.a, out_mat.blend);

    // other stuff...
}

heightmap_noise_blending A patch of grass with heightmaps, noise-based blending

no_heightmap_noise_blending A patch of grass without heightmaps, noise-based blending

This way there would be two approaches to choose from, and a better-functioning feature when not using textures packed with heightmaps. If the previous functionality is important, it can still be partially achieved with using textures without heighmaps and enabling height_blending (since the height will be the same at every point in the texel)

Xtarsia commented 2 weeks ago

you could use luminance in lieu of height data, where you can take the RGB values, and apply a standard luminance calculation:

(0.299*albedo_ht.r + 0.587*albedo_ht.g + 0.114*albedo_ht.b)

passing this, instead of albedo_ht.a would achive much better reletive results in general than noise.

However, this is useless additional computation in comparison to baking luminance into the alpha channel if no height data is readily available. (free tools already exist to create heightmaps from albedo + normals, and are listed in the docs.)

A button to bake luminance as height in the channel packer could be an option however.

NectoT commented 2 weeks ago

Using luminance does sound like a lighter choice then a noise texture.

Will the real-time calculation of this formula noticeably affect shader's performance? I haven't noticed any changes in GPU frame time when alternating between the current height blending and the luminance formula (although my method of checking was simply looking at the Frame Time display in the editor)

TokisanGames commented 2 weeks ago

Thanks for the ideas.

If a game dev wants a realistic terrain, the expectation that they'll use full PBR textures is reasonable. I think people who use textures without heightmaps will be using hand painted textures for a more stylized terrain. So you should test these ideas with those. Eg https://opengameart.org/content/4-hand-painted-ground-textures

Or it's used by newbie gamedevs who "just want it to work" before they learn about intricate details like PBR, vram or getting things to look good.

Will the real-time calculation of this formula noticeably affect shader's performance?

4 additional lookups of the texture per pixel is expensive. The math Xtarsia posted is cheaper, but like he said, better to bake it in. You're already consuming 32-bits of vram per texture pixel even without an alpha channel. So there's no reason to not have or use a height texture if you want a realistic style. Adding the luminance option to the texture packer is a good idea.

TokisanGames commented 1 week ago

Baking luminance as Height is in #471, which will close this issue.