精灵 Sprite
精灵(Sprite)是引擎中用于在 3D 场景里绘制 2D 贴图四边形 的渲染组件,常用于标签、图标、地标(POI)、血条、广告牌、特效贴片等。
版本说明
精灵系统在 @orillusion/core 0.9.0 引入,取代了早期版本的 GUI 组件体系。早期的 UIPanel / UIImage / UITextField / UIButton 等 GUI 组件已移除,相关需求统一通过 SpriteRenderer(配合 公告板 等)实现。
基本用法
给一个 Object3D 添加 SpriteRenderer 组件,设置贴图即可在其所在位置绘制一张面片:
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);
// 加载贴图
const tex = new BitmapTexture2D();
tex.flipY = true;
await tex.load('textures/logo.png');
// 创建精灵
const spriteObj = new Object3D();
const sprite = spriteObj.addComponent(SpriteRenderer);
sprite.texture = tex;
scene.addChild(spriteObj);
// 注意:部分属性需在 addChild 之后设置(addChild 会触发材质初始化)
sprite.size = new Vector2(2, 2); // 面片世界尺寸(米)常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
texture | Texture | 精灵贴图,可用 sprite.texture = tex 或 sprite.setTexture(tex) 设置 |
size | Vector2 | 面片在世界空间的宽高(米) |
pivot | Vector2 | 锚点(0~1)。(0.5, 0.5) 居中,(0.5, 0) 底边中点(标签常用) |
color | Color | 叠加颜色 / 透明度(与贴图相乘) |
cornerRadius | number | 圆角半径(世界单位),基于 SDF 圆角,0 为直角 |
uvRect | Vector4 | 采样的 UV 子区域 (x, y, w, h),用于图集 |
renderOrder | number | 透明排序顺序,数值大的后绘制(叠在上层) |
distanceInvariantSize | boolean | 是否保持屏幕尺寸恒定,详见距离恒定 |
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;渲染顺序
多个精灵重叠时,可用 renderOrder 控制透明桶内的绘制先后(数值越大越靠上层):
ts
cardA.renderOrder = 3000;
cardB.renderOrder = 3001; // 叠在 A 之上
cardC.renderOrder = 3002; // 最上层示例
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();
