Zylann / godot_voxel

Voxel module for Godot Engine
MIT License
2.6k stars 245 forks source link

Generate biomes for smooth sdf VoxelLodTerrain #633

Closed Cyberphinx closed 4 months ago

Cyberphinx commented 4 months ago

My current setup:

What is the correct approach when it comes to generating different biomes with temperature, humidity, etc. with the VoxelLodTerrain using channel sdf? I believe it cannot be done in the same way as the blocky terrain, since it doesn't use channel type. Could you please provide a simple example/workflow description of how to do this with smooth terrain? Should it be done in the shader instead of the generator?

Zylann commented 4 months ago

You can't do all of this with a basic generator such as VoxelGeneratorNoise2D. Biomes require to compose multiple sources in arbitrarily complex ways that depend on your choices. You will have to either use VoxelGeneratorGraph, write your own with VoxelGeneratorScript, or mix both. This is also unrelated to channels. Biomes can be done in similar ways with blocky or SDF, the difference is what kind of data you are blending and outputting. Some of it can also be done in shader if it's purely visual and deterministic (meaning shaders wouldn't have to depend on voxel data thanks to random seed).

Cyberphinx commented 4 months ago

Thanks for the reply. I'm trying to test a simple VoxelGeneratorScript just to render a flat plane to make sure that the script is attached to the VoxelLodTerrain correctly, but nothing shows up: Screenshot from 2024-05-06 14-43-42

class_name TestGenerator
extends VoxelGeneratorScript

const channel : int = VoxelBuffer.CHANNEL_SDF

func _get_used_channels_mask() -> int:
    return 1 << channel

func _generate_block(out_buffer, origin_in_voxels, lod):
    print('Out buffer: ', out_buffer.get_size())
    for z in range(out_buffer.get_size().z):
        for y in range(out_buffer.get_size().y):
            for x in range(out_buffer.get_size().x):
                if y + origin_in_voxels.y == 0:
                    out_buffer.set_voxel(1, x, y, z, VoxelBuffer.CHANNEL_SDF)

and then in the parent node of the VoxelLodTerrain:

@onready var terrain : VoxelLodTerrain = $LandscapeLod
func _ready():
    var generator = preload("res://src/world_generator/simple_generator.gd").new()
    terrain.generator = generator

Try running this, the print function returns a log like these:

Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
Out buffer: (19, 19, 19)
...

Nothing is rendered on screen. Are there some settings that I'm missing? Is there a tutorial of how to use VoxelGeneratorScript with VoxelLodTerrain? Screenshot from 2024-05-06 14-42-40 Screenshot from 2024-05-06 14-42-33

Zylann commented 4 months ago

Thanks for the reply. I'm trying to test a simple VoxelGeneratorScript just to render a flat plane to make sure that the script is attached to the VoxelLodTerrain correctly, but nothing shows up:

There is another recent issue asking the same question, also doing the same mistakes (and that gets asked a lot because people don't read the docs / don't realize how Godot works) so to avoid repeating myself I'll link to it: https://github.com/Zylann/godot_voxel/issues/626#issuecomment-2059921896

Cyberphinx commented 4 months ago

Thank you for the quick reply and the links, they are really helpful! Sorry I'm quite new to godot.

For the biomes materials, it is correct to set buffer.set_voxel(material_index, x, y, z, VoxelBuffer.CHANNEL_INDICES) together with buffer.set_voxel_f(sdf, x, y, z, VoxelBuffer.CHANNEL_SDF) for the same buffer? And how to override the material for the buffer (eg. sand.tres, grass.tres, etc.), as there seem to be only one material slot in the editor of the VoxelLodTerrain?

Zylann commented 4 months ago

For the biomes materials, it is correct to set buffer.set_voxel(material_index, x, y, z, VoxelBuffer.CHANNEL_INDICES) together with buffer.set_voxel_f(sdf, x, y, z, VoxelBuffer.CHANNEL_SDF) for the same buffer?

Yes

And how to override the material for the buffer (eg. sand.tres, grass.tres, etc.), as there seem to be only one material slot in the editor of the VoxelLodTerrain?

Like for many terrains, using standalone materials is not possible because blending between different textures wouldn't work (and Godot doesn't come with something to handle it). So typically you have to do this in a single shader material, mixing procedural approaches (such as using normals to decide which texture to blend) and voxel data (for cases where the actual voxels tell what texture to use). This comes with a whole bunch of caveats and compromises, compared to heightmaps or regular models. See https://voxel-tools.readthedocs.io/en/latest/smooth_terrain/#texturing

