Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple textures for smooth terrain #93

Open
blockspacer opened this issue Jan 9, 2020 · 9 comments
Open

multiple textures for smooth terrain #93

blockspacer opened this issue Jan 9, 2020 · 9 comments

Comments

@blockspacer
Copy link

blockspacer commented Jan 9, 2020

I`m trying to add support for multiple textures based on texarray and COLOR attributes of vertices.

image

use a splat/texarray shader and then all you need is to populate the COLOR attributes of vertices with the material ID, which you use in shader #83 (comment)

Key idea is to texture differently shape created by do_point or do_sphere. For example, it may be useful in games where player can place voxel with chosen texture https://youtu.be/Xm1KVjMePBA?t=85

I managed to pass texture ID via _output_extra from voxel_mesher_transvoxel.cpp and adding CHANNEL_DATA2 to channels_mask like so unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_SDF) | (1 << VoxelBuffer::CHANNEL_DATA2);

But i don't know which element to choose from corner_positions to use in get_voxel (voxel_mesher_transvoxel.cpp)

				corner_positions[0] = Vector3i(pos.x, pos.y, pos.z);
				corner_positions[1] = Vector3i(pos.x + 1, pos.y, pos.z);
				corner_positions[2] = Vector3i(pos.x, pos.y + 1, pos.z);
				corner_positions[3] = Vector3i(pos.x + 1, pos.y + 1, pos.z);
				corner_positions[4] = Vector3i(pos.x, pos.y, pos.z + 1);
				corner_positions[5] = Vector3i(pos.x + 1, pos.y, pos.z + 1);
				corner_positions[6] = Vector3i(pos.x, pos.y + 1, pos.z + 1);
				corner_positions[7] = Vector3i(pos.x + 1, pos.y + 1, pos.z + 1);

Now i'm using corner_positions[0]

texture_idx = (float)voxels.get_voxel(corner_positions[0], VoxelBuffer::CHANNEL_DATA2);

But corner_positions[0] seems related not to voxel, but to voxel nearby to it. That's why when i call do_point texture applies not to created voxel (but to part of mesh created by do_point).

Thanks in advance for any help provided.

@Zylann
Copy link
Owner

Zylann commented Jan 10, 2020

You cannot use COLOR with the Transvoxel mesher at the moment, because it is used already to store transition mesh information.

Transvoxel polygonizes a block by taking corners of a cube (which is why it uses 8 voxels instead of one), which are interpolated to generate vertices. Then the process repeats for every voxel of the block ("marching" cubes).
You are right about using a new channel for material IDs, but doing that in Transvoxel you need to make them part of the interpolation I guess. Also, the paper has a section dedicated to this subject, you may want to take a look: https://transvoxel.org/Lengyel-VoxelTerrain.pdf

@blockspacer
Copy link
Author

@Zylann
Thanks for response.

I'll try to use UV2 and pack multiple texture ids into one float like so https://stackoverflow.com/questions/32915724/pack-two-floats-within-range-into-one-float.

About COLOR - are there any usage examples with shaders and Transvoxel? I changed triplanar shader to use get_transvoxel_position from #2 (comment) and nothing changed.

@Zylann
Copy link
Owner

Zylann commented Jan 10, 2020

This is the shader I use in my local demo:

shader_type spatial;

uniform sampler2D u_texture_top : hint_albedo;
uniform sampler2D u_texture_sides : hint_albedo;
uniform int u_transition_mask;

varying vec3 v_world_pos;
varying vec3 v_world_normal;

vec3 get_triplanar_blend(vec3 world_normal) {
	vec3 blending = abs(world_normal);
	blending = normalize(max(blending, vec3(0.00001))); // Force weights to sum to 1.0
	float b = blending.x + blending.y + blending.z;
	return blending / vec3(b, b, b);
}

vec4 texture_triplanar(sampler2D tex, vec3 world_pos, vec3 blend) {
	vec4 xaxis = texture(tex, world_pos.yz);
	vec4 yaxis = texture(tex, world_pos.xz);
	vec4 zaxis = texture(tex, world_pos.xy);
	// blend the results of the 3 planar projections.
	return xaxis * blend.x + yaxis * blend.y + zaxis * blend.z;
}

vec3 get_transvoxel_position(vec3 vertex_pos, vec4 vertex_col) {

	int border_mask = int(vertex_col.a);
	int cell_border_mask = border_mask & 63; // Which sides the cell is touching
	int vertex_border_mask = (border_mask >> 6) & 63; // Which sides the vertex is touching

	// If the vertex is near a side where there is a low-resolution neighbor,
	// move it to secondary position
	int m = u_transition_mask & (cell_border_mask & 63);
	float t = float(m != 0);

	// If the vertex lies on one or more sides, and at least one side has no low-resolution neighbor,
	// don't move the vertex.
	t *= float((vertex_border_mask & ~u_transition_mask) == 0);

	// Position to use when border mask matches
	vec3 secondary_position = vertex_col.rgb;
	return mix(vertex_pos, secondary_position, t);
}

void vertex() {
	VERTEX = get_transvoxel_position(VERTEX, COLOR);
	vec3 world_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0)).xyz;
	v_world_pos = world_pos;
	v_world_normal = NORMAL;
}

void fragment() {
	vec3 normal = v_world_normal;//normalize(v_world_normal);
	vec3 wpos = v_world_pos * 0.2;
	vec3 blending = get_triplanar_blend(normal);
	vec3 top_col = texture_triplanar(u_texture_top, wpos, blending).rgb;
	vec3 side_col = texture_triplanar(u_texture_sides, wpos, blending).rgb;
	float r = top_col.r;
	ALBEDO = mix(side_col, top_col, clamp(normal.y * 10.0 - 4.0 - 8.0*r, 0.0, 1.0));
}

@blockspacer
Copy link
Author

@Zylann

I'm not sure that understood doing that in Transvoxel you need to make them part of the interpolation I guess.

I hope that you said about VoxelMesherTransvoxel::build_internal cause i tried to use cell_positions like so https://github.com/blockspacer/godot_voxel/blob/indev/meshers/transvoxel/voxel_mesher_transvoxel.cpp#L873

			const int cornerMaterials[8] =
			{
				fvoxels.get_voxel(cell_positions[0], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[1], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[2], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[3], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[4], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[5], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[6], VoxelBuffer::CHANNEL_DATA2),
				fvoxels.get_voxel(cell_positions[7], VoxelBuffer::CHANNEL_DATA2),
			};
			dominant_texture_idx = (float)FindDominantMaterial(cornerMaterials);

I'm going to use function similar to FindDominantMaterial from https://github.com/nickgildea/leven/blob/118bf1cacb75fdb3b306ccf27477037720af86e8/leven/cl/octree.cl#L96

Note that noise function must mark air voxels with empty texture (ID == 0 or - in my code draft - if(texture_idx < 0.9), so i had to change voxel_stream_noise.cpp like so https://github.com/blockspacer/godot_voxel/blob/indev/streams/voxel_stream_noise.cpp#L87
cause i don't know how determine air voxels from float d = (n + 2.0 * t - 1.0) * iso_scale;

@blockspacer
Copy link
Author

BTW, is generation prioritized by distance to the camera?

@Zylann
Copy link
Owner

Zylann commented Jan 13, 2020

Note that noise function must mark air voxels with empty texture (ID == 0 or - in my code draft - if(texture_idx < 0.9), so i had to change voxel_stream_noise.cpp like so https://github.com/blockspacer/godot_voxel/blob/indev/streams/voxel_stream_noise.cpp#L87
cause i don't know how determine air voxels from float d = (n + 2.0 * t - 1.0) * iso_scale;

You can tell if a voxel is air if its SDF value is above zero (though with Transvoxel it would be the opposite). Then a cell is all air if the 8 corner voxels are air. I can't really tell what to do for materials, all I can say is that I would follow the Transvoxel paper.

BTW, is generation prioritized by distance to the camera?

The order of blocks scheduled for processing is sorted by distance to viewer. However scheduling is controlled by octrees and regions. See #87

@blockspacer
Copy link
Author

from https://transvoxel.org/Lengyel-VoxelTerrain.pdf If a vertex is reused from a preceding cell, and the preceding cell selects a different material, then the vertex must be duplicated so that the different texture map indexes can be assigned to it.

Any ideas how to do that will be useful

@Zylann
Copy link
Owner

Zylann commented May 25, 2021

FYI I've been working in a branch with texturing support for a little while, in smooth_texturing.

The way it works is as follows:

Two new channels were added, INDICES and WEIGHTS. They both default to 16-bit depth and should remain in that format in order to use the feature. Indices allow to tell which textures are present in a voxel, and weights tell how much of these textures there is. There can be just one (in which case only one weight will be maxed and others zero), or multiples.
Indices are encoded as four 4-bit values, and weights are also encoded as four 4-bit values. That means up to 16 different textures are supported, and weights can have up to 16 shades. This is very tight, but it should be enough for now.

The only mesher supporting this is VoxelMesherTransvoxel. It has an option to turn texturing on (it has a performance cost). During meshing, for every voxel crossing the surface, indices and weights are looked up, and 4 textures with the highest amount are selected for generated vertices. If the set of textures changes between two cells, a seam is created to fix interpolation issues that could arise from vertex sharing.

Finally, the shader used to render the surface can read indices and weights in the UV variable. It has nothing to do with uvs, it is only being repurposed. UV.x contains 4 bytes for the 4 indices, and same for weights in UV.y. After some decoding with floatBitsToUint, we can now perform simple 4-way blending using a texture array.

2021_04_25_1848

I then implemented ways to paint textures with VoxelTool. It's much more involved than sculpting, but it works too:
2021_04_25_1846_first_working_smooth_painting

Allowing to generate textures in VoxelGeneratorGraph is tricky as well, but I got something basic working. I had to implement new kinds of outputs (which is groundwork for custom outputs), so the density of each texture can be specified. However it involves math so that's another tricky part, as densities must add up to 1. Performance is not as good as I would like, but it works:

generator_textured

Now what's essentially left to do in the branch is to implement basic texture painting for remaining VoxelTools. Even though this took lots of work, it is still quite basic so it will probably be improved or changed over time.

@Zylann
Copy link
Owner

Zylann commented May 31, 2021

The feature is now in master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants