Skip to content

Sprite

A Sprite is a rendering component in the engine used to draw 2D texture quads within a 3D scene. It is commonly used for labels, icons, points of interest (POI), health bars, billboards, effect patches, and more.

Version Note

The sprite system was introduced in @orillusion/core 0.9.0, replacing the GUI component system from earlier versions. The earlier GUI components such as UIPanel / UIImage / UITextField / UIButton have been removed, and the related needs are now uniformly implemented through SpriteRenderer (combined with billboard, etc.).

Basic Usage

Add a SpriteRenderer component to an Object3D and set a texture to draw a quad at its location:

ts
import { Engine3D, Scene3D, View3D, CameraUtil, HoverCameraController,
         Object3D, SpriteRenderer, BitmapTexture2D, Vector2 } from '@orillusion/core';

const engine = await Engine3D.init();
const scene = new Scene3D();

const camera = CameraUtil.createCamera3DObject(scene);
camera.perspective(60, engine.aspect, 0.1, 5000);
camera.object3D.addComponent(HoverCameraController).setCamera(0, -15, 10);

const view = new View3D();
view.scene = scene;
view.camera = camera;
engine.startRenderView(view);

// Load the texture
const tex = new BitmapTexture2D();
tex.flipY = true;
await tex.load('textures/logo.png');

// Create the sprite
const spriteObj = new Object3D();
const sprite = spriteObj.addComponent(SpriteRenderer);
sprite.texture = tex;
scene.addChild(spriteObj);

// Note: some properties need to be set after addChild (addChild triggers material initialization)
sprite.size = new Vector2(2, 2);   // The world size of the quad (meters)

Common Properties

PropertyTypeDescription
textureTextureThe sprite texture, which can be set via sprite.texture = tex or sprite.setTexture(tex)
sizeVector2The width and height of the quad in world space (meters)
pivotVector2The pivot (0~1). (0.5, 0.5) is centered, (0.5, 0) is the bottom-edge midpoint (commonly used for labels)
colorColorThe overlay color / opacity (multiplied with the texture)
cornerRadiusnumberThe corner radius (world units), based on SDF rounded corners; 0 means right angles
uvRectVector4The sampled UV sub-region (x, y, w, h), used for atlases
renderOrdernumberThe transparency sorting order; higher values are drawn later (stacked on top)
distanceInvariantSizebooleanWhether to keep a constant screen size; see distance invariance
ts
import { Color, Vector2 } from '@orillusion/core';

sprite.size = new Vector2(2, 2);
sprite.pivot = new Vector2(0.5, 0.5);
sprite.color = new Color(1, 0.85, 0.4, 1);
sprite.cornerRadius = 0.2;

Render Order

When multiple sprites overlap, you can use renderOrder to control the drawing order within the transparency bucket (the higher the value, the higher up it is stacked):

ts
cardA.renderOrder = 3000;
cardB.renderOrder = 3001; // Stacked on top of A
cardC.renderOrder = 3002; // Topmost layer

Example

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import {AtmosphericComponent, BillboardComponent, BillboardType, BitmapTexture2D, CameraUtil, DirectLight, Engine3D, HoverCameraController, Object3D, Scene3D, SpriteRenderer, Vector2, View3D } from "@orillusion/core";
import * as dat from 'dat.gui';

class Sample_Basic {
    private sprite!: SpriteRenderer;
    private spriteObj!: Object3D;
    private billboard!: BillboardComponent;
    private gui!: dat.GUI;

    private readonly s = {
        width: 2,
        height: 2,
        x: 0,
        y: 2,
        z: 0,
        cornerRadius: 0,
        billboard: BillboardType.None,
    };

    async run() {
        const engine = await Engine3D.init({});
        const scene = new Scene3D();
        const sky = scene.addComponent(AtmosphericComponent);
        
        this.gui = new dat.GUI();

        const camera = CameraUtil.createCamera3DObject(scene);
        camera.perspective(60, engine.aspect, 0.1, 5000);
        camera.object3D.addComponent(HoverCameraController).setCamera(0, -15, 10);

        const view = new View3D();
        view.scene = scene;
        view.camera = camera;
        engine.startRenderView(view);

        // Sun light for the atmospheric sky
        const lightObj = new Object3D();
        lightObj.rotationX = 45; lightObj.rotationY = 110;
        lightObj.addComponent(DirectLight).intensity = 3;
        scene.addChild(lightObj);
        sky.relativeTransform = lightObj.transform;

        // Load a texture
        const tex = new BitmapTexture2D();
        tex.flipY = true;
        await tex.load('https://cdn.orillusion.com/textures/KB3D_NTT_Ads_basecolor.png');

        // The sprite
        this.spriteObj = new Object3D();
        this.sprite = this.spriteObj.addComponent(SpriteRenderer);
        this.sprite.texture = tex;
        this.spriteObj.localPosition.set(this.s.x, this.s.y, this.s.z);
        scene.addChild(this.spriteObj);

        // Apply the initial state values to the sprite AFTER addChild
        // (addChild triggers material init which recomputes renderOrder
        // and any pre-attach sprite state you'd set through the renderer).
        this.sprite.size = new Vector2(this.s.width, this.s.height);
        this.sprite.cornerRadius = this.s.cornerRadius;

        // Billboard is composed as a separate component
        this.billboard = this.spriteObj.addComponent(BillboardComponent);
        this.billboard.type = this.s.billboard;

        this.initGUI();
    }

    private initGUI() {
        const s = this.s;
        const folder = this.gui.addFolder('Basic');
        folder.add(s, 'width',  0.2, 10, 0.1).onChange(v => this.sprite.size = new Vector2(v, s.height));
        folder.add(s, 'height', 0.2, 10, 0.1).onChange(v => this.sprite.size = new Vector2(s.width, v));
        folder.add(s, 'x', -10, 10, 0.1).onChange(v => this.spriteObj.x = v);
        folder.add(s, 'y', -10, 10, 0.1).onChange(v => this.spriteObj.y = v);
        folder.add(s, 'z', -10, 10, 0.1).onChange(v => this.spriteObj.z = v);
        folder.add(s, 'cornerRadius', 0, 1, 0.01).onChange(v => this.sprite.cornerRadius = v);
        folder.add(s, 'billboard', {
            None: BillboardType.None,
            'Billboard Y': BillboardType.BillboardY,
            'Billboard XYZ': BillboardType.BillboardXYZ,
        }).onChange(v => this.billboard.type = Number(v));
        folder.open();
    }
}

new Sample_Basic().run();

Next Steps

Released under the MIT License