Skip to content

图形绘制

Orillusion 提供了 @orillusion/graphic 扩展包,主要用于对具有实时变化的点、线、面、体的绘制,使用特定的方法创建出一个动态网格,统一管理并参与到引擎渲染管线中去,性能高效、使用方便。

目前提供三个模块来创建高性能图形数据:

  1. Graphic3D: 提供基础的线条制能力,常用于绘制辅助线等。
  2. Graphic3DMesh 渲染器:在同一个渲染器中批量创建出一组 Mesh 克隆体,可以自由定义调整每个克隆体的 TransformTexture 以及 Material,组合出高自由度的图形和动画。
  3. Shape3D 渲染器: 创建复杂的自定义 Shape3D 对象,例如 EllipseShape3DRoundRectShape3DCircleShape3D 等。对于拥有可持续绘制功能的 Shape3D ,比如Path2DShape3DPath3DShape3D,参照了 CanvasPath 中的API设计来实现,让开发者能借鉴和沿用自己熟悉的开发方式进行图形绘制工作。

安装

跟引擎方法一致,我们可以通过 NPMCDN 链接两种方式来引入图形插件:

1. 通过 NPM 包安装

bash
npm install @orillusion/core --save
npm install @orillusion/graphic --save
ts
import { Engine3D } from "@orillusion/core"
import { Graphic3D, Shape3D } from "@orillusion/graphic"

2. 通过 CDN 链接引入

推荐使用 ESModule 构建版本

html
<script type="module">
  import { Engine3D } from "https://unpkg.com/@orillusion/core/dist/orillusion.es.js" 
  import { Graphic3D, Shape3D } from "https://unpkg.com/@orillusion/graphic/dist/graphic.es.js" 
</script>

或通过 <script> 加载构建 UMD 版本,在全局 Orillusion 变量中获取 Shape3D 模块:

html
<script src="https://unpkg.com/@orillusion/core/orillusion.umd.js"></script>
<script src="https://unpkg.com/@orillusion/stats/dist/graphic.umd.js"></script>
<script>
  const { Engine3D, Graphic3D, Shape3D } = Orillusion
</script>

Graphic3D

通过创建一个 Graphic3D 对象统一绘制场景中的图形,目前提供 drawLinesdrawBoxdrawCircle 三种 API 快捷创建不同的线段组合。

基本方法

ts
import {Graphic3D} from '@orillusion/graphic'
// ...

// 创建一个 Graphic3D 对象
let graphic3D = new Graphic3D();
// 添加到场景中
scene.addChild(graphic3D);

// 使用 graphic3D 统一绘制线段
// line - (uid, [start1, end1, start2, end2, ...], color)
graphic3D.drawLines('line', [new Vector3(0, 0, 0), new Vector3(0, 10, 0)], new Color(1, 0, 0));
// box - (uid, center, size, color)
graphic3D.drawBox('box', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color(0, 1, 0));
// circle - (uid, center, radius, segments, up, color)
graphic3D.drawCircle('circle', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color(0, 0, 1));

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

<
ts
import { Object3D, Scene3D, Engine3D, Vector3, Color, AnimationCurve, Keyframe, View3D, AtmosphericComponent, CameraUtil, HoverCameraController, DirectLight, KelvinUtil, MeshRenderer, BoxGeometry, LitMaterial } from '@orillusion/core';
import { Graphic3D, Graphic3DLineRenderer } from '@orillusion/graphic';
import { Stats } from '@orillusion/stats';
import * as dat from 'dat.gui';

class GraphicLine {
    scene: Scene3D;
    view: View3D;
    graphic3D: Graphic3D;

    async run() {
        await Engine3D.init();

        // init Scene3D
        this.scene = new Scene3D();
        this.scene.exposure = 1;
        this.scene.addComponent(Stats);

        // init sky
        let atmosphericSky: AtmosphericComponent;
        atmosphericSky = this.scene.addComponent(AtmosphericComponent);
        atmosphericSky.exposure = 1.0;

        // init Camera3D
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, Engine3D.aspect, 1, 5000);

        // init Camera Controller
        let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
        hoverCtrl.setCamera(-30, -15, 100);

        // init View3D
        let view = new View3D();
        view.scene = this.scene;
        view.camera = camera;

