Log inGet started

Render textures

Updated 4 September 2026

A camera renders into a texture you create

A render texture is just a GPU texture — the same kind a .texture asset uploads to — that starts empty and gets its pixels from a camera instead of a CPU upload. So you create the texture first (it owns its own size), then point a camera at it:

-- The texture the camera renders into — a GPU texture with no pixels yet.
local tex = renderer.texture.create({ width = 512, height = 512, name = "mirror" })

local e = entity.spawn("mirror_cam")   -- returns the entity proxy directly
e.position = { 0, 3, 5 }
e.component.add("Camera", { priority = 5 })

local cam = e.component.get("Camera")
cam:setTargetTexture(tex)    -- render into the texture (nil = back to the viewport)
cam:lookAt({ 0, 1, 0 })      -- aim it (entity id or {x, y, z})

A second camera like this renders a view distinct from the player's primary camera (which the scene already spawns — the scenes guide). Its Camera fields fov / near / far / priority / postProcessing and the renderLayers spec (e.g. "all !ui", where ui / sky / debug / EditorUI are built-in layers) control what and how it draws.

How it looks when something draws it larger than it is

A render target takes filter the way raw pixels do, and means the same thing by it:

local panel = renderer.texture.create({ width = 64, height = 32, filter = "nearest" })

"linear" (the default) smooths between the target's pixels, which is right for a camera's view shown at about its own size. "nearest" keeps them square, which is what an image whose pixels ARE the subject needs — a 64x32 LED matrix holds no detail between its pixels to interpolate, so smoothing one is not a softer picture but a different one.

It is said about the TARGET, not about the window showing it, so every surface wearing the target agrees: a viewport widget, a magnified capture, a material's texture slot. And it is kept against the handle, so a resize that reallocates the texture does not lose it.

The camera only references the texture — it doesn't own it. The texture's lifetime is yours: free it with renderer.destroy(tex) when you're done (this reclaims the GPU texture and the camera's render scratch). Don't rely on the camera's despawn to clean it up.

Using the result

tex is a texture like any other; its tex.guid is what other things reference. Two common destinations:

  • In UI — the viewport widget takes the texture guid, giving you in-UI 3D views and minimaps (the ui guide):

    { type = "viewport", props = { renderTarget = tex.guid } }
    
  • On a surface — feed tex.guid to a material as a texture so a mesh displays the rendered view (a screen, a mirror) (the materials guide):

    Material.setTexture("monitor_mat", "base_color_texture", tex.guid)
    
  • Off this machine, as bytesframeStream carries the texture's frames into a byte stream, so what a camera renders reaches a browser, a phone, an LED panel or another process, live. stream.open makes the destination (tcp://host:port, or loopback://name to read the frames back in Luau), and frameStream.attach binds the two:

    local out = task.await(stream.open("loopback://panel", { inboundCapacity = 512 * 512 * 3 * 4 }))
    local session = frameStream.attach(tex.guid, out, { fps = 30, format = "rgb24" })
    local frame = stream.read(out, 512 * 512 * 3)   -- one whole frame's pixels, or ""
    

    Each frame is width * height * bytesPerPixel bytes of tight rows, written in one call, so a reader gets a whole frame or none of one. frameStream.status(session) separates a consumer that is too slow (droppedBackpressure) from a GPU that is (stalledReadbacks). The readback runs off the render thread, so the renderer is never held up. The topics/frame-streaming guide covers this end to end, and topics/byte-streams covers the stream the pixels travel down.

Building or reading textures directly

renderer.texture.create also builds a runtime GPU texture from raw pixels ({ rgba, width, height }) or from a .texture AssetRef's loaded CPU pixels; textureRef:load() reads a .texture asset's pixels on the CPU. renderer.destroy(handle) frees any of them.

Finding the rest

asset.inspect("@builtin::components.Camera") documents the Camera's render-to-texture surface (textureHandle, setTargetTexture, render, capture); lsp.methods("renderer") lists the renderer.texture API; lsp.methods("frameStream") and lsp.methods("stream") list the egress side, and stream.transports() names the transports this build carries; the ui guide covers the viewport widget. The model to hold: a camera renders into a GPU texture, and that texture goes anywhere a texture goes — UI or materials. There is no separate "render target" concept; it's the same texture, just filled by a camera instead of an upload.

  • documentation
  • guide