Create in MagicaVoxel a Terrain model based on shaders and import into MC
acs opened this issue · 23 comments
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
Right now the inputs are: i_args, i_volume_size, i_color_index ... but the old iArgs ... work.
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!
Some of our Things can be implemented as shaders in MV also. Let's see how far we can explore it.
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!
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.
With xs flood
you can fill the lakes: https://github.com/lachlanmcdonald/magicavoxel-shaders/wiki/flood
And we can map the water to be real water in Minecraft, so it can be improved!
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
- fract
- cos
- dot
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.
An online tool for enerating shaders: https://twitter.com/ephtracy/status/1258870089249624064
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).
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.
A marketplace for MagicaVoxel shaders: https://gumroad.com/mode_vis?sort=newest just 28 yet
Cool, VSCode has support for glsl: https://marketplace.visualstudio.com/items?itemName=slevesque.shader But the extension of the files must be "glsl". Fixed:
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.
Online GLSL sandbox: https://github.com/jolivain/glslsandbox-player
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).
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.
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/
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;
}
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;
}
And the result in Minecraft:
We need to add support for glass material in the Minecraft palette! #120
All done!
Pretty useful to learn GLSL: https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders--cms-23313