        // add a Graphic3D
        this.graphic3D = new Graphic3D();
        this.scene.addChild(this.graphic3D);

        // create direction light
        let lightObj3D = new Object3D();
        lightObj3D.x = 0;
        lightObj3D.y = 30;
        lightObj3D.z = -40;
        lightObj3D.rotationX = 20;
        lightObj3D.rotationY = 160;
        lightObj3D.rotationZ = 0;

        let light = lightObj3D.addComponent(DirectLight);
        light.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
        light.intensity = 30;

        this.scene.addChild(light.object3D);

        // relative light to sky
        atmosphericSky.relativeTransform = light.transform;
        Engine3D.startRenderView(view);
        this.view = view;
        await this.initScene();
    }

    async initScene() {
        this.graphic3D.drawLines('line1', [Vector3.ZERO, new Vector3(0, 10, 0)], new Color().hexToRGB(Color.RED));

        let animCurve = new AnimationCurve();
        animCurve.addKeyFrame(new Keyframe(0, 0.5));
        animCurve.addKeyFrame(new Keyframe(0.15, -0.2));
        animCurve.addKeyFrame(new Keyframe(0.22, 0.4));
        animCurve.addKeyFrame(new Keyframe(0.34, 0.2));
        animCurve.addKeyFrame(new Keyframe(0.65, -0.2));
        animCurve.addKeyFrame(new Keyframe(1, 0.9));
        let lines: Vector3[] = [];
        for (let i = 0; i < 100; i++) {
            let y = animCurve.getValue(i / (100 - 1)) * 10;
            lines.push(new Vector3(i, y, 0));
        }
        this.graphic3D.drawLines('line2', lines, new Color().hexToRGB(Color.RED));

        this.graphic3D.drawBox('box1', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color().hexToRGB(Color.GREEN));

        this.graphic3D.drawCircle('Circle1', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color().hexToRGB(Color.GREEN));
        this.graphic3D.drawCircle('Circle2', new Vector3(-15, -5, -5), 5, 15, Vector3.Y_AXIS, new Color().hexToRGB(Color.GREEN));
        this.graphic3D.drawCircle('Circle3', new Vector3(-15, -5, -5), 5, 15, Vector3.Z_AXIS, new Color().hexToRGB(Color.GREEN));

        {
            let obj = new Object3D();
            let mr = obj.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(5, 5, 5);
            mr.material = new LitMaterial();
            this.scene.addChild(obj);
        }
        let gui = new dat.GUI();
        let btn = {'depthTest': true}
        gui.add(btn, 'depthTest').onChange(v=>{
            this.graphic3D.getComponents(Graphic3DLineRenderer).forEach(mr=>{
                mr.materials[0].depthCompare = v ? 'less' : 'always'
            })
        })
    }
}

new GraphicLine().run();

Graphic3DMesh 渲染器

通过 Graphic3DMesh.draw() 快速创建一个 Graphic3DMeshRenderer 实例,该对象可视为多个克隆 Geometry 的集合体,对于这个集合体中每一个对象都可以设置位置和贴图,通过组合达到想要的视觉效果。

参数概览

参数描述
scene在指定Scene3D中创建
geo指定网格数据源
texture贴图列表(使用下标进行索引)
count指定一个渲染器能够支持的克隆集合体的最大数量(选择合适的数值将会提高性能)

TIP

geo 一般输入一个简单的 PlaneGeometry 作为模型源即可,通过不同的贴图来表现不同的外观。理论上你可以传入的任何模型源来创作。例如传入一个 BoxGeometry 类型的模型,即可获得由许多方块组合成的图形,创建像素风格的场景,或者模拟体素的渲染。

  1. 修改 Transform: 对特定下标的(index)单元修改其旋转、缩放、位置。
    取得 Graphic3DMeshRenderer 所属的 object3Ds,使用数组的下标(index)获得对应的 Object3D,对 Object3DTransform 修改即可同步至目标单元。

  2. 修改 Texture: 调用函数 setTextureID,指定修改特定下标(index)单元对应的贴图下标(textureIndex),贴图从 Graphic3DMeshRenderer 初始化参数的texture中获取。

  3. 修改 Material : 在 Graphic3DMeshRenderer 类中开放有一些列命名为类似setTextureID 这样的API。第一个参数为指定设置的目标单元,第二个接口为设置的相关参数。开发者可以通过这样的API,修改修改图形的内容。例如Color、UV、Emissive等数据。

