Voxelers/mcthings

Create in MagicaVoxel a Terrain model based on shaders and import into MC

acs opened this issue · 23 comments

acs commented

It is pretty easy to do it. Let's start with the ones at:

https://github.com/CodingEric/Erics-MagicaVoxel-Shaders

It is a pretty polite guy in then bugs, and the focus is on terrain generation. So let's try!

acs commented

Some of our Things can be implemented as shaders in MV also. Let's see how far we can explore it.

acs commented

The languange for the shaders is GLSL: «Supports GLSL based shader script to generate your custom volume.» Samples shaders to learn from:

https://ephtracy.github.io/index.html?page=mv_resource

which points to:

https://github.com/lachlanmcdonald/magicavoxel-shaders
https://github.com/kchapelier/cellular-automata-voxel-shader

and not sure why this one is not in the resources from MV:

https://github.com/CodingEric/Erics-MagicaVoxel-Shaders

MV also includes some shaders:

 [shader]$ ls -1
gear.txt
mandelbulb.txt
poly.txt
round.txt
sphere.txt
torus.txt

and the first one developed as an exercise:

[shader]$ cat color-replacement.txt 
float map(vec3 v) {
    float index = voxel(v);
    float newIndex = iArgs[0];
    
    if(index == iColorIndex) {
        index = newIndex;
    }
    return index;
}

So the goal is to generate terrain with shaders, so let's fine shaders which implemen it:

https://github.com/CodingEric/Erics-MagicaVoxel-Shaders#tergen
https://github.com/CodingEric/Erics-MagicaVoxel-Shaders#tergen2

https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki
https://github.com/lachlanmcdonald/magicavoxel-shaders/blob/master/shader/sand.txt
https://github.com/lachlanmcdonald/magicavoxel-shaders/blob/master/shader/sand2.txt
https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki/Soil-&-Cover

Let's go!

acs commented

There are some videos showing howto use the shaders.This one:

https://www.youtube.com/watch?v=690U9MLtqtY

is for https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki

so let's try to reproduce it.

xs -n 2 soil

With this the soil shader is executed: https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki/soil-&-cover

is used to create the initial base layer. In it, circles are created that will be the futures lakes.

Probably not, the base layer is just created filling the base of the cube with one color, then adding a new layer with other color, and the removing from this last layer using circles the region that will be the lakes,

Then

'xs -n 40 sand' is executed to created the hills. And it works like a charm once you select the color of the upper layer.

Screenshot from 2020-07-11 01-22-13

With xs flood you can fill the lakes: https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki/flood

acs commented

The results are wonderful:

Screenshot from 2020-07-11 02-07-58

Screenshot from 2020-07-11 02-07-35

Screenshot from 2020-07-11 02-01-10

acs commented

And we can map the water to be real water in Minecraft, so it can be improved!

acs commented

Let's take a quick look to the source code of the sand shader:

// MIT License (MIT)
// https://github.com/lachlanmcdonald/magicavoxel-shaders
// Copyright (c) 2020 Lachlan McDonald
//
// xs sand [Color]
//
// xs_begin
// author : '@lachlanmcdonald'
// arg : { id = '0'  name = 'Color'  value = '0'  range = '0 255'  step = '1'  decimal = '0' }
// xs_end

float random(vec2 co) {
    return fract(cos(dot(co.xy, vec2(23.14069263277926, pow(sqrt(2.0), 2.0)))) * 43758.5453);
}

float random(vec3 co) {
        return random(vec2(random(co.xy), co.z));
}

