내일배움캠프 Unity 55일차 TIL - Voxel 맵 렌더링 최적화 (3)

Wooooo·2024년 1월 14일
0

내일배움캠프Unity

목록 보기
57/94

[오늘의 키워드]

Voxel 타일에 텍스처링 기능을 추가했다.
Chunk를 World 클래스에서 관리하도록 하고, 여러 개의 청크 생성 시에도 맞닿은 면을 그려내지 않도록 했다.


[Voxel Texturing]

1. [BlockType 클래스에 텍스처 정보 추가]

//TODO: Scriptable Object로 빼도 될듯 ?
[System.Serializable]
public class BlockType
{
    public string blockName;
    public bool isSolid;

    public int backFaceTexture;
    public int frontFaceTexture;
    public int topFaceTexture;
    public int bottomFaceTexture;
    public int leftFaceTexture;
    public int rightFaceTexture;

    public int GetTextureID (int faceIndex)
    {
        switch (faceIndex)
        {
            case 0: return backFaceTexture;
            case 1: return frontFaceTexture;
            case 2: return topFaceTexture;
            case 3: return bottomFaceTexture;
            case 4: return leftFaceTexture;
            case 5: return rightFaceTexture;
            default: Debug.LogError($"{nameof(GetTextureID)}: Invalid face index."); return 0;
        }
    }
}
Chunk.cs

    private void AddVoxelDataToChunk(Vector3Int pos)
    {
        for (int i = 0; i < 6; i++)
        {
            if (CheckVoxel(pos + VoxelData.faceChecks[i]))
                continue;

            for (int j = 0; j < 4; j++)
                vertices.Add(pos + VoxelData.voxelVerts[VoxelData.voxelTris[i][j]]);

            var blockID = world.VoxelMap[pos];

            AddTextureUV(world.BlockTypes[blockID].GetTextureID(i)); //★
            triangles.Add(vertexIdx);
            triangles.Add(vertexIdx + 1);
            triangles.Add(vertexIdx + 2);
            triangles.Add(vertexIdx + 2);
            triangles.Add(vertexIdx + 1);
            triangles.Add(vertexIdx + 3);
            vertexIdx += 4;
        }
    }

블럭의 각 면마다 텍스처를 지정할 수 있게 해줬다.
텍스처 아틀라스를 사용할 예정이기 때문에, 아틀라스의 n번째 칸의 텍스처를 저장하도록 int형을 사용했다.

uv 데이터의 생성은 Chunk.cs에서 for문으로 각 면의 데이터를 생성중이다. for문의 인덱스를 받아와서 해당하는 면의 텍스처 ID를 반환하는 GetTextureID() 메서드도 작성했다.

2. [Texture Offset 지정]

VoxelData.cs

    // 텍스쳐 내에서 의도치 않게 들어가는 부분 잘라내기
    public const float uvXBeginOffset = 0.01f;
    public const float uvXEndOffset = -0.01f;
    public const float uvYBeginOffset = 0.01f;
    public const float uvYEndOffset = -0.01f;
Chunk.cs

    private void AddTextureUV(int x, int y)
    {
        if (x < 0 || y < 0 || x >= VoxelData.TextureAtlasWidth || y >= VoxelData.TextureAtlasHeight)
            Debug.LogError($"텍스쳐 아틀라스의 범위를 벗어났습니다 : [x = {x}, y = {y}]");

        float nw = VoxelData.NormalizeTextureAtlasWidth;
        float nh = VoxelData.NormalizeTextureAtlasHeight;

        float uvX = x * nw;
        float uvY = y * nh;

        uvs.Add(new Vector2(uvX + VoxelData.uvXBeginOffset, uvY + VoxelData.uvYBeginOffset));
        uvs.Add(new Vector2(uvX + VoxelData.uvXBeginOffset, uvY + nh + VoxelData.uvYEndOffset));
        uvs.Add(new Vector2(uvX + nw + VoxelData.uvXEndOffset, uvY + VoxelData.uvYBeginOffset));
        uvs.Add(new Vector2(uvX + nw + VoxelData.uvXEndOffset, uvY + nh + VoxelData.uvYEndOffset));
    }

Offset 없이 텍스처를 씌워주면 Voxel의 경계마다 선이 보여서, 조금씩 잘라줬다.

3. [구현 모습]


[Chunk를 World 클래스에서 관리하기]

1. [Chunk에서 MonoBehaviour 떼기]

public class Chunk
{
    private GameObject chunkObject;
    private MeshRenderer meshRenderer;
    private MeshFilter meshFilter;
    private ChunkCoord coord;

    private int vertexIdx = 0;
    private List<Vector3> vertices = new();
    private List<int> triangles = new();
    private List<Vector2> uvs = new();

    private World world;

    public ChunkCoord ChunkCoord => coord;

    public Chunk(ChunkCoord coord, World world)
    {
        this.world = world;
        this.coord = coord;

        chunkObject = new($"{nameof(Chunk)} {coord.x:D2}, {coord.z:D2}");
        meshRenderer = chunkObject.AddComponent<MeshRenderer>();
        meshFilter = chunkObject.AddComponent<MeshFilter>();

        meshRenderer.material = world.Material;
        chunkObject.transform.SetParent(world.transform);
        //chunkObject.transform.position = new(coord.x * VoxelData.ChunkWidth, 0f, coord.z * VoxelData.ChunkWidth);

        CreateMeshData();
        CreateMesh();
    }
    ...

Chunk 클래스에서 MonoBehaviour를 떼고, Start 메서드에서 작업하던 동작을 생성자로 빼줬다.
생성 시, GameObject를 생성하고 필요한 Component를 붙이고 Mesh를 생성한다.

2. [VoxelMap 정보를 World 클래스로 이관]

각 Chunk마다 가지던 VoxelMap 딕셔너리를 World 클래스로 옮겼다.

기존에는 안쪽면, 바깥면을 Chunk 단위에서 VoxelMap을 기준으로 해당 칸에 다른 Voxel이 있는지 없는지를 체크해서 맞닿은 면이 없다면 Mesh를 생성하는 구조였다. 따라서 여러 개의 Chunk를 이어붙이면 Chunk 내부에 또 불필요한 Mesh가 그려지는, 처음과 똑같은 상황이 발생한다.

VoxelMap 딕셔너리를 World 클래스로 옮기고, World에 있는 모든 Voxel을 캐싱하여, Chunk에서 Mesh 생성 시 World 단위로 안쪽면, 바깥면을 계산할 수 있도록 했다.

3. [TODO: 플레이어 근처 Chunk만 활성화하기]

플레이어 근처, 즉 카메라 시야 범위 내의 Chunk만 활성화하는 코드가 필요하다.

profile
game developer

0개의 댓글