Voxelers / mcthings

A Python framework for creating 3D scenes in Minecraft and Minetest
Apache License 2.0
58 stars 11 forks source link

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

Closed acs closed 4 years ago

acs commented 4 years ago

https://github.com/Voxelers/mcthings/issues/104

https://thebitcave.gitbook.io/magicavoxel-resources/tutorials/writing-your-first-shader https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki

The doc about howto use shaders is at:

https://twitter.com/ephtracy/status/692986785689919491

Read carefully:

https://twitter.com/ephtracy/status/692986785689919491/photo/2 Screenshot from 2020-07-11 08-45-15

Right now the inputs are: i_args, i_volume_size, i_color_index ... but the old iArgs ... work.

acs commented 4 years ago

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 4 years ago

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

acs commented 4 years ago

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 4 years ago

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 4 years ago

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 4 years ago

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

acs commented 4 years ago

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:

Built in args:

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

Logic:

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 4 years ago

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

acs commented 4 years ago

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 4 years ago

Ok, let's try to implement with shaders:

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 4 years ago

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

acs commented 4 years ago

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 4 years ago

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 4 years ago

Online GLSL sandbox: https://github.com/jolivain/glslsandbox-player

acs commented 4 years ago

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:

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 4 years ago

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 4 years ago

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 4 years ago

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 4 years ago

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 4 years ago

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 4 years ago

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 4 years ago

All done!

acs commented 4 years ago

Pretty useful to learn GLSL: https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders--cms-23313