使用示例

ts
import { Object3D, Scene3D, Engine3D, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color, ColorUtil } from "@orillusion/core";
import { Graphic3D, Graphic3DMesh, Graphic3DMeshRenderer } from '@orillusion/graphic';

// 加载贴图列表
let textureArray = [];
textureArray.push(await Engine3D.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);

bitmapTexture2DArray.setTextures(textureArray);

// 使用Plane作为网格克隆数据源
let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS);

// 在当前场景中,使用 plane 作为克隆数据源,创建一个 Graphic3DMeshRenderer 实例,该实例最大支持 100 个克隆对象。
let mr:Graphic3DMeshRenderer = Graphic3DMesh.draw(scene, geometry, bitmapTexture2DArray, 100);

// 修改材质球属性
mr.material.blendMode = BlendMode.ADD;
mr.material.transparent = true;
mr.material.depthWriteEnabled = false;
mr.material.useBillboard = true;

// 拿到每个克隆体单元所对应的Object3D对象,修改该 Object3D 的 Transform 属性,即可同步修改目标克隆体 Transform。
// 同样的操作,放在引擎的主update函数里,则可每帧修改以驱动动画效果。
let parts = mr.object3Ds;
for (let i = 0; i < 100; i++) {
    const element = parts[i];
    // set texture index from textureArray
    mr.setTextureID(i, 0);
    // update transform
    element.transform.x = 1;
    element.transform.scaleX = 1;
    element.transform.rotationX = 0;
    // ...
}

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

<
ts
import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color } from '@orillusion/core';
import { Stats } from '@orillusion/stats';
import { Graphic3DMesh, Graphic3DMeshRenderer } from '@orillusion/graphic';

class Sample_GraphicMesh {
    private scene: Scene3D;
    private parts: Object3D[];
    private width: number;
    private height: number;
    private cafe: number = 47;
    private view: View3D;

    graphicMeshRenderer: Graphic3DMeshRenderer;

    constructor() {}

    async run() {
        Matrix4.maxCount = 500000;
        Matrix4.allocCount = 500000;

        await Engine3D.init({beforeRender: ()=> this.update()});

        Engine3D.setting.render.debug = true;
        Engine3D.setting.shadow.shadowBound = 5;

        this.scene = new Scene3D();
        this.scene.addComponent(Stats);
        let sky = this.scene.addComponent(AtmosphericComponent);
        sky.enable = false;
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, Engine3D.aspect, 1, 5000.0);

        camera.object3D.addComponent(HoverCameraController).setCamera(30, 0, 120);

        this.view = new View3D();
        this.view.scene = this.scene;
        this.view.camera = camera;

        Engine3D.startRenderView(this.view);
        await this.initScene();
    }

    async initScene() {
        let texts:any[] = [];
        texts.push((await Engine3D.res.loadTexture('https://cdn.orillusion.com/textures/128/star_0008.png')) as BitmapTexture2D);

        let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length);
        bitmapTexture2DArray.setTextures(texts);

        let mat = new UnLitTexArrayMaterial();
        mat.baseMap = bitmapTexture2DArray;
        mat.name = 'LitMaterial';
        
        {
            this.width = 15;
            this.height = 15;
            let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS);
            this.graphicMeshRenderer = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height);
            this.parts = this.graphicMeshRenderer.object3Ds;

            this.graphicMeshRenderer.material.blendMode = BlendMode.ADD;
            this.graphicMeshRenderer.material.transparent = true;
            this.graphicMeshRenderer.material.depthWriteEnabled = false;
            this.graphicMeshRenderer.material.useBillboard = true;

            for (let i = 0; i < this.width * this.height; i++) {
                const element = this.parts[i];
                this.graphicMeshRenderer.setTextureID(i, 0);
                element.transform.scaleX = 5.5;
                element.transform.scaleY = 5.5;
                element.transform.scaleZ = 5.5;
            }
        }
    }

    update(){
        if (this.parts) {
            let len = this.parts.length;
            for (let i = 0; i < len; i++) {
                const element = this.parts[i];
                let tmp = this.sphericalFibonacci(i, len);
                tmp.scaleBy(Math.sin(i + Time.frame * 0.01) * this.cafe);
                element.transform.localPosition = tmp;
            }
        }
    }

    public madfrac(A: number, B: number): number {
        return A * B - Math.floor(A * B);
    }

    public sphericalFibonacci(i: number, n: number): Vector3 {
        const PHI = Math.sqrt(5.0) * 0.5 + 0.5;
        let phi = 2.0 * Math.PI * this.madfrac(i, PHI - 1);
        let cosTheta = 1.0 - (2.0 * i + 1.0) * (1.0 / n);
        let sinTheta = Math.sqrt(Math.max(Math.min(1.0 - cosTheta * cosTheta, 1.0), 0.0));

        return new Vector3(Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta);
    }
}

