Sprites en 3D
Des gemmes Sprite3D en billboard sur une texture peinte pixel par pixel en code, chacune nommée par un Label3D. Le panneau central garde le billboard désactivé pour comparer.
L'éditeur ci-dessus fait tourner ce projet. Changez une ligne et il se recharge.
Classes Godot utilisées
Sprite3DLabel3DImageTexture
Le 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)