Search Unity

Voxel Texture array with shadows

Discussion in 'World Building' started by shatteredeyeinc, Jan 10, 2019.

  1. shatteredeyeinc

    shatteredeyeinc

    Joined:
    Jan 10, 2019
    Posts:
    5
    I spend way to long trying to figure this out below is how i got it to work

    when creating your mesh use
    Code (CSharp):
    1.  
    2.         mesh.SetUVs(0, Uvs );
    uvs are a vector 3 with the z axis set to the image index
    Code (CSharp):
    1.   this.GetComponent<MeshRenderer>().sharedMaterial.SetTexture("_Tex2DArray", texture);
    use this to set the texture array in the shader


    creating the texture map
    Code (CSharp):
    1.   var textureMap = new Texture2DArray(200, 200, textureMaps.Count, TextureFormat.RGB24, false, false);
    2.             textureMap.filterMode = FilterMode.Point;
    3.             textureMap.wrapMode = TextureWrapMode.Repeat;
    4.             for (int i = 0; i < textureMaps.Count; i++)
    5.             {
    6.                 textureMaps.ElementAt(i).Value.Texture2DLocation = i;
    7.  
    8.                 textureMap.SetPixels(textureMaps.ElementAt(i).Value.Texture2D.GetPixels(), i);
    9.  
    10.             }
    11.  
    12.             textureMap.Apply();
    13.             this.chunkTexure = textureMap;

    and finally the shader




    Code (CSharp):
    1. Shader "Lit/Diffuse With Shadows"
    2. {
    3.     Properties
    4.     {
    5.         _Tex2DArray("Tex2DArray (RGB)", 2DArray) = "white" {}
    6.     }
    7.         SubShader
    8.     {
    9.         Pass
    10.         {
    11.             Tags {"LightMode" = "ForwardBase"}
    12.             CGPROGRAM
    13.             #pragma vertex vert
    14.             #pragma fragment frag
    15.             #include "UnityCG.cginc"
    16.             #include "Lighting.cginc"
    17.  
    18.         // compile shader into multiple variants, with and without shadows
    19.         // (we don't care about any lightmaps yet, so skip these variants)
    20.         #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
    21.         // shadow helper functions and macros
    22.         #include "AutoLight.cginc"
    23.  
    24.         struct v2f
    25.         {
    26.             float3 uv : TEXCOORD0;
    27.             SHADOW_COORDS(1) // put shadows data into TEXCOORD1
    28.             fixed3 diff : COLOR0;
    29.             fixed3 ambient : COLOR1;
    30.             float4 pos : SV_POSITION;
    31.         };
    32.         v2f vert(appdata_base v)
    33.         {
    34.             v2f o;
    35.             o.pos = UnityObjectToClipPos(v.vertex);
    36.             o.uv = v.texcoord;
    37.             half3 worldNormal = UnityObjectToWorldNormal(v.normal);
    38.             half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
    39.             o.diff = nl * _LightColor0.rgb;
    40.             o.ambient = ShadeSH9(half4(worldNormal,1));
    41.             // compute shadows data
    42.             TRANSFER_SHADOW(o)
    43.             return o;
    44.         }
    45.  
    46.         sampler2D _MainTex;
    47.         UNITY_DECLARE_TEX2DARRAY(_Tex2DArray);
    48.         fixed4 frag(v2f i) : SV_Target
    49.         {
    50.             fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_Tex2DArray, i.uv);
    51.         // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
    52.         fixed shadow = SHADOW_ATTENUATION(i);
    53.         // darken light's illumination with shadow, keep ambient intact
    54.         fixed3 lighting = i.diff * shadow + i.ambient;
    55.         col.rgb *= lighting;
    56.         return col;
    57.     }
    58.     ENDCG
    59. }
    60.  
    61. // shadow casting support
    62. UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    63.     }
    64. }



     
    PROE_ likes this.
  2. shatteredeyeinc

    shatteredeyeinc

    Joined:
    Jan 10, 2019
    Posts:
    5
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    You've done some really interesting things there! The shadows are one thing — and they do look nice — but I'm more intrigued by what you've done with the 3D UV's. I've made a voxel engine in Unity before, but I put all the voxel textures into one big texture map, and then used traditional 2D UVs to select the tiny part of that map for each square.

    Do I understand correctly that you're instead using a texture array, with a different element for each square texture? And you select these with the Z value of your 3D UV coordinates, so I guess the X and Y values are always in the range 0-1?
     
  4. shatteredeyeinc

    shatteredeyeinc

    Joined:
    Jan 10, 2019
    Posts:
    5
    yes exactly 00 01 10 11 for the uvs texture atlas has some drawbacks on the size of the atlas and the filtering mode with this even a small texture of 30px by 30px can have better filtering
     
  5. shatteredeyeinc

    shatteredeyeinc

    Joined:
    Jan 10, 2019
    Posts:
    5
    yes this is the code for the texture loader
    Code (CSharp):
    1.   public void GenerateChunkTextureMap()
    2.         {
    3.             List<TextureMap> maps = new List<TextureMap>();
    4.            foreach(var item in _directories)
    5.             {
    6.                 foreach (var image in System.IO.Directory.GetFiles(item.DirectoryName).Where(o => o.EndsWith(".png")))
    7.                 {
    8.                     var data = System.IO.File.ReadAllBytes(image);
    9.                     var texture = new Texture2D(0, 0);
    10.                     texture.LoadImage(data);
    11.                     if (texture.width != 32 || texture.height != 32)
    12.                         continue;
    13.  
    14.                     var map = new TextureMap();
    15.                     map.Prefix = item.Prefix;
    16.                     map.Texture = texture;
    17.                     map.FileLocation = image;
    18.                     map.Name = Regex.Match(image, @"(?<=\\).+(?=(\.png))").Value;
    19.                     maps.Add(map);
    20.  
    21.                 }
    22.             }
    23.        
    24.             var sq = (int) Math.Ceiling(Math.Sqrt(maps.Count));
    25.        
    26.             var textureMap = new Texture2D(sq*32,sq*32);
    27.             // System.IO.File.WriteAllBytes("test.png", textureMap.EncodeToPNG());
    28.             var index = 0;
    29.  
    30.             for (int x= 0;x<sq;x++)
    31.                 for(int y= 0; y < sq; y++)
    32.                 {if (index>maps.Count-1)
    33.                         continue;
    34.                     var image = maps[index];
    35.                     image.Xpos = x;
    36.                     image.Ypos = y;
    37.  
    38.                  
    39.                     textureMap.SetPixels(x  *32, y  * 32, 32, 32, image.Texture.GetPixels());
    40.  
    41.                  
    42.                     index++;
    43.  
    44.                 }
    45.             this._textureMaps = new Dictionary<string, TextureMap>();
    46.             foreach(var item in maps)
    47.             {
    48.                 this._textureMaps.Add($"{item.Prefix}_{item.Name}", item);
    49.             }
    50.             this.chunkTexture = textureMap;
    51.         }
    52.  
    53.     }

    and the code for the entire chunk class

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using V3 = UnityEngine.Vector3;
    5. using V2 = UnityEngine.Vector2;
    6. using UnityEngine;
    7.  
    8. public class Chunk
    9. {
    10.     private static readonly int chunkWidth = 12;
    11.     private static readonly int chunkHeight = 12;
    12.     private Mesh mesh { get; set; }
    13.     public byte[,,] voxels = new byte[chunkWidth, chunkHeight, chunkWidth];
    14.     public Mesh GetMesh() => mesh;
    15.     private static System.Random random = new System.Random();
    16.     public void GenerateChunk()
    17.     {
    18.         for(int x=0;x<chunkWidth;x++)
    19.             for (int y = 0; y < chunkHeight; y++)
    20.                 for (int z = 0; z < chunkWidth; z++)
    21.                 {
    22.                    
    23.                     voxels[x, y, z] = random.Next(2)==1? (byte)1 : (byte)0;
    24.                 }
    25.     }
    26.     public void RedrawChunk()
    27.     {
    28.         var mesh = new MultiVoxMesh();
    29.  
    30.         for (int x = 0; x < chunkWidth; x++)
    31.             for (int y = 0; y < chunkHeight; y++)
    32.                 for (int z = 0; z < chunkWidth; z++)
    33.                 {
    34.                     if (voxels[x, y, z] == 0)
    35.                         continue;
    36.                  
    37.  
    38.                     if (x == 0 || voxels[x - 1, y, z] == 0)
    39.                         mesh.Inject(FaceDefinitions.LeftFace, x, y, z);
    40.  
    41.                     if (y == 0 || voxels[x, y - 1, z] == 0)
    42.                         mesh.Inject(FaceDefinitions.BottomFace, x, y, z);
    43.  
    44.                     if (z == 0 || voxels[x, y, z - 1] == 0)
    45.                         mesh.Inject(FaceDefinitions.FrontFace, x, y, z);
    46.  
    47.  
    48.                     if(y==chunkHeight-1||voxels[x,y+1,z]==0)
    49.                          mesh.Inject(FaceDefinitions.TopFace, x, y, z);
    50.  
    51.                     if (x == chunkWidth - 1 || voxels[x + 1, y, z] == 0)
    52.                         mesh.Inject(FaceDefinitions.RightFace, x, y, z);
    53.  
    54.                     if (z == chunkWidth - 1 || voxels[x, y , z + 1] == 0)
    55.                         mesh.Inject(FaceDefinitions.BackFace, x, y, z);
    56.  
    57.  
    58.  
    59.                
    60.                  
    61.  
    62.  
    63.                 }
    64.         this.mesh = mesh.ToMesh();
    65.   }
    66. }
    67. public class CubeFace
    68. {
    69.     public V3[] Verticies = { };
    70.     public int[] Triangles = { };
    71.     public V2[] Uvs = { };
    72. }
    73. public class FaceDefinitions
    74. {
    75.     private static readonly int[] normal = { 1, 3, 0, 2, 0, 3 };
    76.     private static readonly int[] inverted = {1,0,3,2,3,0 };
    77.     public static CubeFace BottomFace = new CubeFace()
    78.     {
    79.         Verticies = new V3[]{
    80.             new V3(0,0,0),//0
    81.             new V3(1,0,0),//1
    82.             new V3(0,0,1),//2
    83.             new V3(1,0,1)//3
    84.  
    85.         },
    86.         Triangles = normal,
    87.         Uvs = new V2[] {
    88.             new V2(0,0),
    89.             new V2(0,1),
    90.             new V2(1,0),
    91.             new V2(1,1)
    92.  
    93.         }
    94.  
    95.  
    96.  
    97.     };
    98.  
    99.     public static CubeFace TopFace = new CubeFace()
    100.     {
    101.         Verticies = new V3[]{
    102.             new V3(0,1,0),//0
    103.             new V3(1,1,0),//1
    104.             new V3(0,1,1),//2
    105.             new V3(1,1,1)//3
    106.  
    107.         },
    108.         Triangles = inverted,
    109.         Uvs = new V2[] {
    110.             new V2(0,0),
    111.             new V2(0,1),
    112.             new V2(1,0),
    113.             new V2(1,1)
    114.  
    115.         }
    116.  
    117.  
    118.  
    119.     };
    120.     public static CubeFace BackFace = new CubeFace()
    121.     {
    122.         Verticies = new V3[]{
    123.             new V3(0,0,1),//0
    124.             new V3(1,0,1),//1
    125.              new V3(0,1,1),//0
    126.             new V3(1,1,1)
    127.          
    128.  
    129.         },
    130.         Triangles = normal,
    131.         Uvs = new V2[] {
    132.             new V2(0,0),
    133.             new V2(0,1),
    134.             new V2(1,0),
    135.             new V2(1,1)
    136.  
    137.         }
    138.  
    139.  
    140.  
    141.     };
    142.     public static CubeFace FrontFace = new CubeFace()
    143.     {
    144.         Verticies = new V3[]{
    145.             new V3(0,0,0),//0
    146.             new V3(1,0,0),//1
    147.              new V3(0,1,0),//0
    148.             new V3(1,1,0)
    149.  
    150.  
    151.         },
    152.         Triangles = inverted,
    153.         Uvs = new V2[] {
    154.             new V2(0,0),
    155.             new V2(0,1),
    156.             new V2(1,0),
    157.             new V2(1,1)
    158.  
    159.         }
    160.  
    161.  
    162.  
    163.     };
    164.     public static CubeFace RightFace = new CubeFace()
    165.     {
    166.         Verticies = new V3[]{
    167.             new V3(1,0,0),
    168.              new V3(1,1,0),
    169.               new V3(1,0,1),
    170.              new V3(1,1,1)
    171.         },
    172.         Triangles = normal,
    173.         Uvs = new V2[] {//11 01 10 00
    174.             new V2(1,1),
    175.             new V2(0,1),
    176. new V2(1,0),
    177.  
    178. new V2(0,0)
    179.  
    180.  
    181.         }
    182.     };
    183.  
    184.     public static CubeFace LeftFace = new CubeFace()
    185.     {
    186.         Verticies = new V3[]{
    187.             new V3(0,0,0),
    188.              new V3(0,1,0),
    189.               new V3(0,0,1),
    190.              new V3(0,1,1)
    191.         },
    192.         Triangles = inverted,
    193.         Uvs = new V2[] {//11 01 10 00
    194.             new V2(1,1),
    195.             new V2(0,1),
    196. new V2(1,0),
    197.  
    198. new V2(0,0)
    199.  
    200.  
    201.  
    202.         }
    203.     };
    204. }
    205.  
    206. public class MultiVoxMesh
    207. {
    208.     private List<V3> verticies { get; set; }
    209.     private List<int> triangles { get; set; }
    210.     private List<V2> uvs { get; set; }
    211.     public MultiVoxMesh()
    212.     {
    213.         verticies = new List<V3>();
    214.         triangles = new List<int>();
    215.         uvs = new List<V2>();
    216.  
    217.     }
    218.     public void InjectTriangles(int[] triangles)
    219.     {
    220.         int currentIndex = verticies.Count;
    221.  
    222.         this.triangles.AddRange(triangles.Select(o => o + currentIndex));
    223.      
    224.     }
    225.     public void InjectUvs(V2[] uvs) => this.uvs.AddRange(uvs);
    226.     public void InjectVectors(V3[] face,int x , int y, int z)=>this.verticies.AddRange(face.Select(o => new V3( o.x + x - 0.5f, o.y + y - 0.5f,o.z + z - 0.5f)  ));
    227.     public void Inject(CubeFace face, int x, int y, int z)
    228.     {
    229.         this.InjectTriangles(face.Triangles);
    230.         this.InjectVectors(face.Verticies,x,y,z);
    231.         this.InjectUvs(face.Uvs);
    232.  
    233.     }
    234.     public Mesh ToMesh()
    235.     {
    236.         Mesh mesh = new Mesh();
    237.         mesh.vertices = this.verticies.ToArray();
    238.         mesh.triangles = this.triangles.ToArray();
    239.         mesh.uv = this.uvs.ToArray();
    240.         return mesh;
    241.     }
    242.  
    243. }
    244.  
     
    PROE_ likes this.
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    That's really neat.

    As far as I know, there are currently no good, well-maintained voxel assets in the Asset Store. You should consider cleaning & documenting this and making it available there!
     
  7. shatteredeyeinc

    shatteredeyeinc

    Joined:
    Jan 10, 2019
    Posts:
    5
    yeah thats not a bad idea
     
    JoeStrout likes this.
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Please notify me if you do. Even though I have my own voxel code sitting around somewhere, I like your approach better!

    I'm currently working on a Scratch-like environment in VR. One of the features I'm thinking about adding someday is a voxel chunk. You'd define a bunch of block types in terms of the 6 face textures they show, and then have script pieces to set/get the block type at any xyz position in the chunk. Pretty straightforward, but I think scripters would really get into it.