Skip to content

GPU GUI

Orillusion provides high-performance GUI components for developers to use.
By using GUI components reasonably, you can flexibly display 2D/3D GUI content in the project. In this chapter, we first understand some basic concepts of GUI:

GUI Space Mode

Currently, GUI supports two rendering modes ViewSpace and WorldSpace:

  • ViewSpace mode: In this mode, the GUI component is rendered in screen space, and it will not change with the camera's perspective, nor will it have a 3D space occlusion relationship with other objects;
  • WorldSpace mode: In this mode, the GUI component can be regarded as a canvas in three-dimensional space, with 3D properties (rotation, scaling, translation), can participate in depth detection, etc., to achieve occlusion and occlusion relationship with other objects.
ts
import { ViewPanel, WorldPanel } from '@orillusion/core'

// Create a panel object
let panelRoot: Object3D = new Object3D()
// Add ViewPanel and set it to ViewSpace mode
panelRoot.addComponent(ViewPanel)
// Or add WorldPanel and set it to WorldSpace mode
panelRoot.addComponent(WorldPanel)

This following example shows the difference between ViewPanel and WorldPanel:

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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, View3D, ViewPanel, TextAnchor, UITextField, HoverCameraController, AtmosphericComponent, BitmapTexture2D, UIImage, makeAloneSprite, WorldPanel, GPUCullMode, UIPanel } from '@orillusion/core';
import * as dat from 'dat.gui';

// initializa engine
await Engine3D.init();
// create new scene as root node
let scene3D: Scene3D = new Scene3D();
scene3D.addComponent(AtmosphericComponent);
// create camera
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
// adjust camera view
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
// set camera controller
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(0, -20, 100);
// add camera node
scene3D.addChild(cameraObj);

let view = new View3D();
view.scene = scene3D;
view.camera = camera;
Engine3D.startRenderView(view);

// create a UICanvas
let canvas = view.enableUICanvas();
// create view sapce panel
let viewPanel: Object3D = new Object3D();
viewPanel.addComponent(ViewPanel);
// add to UICanvas
canvas.addChild(viewPanel);

// create world sapce panel
let worldPanel: Object3D = new Object3D();
worldPanel.localScale.set(0.15, 0.15, 0.15);
let panel: UIPanel = worldPanel.addComponent(WorldPanel);
// render double side
panel.cullMode = GPUCullMode.none;
// add to UICanvas
canvas.addChild(worldPanel);

// load a BitmapTexture2D
let bitmapTexture2D = new BitmapTexture2D();
bitmapTexture2D.flipY = true;
await bitmapTexture2D.load('https://cdn.orillusion.com/images/webgpu.png');

// create image node
let imageQuad = new Object3D();
viewPanel.addChild(imageQuad);
// create UIImage component
let image: UIImage = imageQuad.addComponent(UIImage);
// set image size
image.uiTransform.resize(320, 320);
// set image source
image.sprite = makeAloneSprite('webgpu', bitmapTexture2D);

let GUIHelp = new dat.GUI();
let f = GUIHelp.addFolder('GUI Space');
let params = {
    ViewSpace: () => {
        viewPanel.addChild(imageQuad);
    },
    WorldSpace: () => {
        worldPanel.addChild(imageQuad);
    }
};
f.add(params, 'ViewSpace');
f.add(params, 'WorldSpace');
f.open();

UICanvas

GUI components also need canvas for drawing. Each View3D in the engine has a built-in array of Canvas. We can activate the UICanvas object by specifying enableUICanvas:

ts
let view = new View3D()
...
let canvas:UICanvas = view.enableUICanvas();

By default, we only need one UICanvas. If we need multiple canvas drawings, we can activate multiple UICanvas by setting different index, which are independent of each other:

ts
let canvas0:UICanvas = view.enableUICanvas(0);
let canvas1:UICanvas = view.enableUICanvas(1);
let canvas2:UICanvas = view.enableUICanvas(2);
//...

This following example shows the performance of multiple UICanvas coexistence:

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

<
ts
import { Engine3D, Object3D, UIImage, ImageType, Color, UIPanel, ViewPanel, Scene3D, Vector2, UITextField, UIShadow, AtmosphericComponent, Camera3D, HoverCameraController, View3D } from '@orillusion/core';