float map(vec3 v) {
        float x = voxel(v);

        if (x == 0.0) {
                if (voxel(vec3(v.x, v.y, v.z - 1.0)) == i_color_index) {
                        bool a = (v.x == 0.0);
                        bool b = (v.x == i_volume_size.x - 1.0);
                        bool c = (v.y == 0.0);
                        bool d = (v.y == i_volume_size.y - 1.0);

                        float z = float((a ? 0.0 : voxel(vec3(v.x - 1.0, v.y, v.z - 1.0))) > 0.0) +
                                          float((b ? 0.0 : voxel(vec3(v.x + 1.0, v.y, v.z - 1.0))) > 0.0) +
                                          float((c ? 0.0 : voxel(vec3(v.x, v.y - 1.0, v.z - 1.0))) > 0.0) +
                                          float((d ? 0.0 : voxel(vec3(v.x, v.y + 1.0, v.z - 1.0))) > 0.0) +
                                          float((a && c ? 0.0 : voxel(vec3(v.x - 1.0, v.y - 1.0, v.z - 1.0))) > 0.0) +
                                          float((b && c ? 0.0 : voxel(vec3(v.x + 1.0, v.y - 1.0, v.z - 1.0))) > 0.0) +
                                          float((a && d ? 0.0 : voxel(vec3(v.x - 1.0, v.y + 1.0, v.z - 1.0))) > 0.0) +
                                          float((b && d ? 0.0 : voxel(vec3(v.x + 1.0, v.y + 1.0, v.z - 1.0))) > 0.0);

                        if (random(v) <= (0.125 * z)) {
                                if (i_args[0] == 0.0) {
                                        return i_color_index;
                                } else {
                                        return i_args[0];
                                }
                        }
                }
        }

        return x;
}

The main method is float map(vec3 v): for all the selected voxels, apply this function to get the new color for a voxel.

Types used:

  • vec2
  • vec3
  • voxel: color for a vec3 coordinates? but it is a float

Built in args:

  • i_color_index
  • i_volume_size
  • i_args: color of the sand (use i_color_index if not provided)

Built in methods: https://www.khronos.org/registry/OpenGL-Refpages/gl4/index.php

Logic:

  • the 4 bools is to detect that a vec3 is in a border of the volume to work with (base or top)
  • the color for a voxel is changed if if (random(v) <= (0.125 * z))

Invocation

The shader is called 40 times, so the max height is 40. In each iteration the decision is to add a new voxel or not.

acs commented

An online tool for enerating shaders: https://twitter.com/ephtracy/status/1258870089249624064

acs commented

Recommendations on howto debug GLSL code: https://stackoverflow.com/questions/2508818/how-to-debug-a-glsl-shader
Tools: https://www.khronos.org/opengl/wiki/Debugging_Tools

But let's try to have the code as simple as possible because it is not easy to debug shaders (executed per each voxel in MagicaVoxel).

acs commented

Ok, let's try to implement with shaders:

  • Adding one voxel (setBlock method)
  • Adding a group of voxels (setBlocks method)

Those are the basic methods to render things in Minecraft. With those implemented, all Things in McThings could be easily converted to shaders. A nice exercise.

acs commented

A marketplace for MagicaVoxel shaders: https://gumroad.com/mode_vis?sort=newest just 28 yet

acs commented

Cool, VSCode has support for glsl: https://marketplace.visualstudio.com/items?itemName=slevesque.shader But the extension of the files must be "glsl". Fixed:

Screenshot from 2020-07-12 08-04-48

acs commented

The sphere implementation is pretty easy to read. We need to understand them in deep:

float map( vec3 v ) {
	v = ( v - i_volume_size * 0.5 ) / ( i_volume_size * 0.5 );
	return i_color_index * step( length( v ), 1.0 );
}

the method map is applied to all the voxels inside the model (volume). The size of the model is i_volume_size,

The method map receives the position of the voxel to work with. It is a vec3 (https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Vectors) which is a vector of three float values.

The operation "v - i_volume_size * 0.5" is correct because both o them are floats. And the same for division in "/ ( i_volume_size * 0.5 )".

With the operation

v = ( v - i_volume_size * 0.5 ) / ( i_volume_size * 0.5 );

we are creating a new vec3. With this value "v" we can decide in the next line if color the voxel or leave it blank. And this logic is what creates the sphere.