Cyberphinx commented 4 months ago

I have another question regarding LOD, do you know the causes for the distant meshes to appear floating / inverted until the player navigates to it/ becoming LOD0? (It is generated with a simple VoxelGeneratorScript with a noise)

Screenshot from 2024-05-07 00-35-27

Zylann commented 4 months ago

My suspicion is that your generator is not outputting LODs correctly. Note that with LOD1, world positions you sample on noise must be spaced twice as much, and so on by powers of two for each higher LOD index.

Cyberphinx commented 4 months ago

I was experimenting with the shader code in the docs with the custom VoxelGeneratorScript with 4 simple textures:

func _ready():
        var img_red = Image.create(16, 16, true, Image.FORMAT_RGBA8)
    img_red.fill(Color.RED)
    var img_green = Image.create(16, 16, true, Image.FORMAT_RGBA8)
    img_green.fill(Color.GREEN)
    var img_blue = Image.create(16, 16, true, Image.FORMAT_RGBA8)
    img_blue.fill(Color.BLUE)
    var img_yellow = Image.create(16, 16, true, Image.FORMAT_RGBA8)
    img_yellow.fill(Color.YELLOW)

    var texture_2d_array = Texture2DArray.new()
    texture_2d_array.create_from_images([img_red, img_green, img_blue, img_yellow])

    var material = terrain.material as ShaderMaterial
    material.set_shader_parameter("u_texture_array", texture_2d_array)

However, it generated some black areas. Do you know what could be the problem area that caused the black zone to appear and how to fix it?

(p.s. The hills are generated with a cellular noise in the VoxelGeneratorScript.)

image

image

Zylann commented 4 months ago

I can't tell, sorry. The problem is somewhere else that you havent posted. Probably your generator

Cyberphinx commented 4 months ago

In the example shader code, does the v_indices and v_weights correspond directly to the voxel buffer's CHANNEL_INDICES and CHANNEL_WEIGHTS values?

void vertex() {
    // Indices are integer values so we can decode them as-is
    v_indices = decode_8bit_vec4(CUSTOM1.x);

    // Weights must be in [0..1] so we divide them
    v_weights = decode_8bit_vec4(CUSTOM1.y) / 255.0;

In my generator, it is set using buffer.set_voxel(material_index, x, y, z, VoxelBuffer.CHANNEL_INDICES) with the material_index being, 1, 2, 3, 4, depending on biome_value of the buffer.

biome values are generated using a noise noise_biome.set_noise_type(FastNoiseLite.NoiseType.TYPE_CELLULAR) and mapped to each buffer var biome_value = noise_biome.get_noise_2d(pos_world.x, pos_world.z)

I've tried to change the values of the heights and the material indices in relation to the biome noise values, but some voxels still appear black.

Zylann commented 4 months ago

In the example shader code, does the v_indices and v_weights correspond directly to the voxel buffer's CHANNEL_INDICES and CHANNEL_WEIGHTS values?

Yes, though with a slightly different encoding.

In my generator, it is set using buffer.set_voxel(material_index, x, y, z, VoxelBuffer.CHANNEL_INDICES) with the material_index being, 1, 2, 3, 4, depending on biome_value of the buffer.

That's wrong. You are setting only one index per voxel, but the engine expects 4 at once. Values you set in here must be encoded as shown here: https://voxel-tools.readthedocs.io/en/latest/smooth_terrain/#voxel-data Things are similar for weights.

Check these functions for encoding:

Cyberphinx commented 4 months ago

It works now. Thank you for your support.