Text on the floor
Draws text into a surface, then hangs that surface's texture on a plane with a
material tagged labels, so the label is painted on the platform in 3D. The
text is redrawn every frame, so what the floor says can change.
local natiny = require("natiny")
natiny.backend.init()
local win = natiny.window.create("Text on the floor", 900, 600)
natiny.window.set_scale_mode(win, natiny.window.SCALE_MODE_STRETCH)
-- The label material. Text is 2D, so it is drawn into a surface first; this
-- shader only puts that surface on a mesh, unlit, with its alpha kept.
local LABEL_SHADER = [[
attribute vec3 position;
attribute vec2 texcoord0;
varying vec2 var_texcoord0;
uniform mat4 mtx_worldviewproj;
uniform sampler2D label_map;
void vertex()
{
/* The plane's UVs run opposite to 2D screen space on both axes, so the
label would land upside down on the floor without this. */
var_texcoord0 = vec2(1.0) - texcoord0;
POSITION = mtx_worldviewproj * vec4(position, 1.0);
}
void fragment()
{
COLOR = texture(label_map, var_texcoord0);
}
]]
local BG = { 21 / 255, 30 / 255, 44 / 255 } -- #151E2C
local ACCENT = { 168 / 255, 194 / 255, 56 / 255 } -- #A8C238
local LIGHT_TEXT = { 243 / 255, 245 / 255, 250 / 255 } -- #F3F5FA
local LABEL_W, LABEL_H = 512, 256
local res = natiny.resource.load("data/common/fonts/roboto.ttf")
local font = natiny.font.create(res, 72)
local small = natiny.font.create(res, 18)
-- The text lives here: an off-screen colour target, no depth, cleared to
-- transparent every frame so only the glyphs reach the plane.
local label = natiny.surface.create(LABEL_W, LABEL_H, natiny.surface.COLOR, false)
local shader_source = natiny.resource.from_data(LABEL_SHADER)
local shader = natiny.shader.load(shader_source)
natiny.resource.destroy(shader_source)
local material = natiny.material.create(shader, "labels")
natiny.material.set_state(material, {
blend = natiny.material.BLEND_ALPHA,
cull = natiny.material.CULL_NONE,
depth = natiny.material.DEPTH_ON,
})
natiny.material.set_texture(material, "label_map", natiny.surface.get_texture(label))
local platform_res = natiny.resource.load("data/common/models/platform.glb")
local platform = natiny.model.create(natiny.mesh.load(platform_res))
natiny.resource.destroy(platform_res)
-- A plane lies in XZ facing up, so the label reads as paint on the floor.
-- Two centimetres above it keeps the two surfaces from fighting for depth.
local sign = natiny.model.create(natiny.mesh.plane(3.0, 1.5))
natiny.model.set_material(sign, material)
natiny.entity.set_position(sign, natiny.entity.SPACE_WORLD, 0.0, 0.02, 0.0)
local pivot = natiny.pivot.create()
local camera = natiny.camera.create()
natiny.entity.set_parent(camera, pivot)
natiny.entity.set_position(camera, natiny.entity.SPACE_LOCAL, 0.0, 2.4, 3.0)
natiny.entity.look_at(camera, 0.0, 0.0, 0.0)
natiny.camera.set_fov(camera, 55.0)
natiny.camera.set_clip(camera, 0.1, 100.0)
local time = 0
natiny.backend.loop(function(dt)
time = time + dt
natiny.entity.rotate(pivot, natiny.entity.SPACE_LOCAL, 0.0, 20.0 * dt, 0.0)
natiny.window.bind(win, function()
-- Redrawn every frame, so the floor can say something that changes.
natiny.surface.bind(label, function()
natiny.render.clear(0, 0, 0, 0)
natiny.render.set_color(ACCENT[1], ACCENT[2], ACCENT[3], 1)
natiny.render.text_box(font, 0, 0, LABEL_W, LABEL_H,
string.format("Natiny\n%.1f s", time),
natiny.render.ALIGN_CENTER | natiny.render.ALIGN_MIDDLE)
end)
natiny.render.clear(BG[1], BG[2], BG[3], 1)
natiny.camera.bind(camera, function()
natiny.render.tag("model")
natiny.render.tag("labels")
end)
natiny.render.set_color(LIGHT_TEXT[1], LIGHT_TEXT[2], LIGHT_TEXT[3], 1)
natiny.render.text(small, 24, 24,
"a surface with text on it, worn by a plane tagged \"labels\"")
end)
end)
natiny.backend.shutdown()