Instancing
Four hundred boxes, one mesh and one material. SPACE turns instancing on
and off, and the panel counts the draw calls it takes either way.
local natiny = require("natiny")
if not natiny.backend.init(arg and arg[1] or natiny.backend.AUTO) then
os.exit(1)
end
local window = natiny.window.create("Natiny - Instancing", 960, 640)
natiny.window.set_scale_mode(window, natiny.window.SCALE_MODE_STRETCH)
local BG = natiny.color.from_hex("#151E2C")
local PLATE = natiny.color.from_hex("#0B121EC8")
local INK = natiny.color.from_hex("#E6EDFA")
local ACCENT = natiny.color.from_hex("#A8C238")
local COLUMNS, ROWS = 20, 20
local SPACING = 1.6
local ORBIT_SPEED = 12.0
local function load_resource(path, make)
local resource = natiny.resource.load(path)
local object = make(resource)
natiny.resource.destroy(resource)
return object
end
local box_mesh = load_resource("common/models/box_01.glb", natiny.mesh.load)
local shader = load_resource("common/shaders/unlit.nsl", natiny.shader.load)
local font = load_resource("common/fonts/roboto.ttf", function(resource)
return natiny.font.create(resource, 18)
end)
-- unlit.nsl is the engine's own default material written out on disk, so the
-- boxes look exactly the same whether they are instanced or not.
local material = natiny.material.create(shader)
natiny.material.add_tag(material, "boxes")
natiny.material.set_instanced(material, true)
-- One mesh, one material, four hundred models.
for row = 0, ROWS - 1 do
for column = 0, COLUMNS - 1 do
local model = natiny.model.create(box_mesh, material)
natiny.entity.set_position(model, natiny.entity.SPACE_WORLD,
(column - (COLUMNS - 1) * 0.5) * SPACING,
0.0,
(row - (ROWS - 1) * 0.5) * SPACING)
end
end
local pivot = natiny.pivot.create()
natiny.entity.set_position(pivot, natiny.entity.SPACE_WORLD, 0.0, 0.0, 0.0)
local camera = natiny.camera.create()
natiny.entity.set_parent(camera, pivot)
natiny.entity.set_position(camera, natiny.entity.SPACE_LOCAL, 0.0, 14.0, 26.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, 200.0)
local MODELS = COLUMNS * ROWS
local PARTS = natiny.mesh.get_part_count(box_mesh)
-- Without instancing every model is drawn on its own, once per mesh part.
-- With instancing the whole grid is one draw per mesh part.
local function draw_calls()
if natiny.material.get_instanced(material) then
return PARTS
end
return MODELS * PARTS
end
local fps = 60.0
local function draw_panel()
local instanced = natiny.material.get_instanced(material)
local lines = {
{ "Instancing: " .. (instanced and "ON" or "OFF"), instanced and ACCENT or INK },
{ "Models: " .. MODELS, INK },
{ "Draw calls: " .. draw_calls(), INK },
{ string.format("FPS: %.0f", fps), INK },
{ "SPACE - toggle instancing", INK },
}
local line_height = natiny.font.get_line_height(font)
local width = 0
for _, line in ipairs(lines) do
width = math.max(width, natiny.font.measure_text(font, line[1]))
end
natiny.render.set_color(PLATE)
natiny.render.rounded_rectangle(16, 16, width + 32, line_height * #lines + 32, 10)
for index, line in ipairs(lines) do
natiny.render.set_color(line[2])
natiny.render.text(font, 32, 32 + line_height * (index - 1), line[1])
end
end
natiny.backend.loop(function(dt)
if dt > 0 then
fps = fps + (1 / dt - fps) * math.min(dt * 8, 1)
end
if natiny.input.get_key_pressed(natiny.input.KEY_SPACE) then
natiny.material.set_instanced(material, not natiny.material.get_instanced(material))
end
natiny.entity.rotate(pivot, natiny.entity.SPACE_LOCAL, 0.0, ORBIT_SPEED * dt, 0.0)
natiny.window.bind(window, function()
natiny.render.clear(BG)
natiny.camera.bind(camera, function()
natiny.render.tag("boxes")
end)
draw_panel()
end)
end)
natiny.backend.shutdown()