new Sample_GraphicMesh().run();

更多 Graphic3D API 用法请参考 GraphicMesh 示例代码。

Shape3D 渲染器

通过 Shape3DMaker 创建一个 Shape3DRenderer 渲染器,它可以持有和维护一个 Shape3D 数据集。每个Shape3D是被定义好的各种各样的形状,例如 EllipseShape3DRoundRectShape3DCircleShape3D 等等。其中 Path2DShape3DPath3DShape3D 拥有更加丰富的API,可以帮你组合绘制出复杂的图形。

参数描述
name名称,用于标识Shape3DRenderer
scene指定 Shape3DRenderer 放入到哪个 scene 中
textureList贴图列表,使用 index 进行索引
maxNodeCount指定渲染器支持最多 Shape3D 的数量
triangleEachNode指定每个 Shape3D 平均拥有的三角形的数量

渲染器参照 CanvasPath 中的API设计来实现,让开发者能沿用和借鉴自己熟悉的开发方式进行3D绘制工作。渲染器的 2D 绘制部分指的是在 XZ 平面中绘制点、线、面。同时对每个单元仍然可以通过 Transform 独立控制。而在 3D 空间中绘制图形,则需使用 Path3DShape3D 即可开始具有Y轴高程数据的图形绘制。

基础属性

引擎内置了很多基础图形,都继承于 Shape3D 类,主要包含以下属性:

属性名称描述
lineColor绘制线条时加成的颜色
fillColor绘制填充区域时加成的颜色
lineTextureID设定绘制线条时采用的贴图
fillTextureID设定绘制填充区域时采用的贴图
fillRotation设置填充区域,使用贴图的旋转角度
shapeOrder设置每个Shape的层级(消除zFighting,每个 Shape3DRenderer 可以设定 zFighting 最大范围,根据这个范围和Shape3D的数量,得到每个Shape3D拥有的偏移量)
points3D预留外部传入关键点的集合
isClosed图形首尾首尾封闭
fill图形是否填充
lineWidth绘制线条的宽度
lineUVRectUV数据:xy分别对应线条贴图的offset、zw对应贴图数据的缩放
fillUVRectUV数据:xy分别对应填充区域贴图的offset、zw对应贴图数据的缩放
uvSpeedUV数据:xy分别填充区域贴图的uv移动速度;zw对应线条绘制时贴图数据的uv移动速度

内置图形

CanvasPath 的 API 类似,引擎目前提供一下几种 Shape3D 的子类/派生类:

图形名称描述
CircleShape3D圆形、圆弧
CurveShape3D拥有2个锚点控制的贝塞尔曲线
EllipseShape3D椭圆
LineShape3D折线
Path2DShape3D在xz平面绘制线条路径
Path3DShape3D在3D空间内绘制线条路径
QuadraticCurveShape3D拥有1个锚点控制的贝塞尔曲线
RoundRectShape3D矩形、圆角矩形

内置方法

通过 Shape3DMaker 的实例,我们可以调用以下几种方法获得对应的特定图形:

函数名称图形类型
ellipseEllipseShape3D
arcCircleShape3D
lineLineShape3D
quadraticCurveQuadraticCurveShape3D
curveCurveShape3D
path2DPath2DShape3D
path3DPath3DShape3D
rectRoundRectShape3D
roundRectRoundRectShape3D

TIP

