用户界面(GPU GUI)
Orillusion
提供了高性能的用户界面(GUI)组件供开发者使用。 通过合理搭配使用 GUI 组件,即可在项目中灵活展示 2D/3D 的 GUI 内容。本章我们先了解几个 GUI 的基本概念:
GUI 空间模式
目前 GUI
支持两种模式渲染 ViewSpace
和 WorldSpace
:
- ViewSpace 模式:在这种模式下,GUI 组件被渲染在屏幕空间中,不随相机的视角更改而变动,也不会与其它物体产生 3D 空间遮挡关系;
- WorldSpace 模式:在这种模式下,GUI 组件可看做三维空间的一块画布,拥有3D属性(旋转、缩放、平移),能够参与深度检测等,实现与其他对象遮挡和被遮挡关系。
ts
import { ViewPanel, WorldPanel } from '@orillusion/core'
// 创建一个面板对象
let panelRoot: Object3D = new Object3D()
// 添加 ViewPanel,设定为 ViewSpace 模式
panelRoot.addComponent(ViewPanel)
// 或 添加 WorldPanel,设定为 WorldSpace 模式
panelRoot.addComponent(WorldPanel)
下面这个例子展示了 ViewPanel
与 WorldPanel
的区别:
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 组件同样需要画布进行绘制,引擎中每个 View3D
中都内置有 Canvas
的数组,我们可以通过指定 enableUICanvas
来主动激活 UICanvas
对象:
ts
let view = new View3D()
...
let canvas:UICanvas = view.enableUICanvas();
默认情况下,我们只需要一个 UICanvas
即可, 如果需要多个画布绘制,我们可以通过设置不同的 index
来激活多个 UICanvas
,它们互相独立:
ts
let canvas0:UICanvas = view.enableUICanvas(0);
let canvas1:UICanvas = view.enableUICanvas(1);
let canvas2:UICanvas = view.enableUICanvas(2);
//...
以下示例展示了多个 UICanvas
共存的表现:
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
面板 UIPanel
用于承载具体的 GUI 组件渲染,需要添加到 UICanvas
中;
ts
let panelObj = new Object3D();
let panel:UIPanel = panelObj.addComponent(ViewPanel) // 创建一个屏幕空间面板组件
let canvas:UICanvas = view.enableUICanvas(); // 启用默认的 UICanvas
canvas.addChild(panel.object3D); // 添加面板
每个 UIPanel
可以视为 GUI 组件的根容器,在 UIPanel
内可以添加其它类型的 GUI 组件:
ts
// 创建一个 UIImage 组件
let imageQuad = new Object3D();
let image:UIImage = imageQuad.addComponent(UIImage);
// 创建一个 UIPanel
let panel:UIPanel = new Object3D().addComponent(ViewPanel); // 创建一个屏幕空间面板组件
// 将 UIImage 的 Object3D 添加到 UIPanel 的 Object3D 中
panel.object3D.addChild(imageQuad);
渲染顺序
在同一个 UICanvas
下,可以允许有多个 ViewPanel
或者 WorldPanel
共存,它们的渲染层级满足以下规则:
ViewPanel
总是会显示在WorldPanel
之上。ViewPanel
之间通过属性panelOrder
控制绘制优先级。相同panelOrder
下,根据它们所挂载的Object3D
在场景树中的顺序为准。WorldPanel
之间通过属性panelOrder
控制绘制优先级。相同panelOrder
下,可以通过needSortOnCameraZ
来让UIPanel
根据相机远近距离自动排序。
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);
// 手动设置 panelOrder, panel2 遮挡 panel1
panel1.panelOrder = 1
panel2.panelOrder = 2
// ViewPanel 的 panel1/2 永远遮挡 WorldPanel 的 panel3/4
panel3.panelOrder = 3
panel4.panelOrder = 4 // panel4 优先遮挡 panel3
// 若 panelOrder 相同,自动根据相机位置排序
panel3.panelOrder = panel4.panelOrder = 3
panel3.needSortOnCameraZ = true;
panel4.needSortOnCameraZ = true;
WorldPanel
WorldPanel
组件相较于 ViewPanel
拥有更多的属性和功能:
相机锁定
我们可以通过设置面板的 billboard
属性来控制面板的渲染角度:
ts
let panel = new Object3D().addComponent(WorldPanel);
panel.billboard = BillboardType.None; //默认视角,保持物体本身的渲染角度
panel.billboard = BillboardType.BillboardY; //锁定Y轴,面板的XZ平面始终朝向相机方向
panel.billboard = BillboardType.BillboardXYZ; //面板始终朝向相机
深度测试
设置面板是否参与深度排序:
ts
let panel = new Object3D().addComponent(WorldPanel);
panel.depthTest = true; //参与深度排序,获得遮挡关系
panel.depthTest = false; //不参与深度排序,始终悬浮于所有物体的表面
剔除模式
跟 材质剔除 类似,我们也可以设置 UIPanel
渲染材质球的 cullMode
来切换剔除方式:
ts
let panel = new Object3D().addComponent(WorldPanel);
panel.cullMode = GPUCullMode.none; // 双面显示
panel.cullMode = GPUCullMode.front; // 前面剔除,背面显示
panel.cullMode = GPUCullMode.back; // 默认背面剔除,前面显示
下面这个例子,集中展示了面板之间的空间关系和 WorldPanel
的渲染特性:
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();