class Sample_UIMultiCanvas {
    async run() {
        Engine3D.setting.shadow.autoUpdate = true;

        // initializa engine
        await Engine3D.init();
        // load fnt
        await Engine3D.res.loadFont('https://cdn.orillusion.com/fnt/0.fnt');
        // create new scene as root node
        let scene3D: Scene3D = new Scene3D();
        scene3D.addComponent(AtmosphericComponent);
        // create camera
        let cameraObj: Object3D = new Object3D();
        let camera = cameraObj.addComponent(Camera3D);
        // adjust camera view
        camera.perspective(60, Engine3D.aspect, 1, 5000.0);
        // set camera controller
        let controller = cameraObj.addComponent(HoverCameraController);
        controller.setCamera(0, -20, 50);
        // add camera node
        scene3D.addChild(cameraObj);

        let view = new View3D();
        view.scene = scene3D;
        view.camera = camera;
        Engine3D.startRenderView(view);

        let total: number = 4;
        for (let i = 0; i < total; i++) {
            let size: Vector2 = new Vector2();
            size.x = 500 - i * 100;
            size.y = 400 - i * 100;
            this.createPanel(scene3D, i, size);
        }
    }

    private createPanel(scene: Scene3D, index: number, size: Vector2): UIPanel {
        let panelRoot: Object3D = new Object3D();
        // enable ui canvas at index
        let canvas = scene.view.enableUICanvas(index);
        let panel = panelRoot.addComponent(ViewPanel);
        canvas.addChild(panel.object3D);
        // create image
        let obj3D = new Object3D();
        panelRoot.addChild(obj3D);
        let image = obj3D.addComponent(UIImage);
        image.isShadowless = true;
        image.imageType = ImageType.Sliced;
        image.uiTransform.resize(size.x, size.y);
        image.color = Color.random();

        //text
        let text = obj3D.addComponent(UITextField);
        text.text = 'Canvas index: ' + index;
        text.fontSize = 24;

        //shadow
        let shadow = obj3D.addComponent(UIShadow);
        shadow.shadowOffset.multiplyScaler(0.4);
        return panel;
    }
}

new Sample_UIMultiCanvas().run();

UIPanel

The panel UIPanel is used to carry the specific GUI component rendering, and needs to be added to the UICanvas;

ts
let panelObj = new Object3D();
let panel:UIPanel = panelObj.addComponent(ViewPanel) // 创建一个屏幕空间面板组件 Create a screen space panel component
let canvas:UICanvas = view.enableUICanvas(); // 启用默认的 UICanvas Enable the default UICanvas
canvas.addChild(panel.object3D); // 添加面板 Add panel

Each UIPanel can be regarded as the root container of the GUI component. Other types of GUI components can be added in the UIPanel:

ts
// Create a UIImage component
let imageQuad = new Object3D();
let image:UIImage = imageQuad.addComponent(UIImage);
// Create a UIPanel
let panel:UIPanel = new Object3D().addComponent(ViewPanel); // 创建一个屏幕空间面板组件 Create a screen space panel component
// Add the Object3D of UIImage to the Object3D of UIPanel
panel.object3D.addChild(imageQuad);

Rendering Order

In the same UICanvas, it is allowed to have multiple ViewPanel or WorldPanel coexist, and their rendering hierarchy meets the following rules:

  1. ViewPanel will always be displayed above WorldPanel.
  2. The drawing priority of ViewPanel is controlled by the property panelOrder. Under the same panelOrder, the order of Object3D mounted in the scene tree is subject to.
  3. The drawing priority of WorldPanel is controlled by the property panelOrder. Under the same panelOrder, UIPanel can be automatically sorted according to the distance of the camera by needSortOnCameraZ.
ts
let panel1 = new Object3D().addComponent(ViewPanel);
let panel2 = new Object3D().addComponent(ViewPanel);
let panel3 = new Object3D().addComponent(WorldPanel);
let panel4 = new Object3D().addComponent(WorldPanel);

// Manually set panelOrder, panel2 covers panel1
panel1.panelOrder = 1
panel2.panelOrder = 2

// ViewPanel panel1/2 always covers WorldPanel panel3/4
panel3.panelOrder = 3
panel4.panelOrder = 4 // panel4 covers panel3 first

// If panelOrder is the same, sort automatically according to the camera position
panel3.panelOrder = panel4.panelOrder = 3
panel3.needSortOnCameraZ = true;
panel4.needSortOnCameraZ = true;

WorldPanel

WorldPanel component has more properties and functions than ViewPanel:

Camera Lock

We can control the rendering angle of the panel by setting the billboard property of the panel:

ts
let panel = new Object3D().addComponent(WorldPanel);
panel.billboard = BillboardType.None;           // Default view, keep the rendering angle of the object itself
panel.billboard = BillboardType.BillboardY;     // Lock the Y axis, the XZ plane of the panel always faces the camera direction
panel.billboard = BillboardType.BillboardXYZ;   // Always face the camera

Depth Test

Set whether the panel participates in depth sorting:

ts
let panel = new Object3D().addComponent(WorldPanel);
panel.depthTest = true;      // Participate in depth sorting, get occlusion relationship
panel.depthTest = false;     // Do not participate in depth sorting, always float on the surface of all objects

Cull Mode

Similar to Material Culling, we can also set the cullMode of the UIPanel rendering material ball to switch the culling method:

ts
let panel = new Object3D().addComponent(WorldPanel);
panel.cullMode = GPUCullMode.none; // Both sides are displayed
panel.cullMode = GPUCullMode.front; // Front culling, back display
panel.cullMode = GPUCullMode.back; // Default back culling, front display

The following example focuses on the spatial relationship between the panels and the rendering characteristics of WorldPanel:

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

<
ts
import { Engine3D, Object3DUtil, Object3D, UIImage, ImageType, Color, WorldPanel, UIPanel, GUICanvas, BillboardType, AtmosphericComponent, Camera3D, HoverCameraController, Scene3D, View3D, GPUCullMode, ViewPanel } from '@orillusion/core';
import * as dat from 'dat.gui';

class Sample_UIPanelOrder {
    GUIHelp: dat.GUI;
    async run() {
        // initializa engine
        await Engine3D.init();
        // create new scene as root node
        let scene3D: Scene3D = new Scene3D();
        scene3D.addComponent(AtmosphericComponent);
        // create camera
        let cameraObj: Object3D = new Object3D();
        let camera = cameraObj.addComponent(Camera3D);
        // adjust camera view
        camera.perspective(60, Engine3D.aspect, 1, 5000.0);
        // set camera controller
        let controller = cameraObj.addComponent(HoverCameraController);
        controller.setCamera(0, -10, 100);
        // add camera node
        scene3D.addChild(cameraObj);

        let view = new View3D();
        view.scene = scene3D;
        view.camera = camera;
        Engine3D.startRenderView(view);

        // create floor
        let floor = Object3DUtil.GetSingleCube(100, 2, 50, 0.5, 0.5, 0.5);
        scene3D.addChild(floor);
        floor.y = -40;

        // enable ui canvas at index 0
        let canvas = scene3D.view.enableUICanvas();
        //create UI root
        let panelRoot: Object3D = new Object3D();
        panelRoot.name = 'WorldPanel red';
        panelRoot.scaleX = panelRoot.scaleY = panelRoot.scaleZ = 0.1;

        let panelRoot2: Object3D = new Object3D();
        panelRoot2.name = 'WorldPanel blue';
        panelRoot2.z = 20;
        panelRoot2.y = -10;
        panelRoot2.x = -10;
        panelRoot2.scaleX = panelRoot2.scaleY = panelRoot2.scaleZ = 0.1;

        let panelRoot3: Object3D = new Object3D();
        panelRoot3.name = 'ViewPanel Green';

        this.GUIHelp = new dat.GUI();
        this.createPanel(panelRoot, canvas, new Color(1.0, 0, 0.0, 0.8), 'world');
        this.createPanel(panelRoot2, canvas, new Color(0, 0, 1, 0.8), 'world');
        this.createPanel(panelRoot3, canvas, new Color(0, 1, 0, 0.5), 'view');
    }

    private createPanel(panelRoot: Object3D, canvas: GUICanvas, color: Color, type: string) {
        let f = this.GUIHelp.addFolder(panelRoot.name);
        if (type === 'world') {
            let panel = panelRoot.addComponent(WorldPanel);
            f.add(panel, 'panelOrder', 0, 10, 1);
            panel.billboard = BillboardType.BillboardXYZ;
            panel.needSortOnCameraZ = true;
            f.add(panel, 'needSortOnCameraZ');
            f.add({ cullMode: GPUCullMode.none }, 'cullMode', {
                none: GPUCullMode.none,
                front: GPUCullMode.front,
                back: GPUCullMode.back
            }).onChange((v) => {
                panel.cullMode = v;
            });
            f.add({ billboard: panel.billboard }, 'billboard', {
                None: BillboardType.None,
                Y: BillboardType.BillboardY,
                XYZ: BillboardType.BillboardXYZ
            }).onChange((v) => {
                panel.billboard = v;
            });
            f.add(panel, 'depthTest');
        } else {
            let panel = panelRoot.addComponent(ViewPanel);
            f.add(panel, 'panelOrder', 0, 10, 1);
            canvas;
        }
        f.open();

        // create a UIImage
        let obj3D = new Object3D();
        panelRoot.addChild(obj3D);
        let image = obj3D.addComponent(UIImage);
        image.imageType = ImageType.Sliced;
        image.uiTransform.resize(400, 300);
        image.color = color;

        canvas.addChild(panelRoot);
    }
}

new Sample_UIPanelOrder().run();

Released under the MIT License