所有 2D 图形,例如 path2D 将会忽略 Y 轴数据,图形将被展开在 XZ 平面

除此之外,我们还可以通过 Shape3DRendererShape3D 进行增删改操作:

函数名称描述
createShape指定Shape3D的类型,在渲染器中创建Shape3D实例
removeShape删除一个Shape3D实例
getShapeObject3D通过Shape3D的实例的shapeIndex属性,获得对应的Object3D。供后续修改Transform使用

使用示例

ts
import { Object3D, Scene3D, Engine3D, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color,ColorUtil } from "@orillusion/core";
import { CircleShape3D, EllipseShape3D, Shape3DMaker, Shape3D } from "@orillusion/graphic";

// 加载贴图列表
let textureArray = [];
textureArray.push(await Engine3D.res.loadTexture("path/to/texture.png") as BitmapTexture2D);
let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);
bitmapTexture2DArray.setTextures(textureArray);

//在当前场景中,创建一个Shape3DRenderer实例
maker = Shape3DMaker.makeRenderer(`path`, bitmapTexture2DArray, scene);
maker.renderer.material.doubleSide = true;

//创建一个基于XZ平面的Circle,其半径为5、圆心为(0, 0)
let circle:CircleShape3D = maker.arc(5, 0, 0);
circle.lineWidth = 1; //线条宽度为1
circle.segment = 16; //该圆弧将会使用16条线段拟合
circle.fill = true; //设置是否填充
circle.line = true; //设置是否画线描边
circle.uvSpeed = new Vector4(0, 0, 0, Math.random() - 0.5).multiplyScalar(0.005); //设置UV滚动速度
circle.fillColor = Color.randomRGB(); //设置填充色加成
circle.lineColor = Color.randomRGB(); //设置线条描边色加成

circle.startAngle = 30; //设置圆弧起始角度
circle.endAngle = 240; //设置圆弧结束角度

// 将对circle的控制脚本放在引擎的主循环中,则可驱动动画效果

上述代码展示绘制一个独立圆形/圆弧,采用创建 CircleShape3D 的实例的方法。你也可以通过创建通用 Path2DShape3D 实例,然后调用其 arc() 函数获得。

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

<
ts
import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, LineJoin, Vector4, Object3DUtil, AxisObject } from '@orillusion/core';
import { Stats } from '@orillusion/stats';
import { Shape3DMaker, Shape3D, CircleArcType, CircleShape3D } from '@orillusion/graphic';
import * as dat from 'dat.gui';

/**
 * This example shows how to use Shape2D to draw various different paths on xz plane.
 *
 * @export
 * @class Sample_Shape3DPath2D
 */
class Sample_Shape3DPath2D {
    lightObj3D: Object3D;
    scene: Scene3D;
    view: View3D;

    async run() {
        Matrix4.maxCount = 10000;
        Matrix4.allocCount = 10000;

        await Engine3D.init({ beforeRender: () => this.update() });

        Engine3D.setting.render.debug = true;
        Engine3D.setting.shadow.shadowBound = 5;

        this.scene = new Scene3D();
        this.scene.addComponent(Stats);
        let sky = this.scene.addComponent(AtmosphericComponent);
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, Engine3D.aspect, 1, 5000.0);

        camera.object3D.addComponent(HoverCameraController).setCamera(0, -60, 60);

        this.view = new View3D();
        this.view.scene = this.scene;
        this.view.camera = camera;

        Engine3D.startRenderView(this.view);

        await this.initScene();

        this.scene.addChild(new AxisObject(10, 0.1));