The step method (http://learnwebgl.brown37.net/12_shader_language/documents/webgl-reference-card-1_0.pdf): «0.0 if x < edge, else 1.0»

There are a lot of places documenting GLSL: https://www.shaderific.com/glsl-functions

Step

float step(float edge, float x)  
....

The step function returns 0.0 if x is smaller than edge and otherwise 1.0. The input parameters can be floating scalars or float vectors. In case of float vectors the operation is done component-wise.

Length

float length(float x)  
float length(vec2 x)  
float length(vec3 x)  
float length(vec4 x)

The length function returns the length of a vector defined by the Euclidean norm, i.e. the square root of the sum of the squared components.
acs commented

After thinking about it the implementation is easy to understand:

float map( vec3 v ) {
	v = ( v - i_volume_size * 0.5 ) / ( i_volume_size * 0.5 );
	return i_color_index * step( length( v ), 1.0 );
}

For all the voxels in the volume (the full model in MV) a voxel is colored or not depending if it is closer than then radius to the center of the sphere. In this case:

  • the center of the sphere is the center of the volume: i_volume_size * 0.5
  • the radius of the sphere is also i_volume_size * 0.5.

So with ( v - i_volume_size * 0.5 ) we check then voxel distance to the center. We use divide it with the radius so the length is between 0 and 1 (normalize it).

With length( v ) we measure the distance and with step we get 0 or 1 depending if then voxel is inside the sphere or outside it (less than or greater than 1.0, the normalized distance). If it is 0, we get the blank color. And if it is 1, we get the selected color for the sphere.

And easier to read code:

float map(vec3 v) {
	// generate a new voxel color index at position v ( v is at the center of voxel, such as vec3( 1.5, 2.5, 4.5 ) )
	float color = 0.0;
	vec3 radius = i_volume_size * 0.5;
	vec3 distance_to_center_normalized = (v - radius) / radius;
	if (length(distance_to_center_normalized) < 1.0) { 
		color = i_color_index;
	}
	return color;
}

From a performace point of view, it is better the original implementation to avoid declaring variables. Take in mind that this method is calle for each voxel in the model. For a 256x256x256 = 16777216 (17 millions time aprox).

acs commented

So it is interesting for example to create a hollow sphere (the distance mustbe exactly the radius, not less tha the radiius). With it, we can for example, create a hollow sphere of glass and put something inside it.

acs commented

Using GLSL has approached us to WebGL, which uses the same language for shaders in its renderer: https://threejs.org/docs/#api/en/renderers/WebGLRenderer. Also Minecraft? Yes: http://shadersmods.com/shaders-mod/

acs commented

With a small change we can make our sphere hollow:

float map(vec3 v) {
	// generate a new voxel color index at position v ( v is at the center of voxel, such as vec3( 1.5, 2.5, 4.5 ) )
	float color = 0.0;
	vec3 radius = i_volume_size * 0.5;
	vec3 distance_to_center_normalized = (v - radius) / radius;
	if (0.9 < length(distance_to_center_normalized) && 
		1.0 > length(distance_to_center_normalized)) { 
		color = i_color_index;
	}
	return color;
}

Screenshot from 2020-07-12 19-33-41

Screenshot from 2020-07-12 19-34-53

Screenshot from 2020-07-12 19-42-01

acs commented

The hollow sphere with the knight inside it!

Screenshot from 2020-07-12 19-46-41
Screenshot from 2020-07-12 19-46-22

acs commented

To not remove the already contents in the mode, just preserve the voxel color:

float map(vec3 v) {
	float color = voxel(v);
	vec3 radius = i_volume_size * 0.5;
	vec3 distance_to_center_normalized = (v - radius) / radius;
	if (0.9 < length(distance_to_center_normalized) && 
		1.0 > length(distance_to_center_normalized)) { 
		color = i_color_index;
	}
	return color;
}
acs commented

And the result in Minecraft:

Screenshot from 2020-07-13 06-15-54

Screenshot from 2020-07-13 06-15-43

We need to add support for glass material in the Minecraft palette! #120

acs commented

All done!