2
\$\begingroup\$

I have a cubic world (like Minecraft) where I'm generating chunks. In those chunks, only visible vertices and faces are generated.

Currently, it means that if I have a 2x2x1 chunk, it will generate 8 triangles for the top part (2 for each block). I'm trying to generate only two following this greedy meshing method:

enter image description here

If I combine adjacent faces together, I'd still like to be able to draw transitions in materials between adjacent blocks, something like this:

2x2x2 block of voxels, with grass on the top surface and dirt below

As you can see it has 3 different tiles: grass, dirt-grass transition, and dirt.

Currently, my shader looks like this:

Shader "MultiTexture_Diffuse_Worldspace" 
{

    Properties 
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _TexAbove ("Above texture", 2D) = "surface" {}
        _TexSide ("Side texture", 2D) = "surface" {}
        _TexBelow ("Below texture", 2D) = "surface" {}
        _TexTopSide ("Top side texture", 2D) = "surface" {}
        _Scale ("Texture Scale", Float) = 1.0
    }

    SubShader 
    {

        Tags 
        { 
            "RenderType"="Opaque" 
        }

        CGPROGRAM
        #pragma surface surf Lambert

        struct Input 
        {
            float3 worldNormal;
            float3 worldPos;
        };

        sampler2D _TexAbove;
        sampler2D _TexSide;
        sampler2D _TexBelow;
        sampler2D _TexTopSide;
        float4 _Color;
        float _Scale;

        void surf (Input IN, inout SurfaceOutput o) 
        {
            float2 UV;
            fixed4 c;

            if(abs(IN.worldNormal.y > 0.5))
            {
                UV = IN.worldPos.xz;
                c = tex2D(_TexAbove, UV* _Scale);
            }
            else if(abs(IN.worldNormal.x) > 0.5)
            {
                UV = IN.worldPos.yz;
                c = tex2D(_TexSide, UV* _Scale);
            }
            else if(abs(IN.worldNormal.z) > 0.5)
            {
                UV = IN.worldPos.xy;
                c = tex2D(_TexSide, UV* _Scale);
            }
            else
            {
                UV = IN.worldPos.xz;
                c = tex2D(_TexBelow, UV* _Scale);
            }

            o.Albedo = c.rgb * _Color;
        }
        ENDCG
    }
}

How would I modify this shader to work with faces that have been merged together? Or would I have to keep separate faces per block and handle them individually?

\$\endgroup\$
0

2 Answers 2

6
\$\begingroup\$

Quoting the meshing page you link:

It isn’t too difficult to modify the code deal with either multiple block types or different normal directions. What you would do is modify the array called “mask” in the code to store an integer value which encodes the type of each block. You’d need at least 1 bit for orientation, and then you could use the rest to store block color or whatever. Then when you build the greedy quad, you store the type of the block you are scanning over and only group blocks which are the same type together.

The author elaborates in part 2:

The general idea is pretty simple. What you do is group blocks together according to their type, and then do the meshing on each part separately:

Multi-type mesh cut into several pieces

That’s it! This same idea works for normals too. All you need to do is add an extra bit to keep track of the orientation. To show that this isn’t so difficult, I made an updated demo that shows how some of these methods work for different voxel types.

That would give you something like this:

2x2 grass-dirt stack

Here we've combined...

  • the 4 grass faces on the top into a single combined grass face,

  • the 8 grass-dirt faces into four (one on each side of the chunk),

  • and the 12 all-dirt faces into 5 (one on the bottom and one on each side).

You could make each of these into a sub-mesh with its own material to be drawn in its own draw call with your current shader.

Or, you could combine all your block textures into an atlas texture to draw the whole multi-block-type chunk in a single draw call. You'd split the vertices shared by multiple different block types, and give each copy a distinct texture coordinate - say, the top-left corner of the texture to use in your atlas, or its index in the stack if using an array texture. If you're using a flat 2D atlas, you'd need to modify your shader to do the texture wrapping in the shader math, since the sampling hardware won't do it for you.

\$\endgroup\$
1
  • \$\begingroup\$ Indeed, i didn't saw that there was a part two for the blog post. Thank you, i will try to do that. \$\endgroup\$
    – Kamigaku
    Aug 11, 2020 at 15:35
7
\$\begingroup\$

Nice idea by the author.

From experience... High vertex counts aren't that much of a problem (100Ks, millions even). Dealing with complex UV mapping is far more so. Sometimes it is worth staying closer to the actual description of the surface (i.e. the backing 3D array), than optimising yourself into a place where it's not as easy to change the mesh when you need to do so (assuming you don't just rebuild the entire chunk each time a voxel changes). That is, it can help to have the ability to easily reference between mesh locations, and the data that formed it.

Assuming you intend to use textures: Remember that in any space where you have non-homogenous voxels but a single rectangular space, you will need to create a new texture specifically to map onto that plane... and every other heterogenous plane. Texture write / reads are not the fastest thing in the GPU pipeline. Worth it? Only you will know. For frequently-changed terrain, I'd avoid this.

(If using textures) I would stick with a simpler approach each cube face is its own set of non-shared vertices, with the ability to UV map a single cube-face texture at a time. If using vertex colours? - the world is your oyster.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Adding to this, if OP uses instancing instead of regular object rendering, the performance will increase dramatically. Block textures can then be stored in a 2DSamplerArray (or whatever it is in Unity) and the block type can correspond to an index into that array so that all blocks in view can be rendered in a single pass and you don't have to batch based on block type. \$\endgroup\$
    – Beefster
    Aug 11, 2020 at 19:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .