Sprites in 3D

Billboarded Sprite3D gems over a texture painted pixel by pixel in code, each one named by a Label3D. The center sign keeps billboard off for comparison.

The editor above is running this project. Change a line and it reloads.

Godot classes used

Sprite3DLabel3DImageTexture

The code

scripts/main.gd
extends Node

const ICON: int = 16
const RING: float = 5.0
const COLORS: Array[String] = ['#8da5f3', '#8eef97', '#fc7f7f', '#f3d48d']

var camera: Camera3D
var angle: float = 0.0


func _ready() -> void:
	_add_environment()
	_add_light()
	_add_ground()
	_add_fixed_sign()

	for i in COLORS.size():
		var turn: float = TAU * i / COLORS.size() + PI * 0.25
		_add_gem(Vector3(cos(turn), 0, sin(turn)) * RING, COLORS[i])

	camera = Camera3D.new()
	camera.fov = 50.0
	add_child(camera)
	camera.current = true
	_place_camera()

	print('the camera keeps circling, the gems and their names stay flat on screen, the center sign does not')


func _process(delta: float) -> void:
	angle += delta * 0.3
	_place_camera()


func _place_camera() -> void:
	camera.position = Vector3(sin(angle), 0, cos(angle)) * 11.0 + Vector3(0, 4.4, 0)
	camera.look_at(Vector3(0, 1.5, 0), Vector3.UP)


func _add_gem(at: Vector3, hex: String) -> void:
	var sprite: Sprite3D = Sprite3D.new()
	sprite.texture = _make_icon(Color(hex))
	sprite.pixel_size = 0.15
	sprite.billboard = BaseMaterial3D.BILLBOARD_ENABLED
	sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
	sprite.alpha_cut = SpriteBase3D.ALPHA_CUT_DISCARD
	sprite.shaded = false
	sprite.position = at + Vector3(0, 1.5, 0)
	add_child(sprite)

	var label: Label3D = Label3D.new()
	label.text = hex
	label.font_size = 64
	label.pixel_size = 0.0055
	label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
	label.alpha_cut = Label3D.ALPHA_CUT_DISCARD
	label.modulate = Color(hex)
	label.outline_size = 14
	label.outline_modulate = Color(0, 0, 0, 0.9)
	label.position = at + Vector3(0, 2.9, 0)
	add_child(label)


func _add_fixed_sign() -> void:
	var label: Label3D = Label3D.new()
	label.text = 'billboard off'
	label.font_size = 90
	label.pixel_size = 0.011
	label.billboard = BaseMaterial3D.BILLBOARD_DISABLED
	label.alpha_cut = Label3D.ALPHA_CUT_DISCARD
	label.modulate = Color('#c8ccd8')
	label.outline_size = 16
	label.outline_modulate = Color(0, 0, 0, 0.9)
	label.position = Vector3(0, 4.8, 0)
	add_child(label)


func _make_icon(tint: Color) -> ImageTexture:
	var image: Image = Image.create(ICON, ICON, false, Image.FORMAT_RGBA8)
	image.fill(Color(0, 0, 0, 0))

	var center: float = (ICON - 1) * 0.5

	for y in ICON:
		for x in ICON:
			var reach: float = abs(x - center) + abs(y - center)

			if reach > center:
				continue

			var shade: float = 1.0 - reach / center * 0.6
			image.set_pixel(x, y, Color(tint.r * shade, tint.g * shade, tint.b * shade, 1.0))

	return ImageTexture.create_from_image(image)


func _add_ground() -> void:
	var mesh: PlaneMesh = PlaneMesh.new()
	mesh.size = Vector2(120, 120)

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#242938')
	material.roughness = 0.95

	var ground: MeshInstance3D = MeshInstance3D.new()
	ground.mesh = mesh
	ground.material_override = material
	add_child(ground)


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.07, 0.09, 0.18)
	sky_material.sky_horizon_color = Color(0.26, 0.29, 0.4)
	sky_material.ground_bottom_color = Color(0.05, 0.06, 0.09)
	sky_material.ground_horizon_color = Color(0.18, 0.2, 0.28)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 0.9

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-55, -35, 0)
	light.light_energy = 1.2
	add_child(light)

More 3D scene examples