        sky.relativeTransform = this.lightObj3D.transform;
    }

    async initScene() {
        {
            /******** light *******/
            this.lightObj3D = new Object3D();
            this.lightObj3D.rotationX = 21;
            this.lightObj3D.rotationY = 108;
            this.lightObj3D.rotationZ = 10;
            let directLight = this.lightObj3D.addComponent(DirectLight);
            directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            directLight.castShadow = false;
            directLight.intensity = 20;
            this.scene.addChild(this.lightObj3D);
            await this.addNode();
        }
        {
            let floor = Object3DUtil.GetSingleCube(100, 0.1, 100, 0.2, 0.2, 0.2);
            floor.y = -0.2;
            this.scene.addChild(floor);
        }
    }

    private maker: Shape3DMaker;
    private async addNode() {
        let textureArray:any[] = [];
        textureArray.push((await Engine3D.res.loadTexture('https://cdn.orillusion.com/textures/128/vein_0013.png')) as BitmapTexture2D);
        textureArray.push((await Engine3D.res.loadTexture('https://cdn.orillusion.com/textures/128/vein_0014.png')) as BitmapTexture2D);

        let bitmapTexture2DArray = new BitmapTexture2DArray(textureArray[0].width, textureArray[0].height, textureArray.length);
        bitmapTexture2DArray.setTextures(textureArray);

        this.maker = Shape3DMaker.makeRenderer(`path`, bitmapTexture2DArray, this.scene);
        this.maker.renderer.material.doubleSide = true;

        this.createPath();
    }

    private createPath(): Shape3D {
        let circle = this.maker.arc(20, 0, 360, undefined);
        circle.lineWidth = 2;
        circle.segment = 40;
        circle.fill = true;
        circle.line = true;
        circle.isClosed = false;
        circle.lineUVRect.z = 0.5;
        circle.lineUVRect.w = 0.5;
        circle.fillUVRect.z = 0.1;
        circle.fillUVRect.w = 0.1;

        circle.fillTextureID = 0;
        circle.lineTextureID = 1;

        circle.lineColor = Color.random();
        circle.uvSpeed = new Vector4(0, 0, 0, Math.random() - 0.5).multiplyScalar(0.005);

        const GUIHelp = new dat.GUI();
        this.renderCircle(GUIHelp, circle, 5, false);
        return circle;
    }

    update() {}

    renderCircle(GUIHelp: dat.GUI, shape: CircleShape3D, maxSize: number, open: boolean = true, name?: string) {
        name ||= 'Circle3D_' + shape.shapeIndex;
        GUIHelp.addFolder(name);
        GUIHelp.add(shape, 'radius', 0, maxSize, 0.1);
        GUIHelp.add(shape, 'segment', 0, 100, 1);
        GUIHelp.add(shape, 'startAngle', 0, 360, 1);
        GUIHelp.add(shape, 'endAngle', 0, 360, 1);
        let arcType = {};
        arcType['sector'] = CircleArcType.Sector;
        arcType['moon'] = CircleArcType.Moon;
        GUIHelp.add({ arcType: shape.arcType }, 'arcType', arcType).onChange((v) => {
            shape.arcType = Number.parseInt(v);
        });

        this.renderCommonShape3D(GUIHelp, shape, maxSize);
    }

    renderCommonShape3D(GUIHelp: dat.GUI, shape: Shape3D, maxSize: number, uvMin: number = 0.01, uvMax: number = 1.0) {
        GUIHelp.add(shape, 'line');
        GUIHelp.add(shape, 'fill');
        GUIHelp.add(shape, 'isClosed');
        GUIHelp.add(shape, 'lineWidth', 0, maxSize, 0.01);
        GUIHelp.add(shape, 'fillRotation', -Math.PI, Math.PI, 0.01);

        this.renderVec4(GUIHelp, 'FillUVRect.', shape, 'fillUVRect', 0, 10, 0.01);
        this.renderVec4(GUIHelp, 'LineUVRect.', shape, 'lineUVRect', 0, 10, 0.01);
        this.renderVec4(GUIHelp, 'UVSpeed.', shape, 'uvSpeed', -0.01, 0.01, 0.0001);

        GUIHelp.add(shape, 'lineTextureID', 0, 1, 1);
        GUIHelp.add(shape, 'fillTextureID', 0, 1, 1);
    }

    renderVec4(GUIHelp: dat.GUI, label: string, target: Object, key: string, min: number, max: number, step: number = 0.01) {
        let components = ['x', 'y', 'z', 'w'];
        let data = {} as any;
        let vec4: Vector4 = target[key];
        for (let component of components) {
            data[label + component] = vec4[component];
            GUIHelp.add(data, label + component, min, max, step).onChange((v) => {
                vec4[component] = v;
                target[key] = vec4;
            });
        }
    }
}

new Sample_Shape3DPath2D().run();

更多 Shape3D API 用法请参考 Shape3D 示例代码。