Godot Version
v4.6 stable
Question
Hello! I am confused about texture UVs as I got them all correctly mapped using a texture atlas, just the textures are rotated differently on all sides, I do not know how to fix this. I was following this tutorial: https://www.youtube.com/watch?v=6kfUlmv6bvc&list=PLqF5LscxmBZWBPnlnPg_OSr2xd_WkSGuG&index=1
(texture atlas in question attached for clarity)
![]()
@tool
extends MeshInstance3D
@export var block_size : float = 1.0
@export var chunk_width : int = 4
@export var chunk_height : int = 16
@export var chunk_depth : int = 4
@export_category("Texture Settings")
@export var atlas_texture : Texture2D
@export var atlas_columns : int = 3
@export var atlas_rows : int = 1
var block_mesh : ArrayMesh
var voxels = []
var rng = RandomNumberGenerator.new()
const FACE_TOP : = 0
const FACE_BOTTOM : = 1
const FACE_SIDE : = 2
const BLOCKS : = {
0 : {"name" : "Air", "solid" : false, "tileData" : {FACE_TOP : Vector2i(0,0), FACE_BOTTOM : Vector2i(0,0), FACE_SIDE : Vector2i(0,0)}},
1 : {"name" : "Grass", "solid" : true, "tileData" : {FACE_TOP : Vector2i(2,0), FACE_BOTTOM : Vector2i(0,0), FACE_SIDE : Vector2i(1,0)}},
2 : {"name" : "Dirt", "solid" : true, "tileData" : {FACE_TOP : Vector2i(0,0), FACE_BOTTOM : Vector2i(0,0), FACE_SIDE : Vector2i(0,0)}},
3 : {"name" : "Stone", "solid" : true, "tileData" : {FACE_TOP : Vector2i(0,1), FACE_BOTTOM : Vector2i(0,1), FACE_SIDE : Vector2i(0,1)}},
4 : {"name" : "Test Block", "solid" : true, "tileData" : {FACE_TOP : Vector2i(1,1), FACE_BOTTOM : Vector2i(1,1), FACE_SIDE : Vector2i(1,1)}}
}
func _ready() -> void:
var rand_gen = RandomNumberGenerator.new()
voxels = generate_voxels()
generate_mesh(voxels)
var mat : = StandardMaterial3D.new()
mat.albedo_texture = atlas_texture
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
material_override = mat
func generate_mesh(voxels):
var faces = []
for x in range(voxels.size()):
for y in range(voxels[x].size()):
for z in range(voxels[x][y].size()):
var block_id : int = voxels[x][y][z]
if block_id == 0:
continue
if voxels[x][y][z] != 0:
var position = Vector3(x,y,z) * block_size
if x == 0 or voxels[x - 1][y][z] == 0:
var t := block_tile_for_face(block_id, Vector3.LEFT)
faces.append(create_face(Vector3.LEFT, position, get_tile_uvs(t.x, t.y)))
if x == voxels.size() - 1 or voxels[x + 1][y][z] == 0:
var t := block_tile_for_face(block_id, Vector3.RIGHT)
faces.append(create_face(Vector3.RIGHT, position, get_tile_uvs(t.x, t.y)))
if y == 0 or voxels[x][y - 1][z] == 0:
var t := block_tile_for_face(block_id, Vector3.DOWN)
faces.append(create_face(Vector3.DOWN, position, get_tile_uvs(t.x, t.y)))
if y == voxels[x].size() - 1 or voxels[x][y + 1][z] == 0:
var t := block_tile_for_face(block_id, Vector3.UP)
faces.append(create_face(Vector3.UP, position, get_tile_uvs(t.x, t.y)))
if z == 0 or voxels[x][y][z - 1] == 0:
var t := block_tile_for_face(block_id, Vector3.FORWARD)
faces.append(create_face(Vector3.FORWARD, position, get_tile_uvs(t.x, t.y)))
if z == voxels[x][y].size() - 1 or voxels[x][y][z + 1] == 0:
var t := block_tile_for_face(block_id, Vector3.BACK)
faces.append(create_face(Vector3.BACK, position, get_tile_uvs(t.x, t.y)))
var vertices = []
var normals = []
var uvs = []
for face in faces:
vertices += face["vertices"]
normals += face["normals"]
uvs += face["uvs"]
var vertex_array = PackedVector3Array(vertices)
var normal_array = PackedVector3Array(normals)
var uv_array = PackedVector2Array(uvs)
var arrays = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertex_array
arrays[Mesh.ARRAY_NORMAL] = normal_array
arrays[Mesh.ARRAY_TEX_UV] = uv_array
block_mesh = ArrayMesh.new()
block_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
self.mesh = block_mesh
func generate_voxels() -> Array:
rng.randomize()
var array = []
array.resize(chunk_width)
for x in chunk_width:
array[x] = []
array[x].resize(chunk_height)
for y in chunk_height:
array[x][y] = []
array[x][y].resize(chunk_depth)
for x in chunk_width:
for y in chunk_height:
for z in chunk_depth:
array[x][y][z] = rng.randi_range(0,1)
return array
func create_face(direction : Vector3, position: Vector3, uv_coords : Array) -> Dictionary:
var vertices = []
var normals = []
var uvs = []
normals.resize(4)
match direction:
Vector3.UP:
vertices = [
position + Vector3(-0.5, 0.5, -0.5) * block_size,
position + Vector3(0.5, 0.5, -0.5) * block_size,
position + Vector3(0.5, 0.5, 0.5) * block_size,
position + Vector3(-0.5, 0.5, 0.5) * block_size
]
normals.fill(Vector3.UP)
uvs = uv_coords
Vector3.DOWN:
vertices = [
position + Vector3(-0.5, -0.5, 0.5) * block_size,
position + Vector3(0.5, -0.5, 0.5) * block_size,
position + Vector3(0.5, -0.5, -0.5) * block_size,
position + Vector3(-0.5, -0.5, -0.5) * block_size
]
normals.fill(Vector3.DOWN)
uvs = uv_coords
Vector3.LEFT:
vertices = [
position + Vector3(-0.5, -0.5, -0.5) * block_size,
position + Vector3(-0.5, 0.5, -0.5) * block_size,
position + Vector3(-0.5, 0.5, 0.5) * block_size,
position + Vector3(-0.5, -0.5, 0.5) * block_size
]
normals.fill(Vector3.LEFT)
uvs = uv_coords
Vector3.RIGHT:
vertices = [
position + Vector3(0.5, -0.5, 0.5) * block_size,
position + Vector3(0.5, 0.5, 0.5) * block_size,
position + Vector3(0.5, 0.5, -0.5) * block_size,
position + Vector3(0.5, -0.5, -0.5) * block_size
]
normals.fill(Vector3.RIGHT)
uvs = uv_coords
Vector3.FORWARD:
vertices = [
position + Vector3(-0.5, -0.5, -0.5) * block_size,
position + Vector3(0.5, -0.5, -0.5) * block_size,
position + Vector3(0.5, 0.5, -0.5) * block_size,
position + Vector3(-0.5, 0.5, -0.5) * block_size
]
normals.fill(Vector3.FORWARD)
uvs = uv_coords
Vector3.BACK:
vertices = [
position + Vector3(-0.5, 0.5, 0.5) * block_size,
position + Vector3(0.5, 0.5, 0.5) * block_size,
position + Vector3(0.5, -0.5, 0.5) * block_size,
position + Vector3(-0.5, -0.5, 0.5) * block_size
]
normals.fill(Vector3.BACK)
uvs = uv_coords
return{
"vertices" : [
vertices[0], vertices[1], vertices[2],
vertices[0], vertices[2], vertices[3]
],
"normals" : [
normals[0], normals[1], normals[2],
normals[0], normals[2], normals[3]
],
"uvs" : [
uvs[0], uvs[1], uvs[2],
uvs[0], uvs[2], uvs[3]
]
}
func block_tile_for_face(block_id: int, face_dir: Vector3) -> Vector2i:
var block = BLOCKS.get(block_id, BLOCKS[0])
var tiles = block["tileData"]
if face_dir == Vector3.UP:
return tiles[FACE_TOP]
elif face_dir == Vector3.DOWN:
return tiles[FACE_BOTTOM]
elif face_dir == Vector3.LEFT:
return tiles[FACE_SIDE]
elif face_dir == Vector3.RIGHT:
return tiles[FACE_SIDE]
elif face_dir == Vector3.FORWARD:
return tiles[FACE_SIDE]
elif face_dir == Vector3.BACK:
return tiles[FACE_SIDE]
else:
return tiles
func get_tile_uvs(tile_x : int, tile_y : int) -> Array:
var tex_w = float(atlas_texture.get_width())
var tex_h = float(atlas_texture.get_height())
var tile_w_pixel = tex_w / float(atlas_columns)
var tile_h_pixel = tex_h / float(atlas_rows)
var u0 = (tile_x * tile_w_pixel) / tex_w
var v0 = (tile_y * tile_h_pixel) / tex_h
var u1 = ((tile_x + 1) * tile_w_pixel) / tex_w
var v1 = ((tile_y + 1) * tile_h_pixel) / tex_h
return [
Vector2(u0, v1),
Vector2(u1, v1),
Vector2(u1, v0),
Vector2(u0, v0)
]