X-visual应用程序基本结构

Note

搭建并运行x-visual examples后有更助于阅读下文内容细节。

x-visual是一个ECS WebGl渲染程序javascript框架。应用程序需要遵循ECS (Entity, Component, System) 设计思想才能与x-visual步调一致,提高开发效率。

1. 引用x-visual

x-visual可以用npm管理依赖包,也可以plain javascript方式引用。详情参见example readme.

2. Hello XWorld

以下是一个最简单的应用程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** Example: Hello XWorld
 */

import * as xv from 'x-visual'
import Cube from './hellocube'

/** Hollow XWorld Application.
 * add the user implemented system, Cube, into xworld, then show it.
 * @class
 */
export class App {
	constructor(canv) {
		var c = document.getElementById(canv);
		const xworld = new xv.XWorld(c, window, {
			camera: {far: 10000} // default 5000
		});
		var ecs = xworld.xecs;

		xworld.addSystem('hello', // any group name as you like
			new Cube(ecs, {xscene: xworld.xscene}));
		xworld.startUpdate();
	}
}

主程序创建了一个xworld,作为渲染3D空间,然后添加定义的立方体,之后调用xworld.startUpdate()开始反复渲染更新场景。

基于x-visual的应用程序主入口与以上程序片段一致。应用程序的业务处理由各种继承的System来实现。比如下文中的Cube类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import * as xv from 'x-visual'
import * as THREE from 'three'

/**
 * Subclass for rendering data objects
 * @class
 */
export default class Cube extends xv.XSys {
    constructor(ecs, options) {
        super(ecs);
        this.ecs = ecs;

        this.logcnt = 0;

        // create a cube with options
        if (ecs) {
            var cube = ecs.createEntity({
                id: 'cube0',
                Obj3: { geom: xv.XComponent.Obj3Type.BOX,
                        box: [200, 120, 80] },    // geometry parameters, for BOX, it's bounding box
                Visual: {vtype: xv.AssetType.mesh,
                         asset: '../../assets/rgb2x2.png' }
            });
        }
    }

    update(tick, entities) {
        if (this.logcnt < 2) {
            this.logcnt += 1;
            console.log('cube.update(): ', tick, entities)
        }

        for (const e of entities) {
             if (e.flag > 0) {
                // handling command like start an animation here
                this.cmd = x.xview.cmds[0].cmd;
            }
            else this.cmd = undefined;
        }
    }
}

Cube.query = {
    iffall: ['Visual']
};

在Cube中定义了一个立方体, zh: and id, Obj3, Visual, update(), query...

zh: All examples are using Webpack for transpiling.

npm i
webpack

zh: If everything goes well, open the examples/cube/index.html and it will show a cube.

../_images/001-hellocube.png

3. 应用程序基本结构

基于x-visual的应用程序包括如下部分:

  • 主程序模板

    包括创建XWorld、添加Entity和启动渲染循环等代码。

  • Entity定义

    Entity由若干Component构成。实际是一组数据和System动作规则。

  • System实现

    继承ECS.XSys基础类,实现用户处理逻辑。

    用户处理逻辑在这里应该只处理与渲染有关的工作,包括用户输入响应、数据展示方式处理等。更多复杂 逻辑处理应当推到后台处理。

4. 框架基本功能范围

x-visual封装了Three.js渲染引擎,全部包装在Thrender system中,是ECS处理的最后环节。 (之后可能扩展post effect system)

在Thrender处理渲染之前,所有的数据展示约束、tween动画处理已经分解到不同的子系统中处理完成, 为 最后渲染做好了准备。x-visual为这一系列处理提供了一个基本结构,包括一个MVC模式的view结构封装, 若干个system来处理视效配置、动画脚本到渲染对象的分解转换。

具体功能包括:

  • 全局静态Asset管理

  • 用户输入映射

    目前版本只考虑了键盘鼠标事件。用户输入被翻译成UserCmd component,保存在一个特殊的Entity 管理,entity.id = ‘xview’. x-visual实现了一个利用渲染结果拾取场景模型的子系统,可以拾 取透明材质后面的模型对象。拾取对象放在Entity包含的Pickable.pickId中,(picktick = update-tick)。

Attention

This is deprecated, docs is coming soon.

  • GLTF载入

  • 初等几何体创建展示准备

    如立方体、球体、环等。

  • 静态动画脚本解释

    可解释的动画类型由AnimType定义。

  • tween动画驱动

  • 基于Three.js渲染

5. 模型空间属性动画

包括AnimType.ROTATXYZ等。

示例:

ModelSeqs: { script:
    [[{ mtype: xv.XComponent.AnimType.ROTATEX,
        paras: {start: 0,        // auto start
                duration: 1,     // seconds
                deg: [0, 45],    // from, to
                ease: undefined} // default linear
      },
      { mtype: xv.XComponent.AnimType.OBJ3ROTAXIS,
        paras: {start: Infinity, // follow the first
                duration: 3.5,   // seconds
                axis: [0, 1, 0],
                deg: [0, 90],    // from, to
                ease: xv.XEasing.Elastic.InOut}
      }
    ]]
 }

上例中,模型将被xtweener驱动绕X轴旋转45°, 动画时长1秒。

see test/html/tween-rot.html

5.1 仿射变换(Affine Transformation)

线性变换有一类特殊的变换 - Affine Transformation, 三维空间模型的常见变换 可以分解为基本变换。Affine Transformation的组合可以形成新的空间动画,如Orbit。

关于Affine Transformation的介绍较多,如`Geometry Operations-Affine <https://homepages.inf.ed.ac.uk/rbf/HIPR2/affine.htm>`_

6. shader uniform动画

AnimType.UNIFORMS

示例: see test/html/morph/lerp-model.html

这段首先定义了两个用于做位置参照的模型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
                box: [200, 120, 80],
                // mesh is inited by thrender, can be ignored here - MorphSwitch's target
                mesh: undefined,
                uniforms: {opacity: 0.1}},
        Visual:{vtype: xv.AssetType.mesh,
                asset: '../../assets/tex/byr0.png' },
        ModelSeqs: {
            script: [[{
                mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: 0,            // auto start
                        duration: 1.7,       // seconds
                        alpha: [0.95, 0.05], // from, to
                        ease: xv.XEasing.Elastic.Elastic},
                startWith:[{entity: 'points',
                            seqx: 0,        // index of the fade-in
                            start: 0.0}] }],
             [{ mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: Infinity,
                        alpha: [0.05, 0.95],
                        ease: undefined}, }] ] },
        CmpTweens: {}
    };
    xworld.addEntities(ent1);

    const ent2 = {
        id: 'entity2',
        Obj3: { geom: xv.XComponent.Obj3Type.BOX,
                box: [260, 40, 200],
                mesh: undefined,
                uniforms: {opacity: 0.2}},
        Visual:{vtype: xv.AssetType.mesh,
                asset: '../../assets/city/textures/World_ap_baseColor.jpeg' },
        ModelSeqs: {
            script: [[{
                mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: Infinity,        // follow points[0][1]
                        duration: 1.2,          // seconds
                        alpha: [0.95, 0.05],    // from, to
                        ease: xv.XEasing.Elastic.In},
                }],
             [{ mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: Infinity,
                        duration: 3.2,          // seconds
                        alpha: [0.05, 0.95],
                        ease: undefined }}]
            ]},
        CmpTweens: {}
    };
    xworld.addEntities(ent2);

    // pinned line: 73-115 tests/tests-morph.rst

然后定义了若干顶点(points / vertices),并且控制在这两个模型对应顶点间移动。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
        id: 'points',
        Obj3: { geom: xv.XComponent.Obj3Type.NA, // use the geometry of entity1
                box: [200, 2, 1],
                mesh: undefined,                 // THREE.Points
                invisible: false },              // It's visible, but alpha 0?
        Visual:{vtype: xv.AssetType.refPoint,
                asset: 'entity1',
                shader: xv.XComponent.ShaderFlag.randomParticles,
                paras: {vert_scale: '120.0',
                        a_dest: 'entity2',      // entity2.Obj3.mesh
                        // u_tex: 'tex/spark1.png',
                        u_tex: '../../assets/tex/crosstar.png',
                        a_noise: false}},
        ModelSeqs: { script: [[
              { mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: Infinity,        // triggered by entity1
                        duration: 1.2,          // seconds
                        alpha: [0.06, 0.9],     // from, to
                        ease: xv.XEasing.Elastic.In} },
              // both UNIFORMS and U_MORPHi should works for ShaderFlag.randomParticles
              // { mtype: xv.XComponent.AnimType.UNIFORMS,
              { mtype: xv.XComponent.AnimType.U_MORPHi,
                paras: {start: Infinity,        // follow previous
                        duration: 1.2,          // seconds
                        uniforms: { u_morph: [0, 1],
                                    u_alpha: [0.1, 0.9] }},
                followBy: [{entity: 'entity2',
                            seqx: 1,            // index of the fade-in
                            start: 0.3}] },
              { mtype: xv.XComponent.AnimType.U_ALPHA,
                paras: {start: Infinity,        // triggered by entity1
                        duration: 1.2,          // seconds
                        alpha: [0.9, 0.41],     // from, to
                        ease: xv.XEasing.Elastic.In} }

这个动画脚本将使Points的位置在两个模型的顶点位置间移动。因为u_morph设置了两个模型顶点的权重。

7. Particles动画

AnimType.U_MORPHi

示例: see test/html/voxel-morph-particles.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
        ModelSeqs: { script: [[
              { mtype: xv.XComponent.AnimType.U_MORPHi,
                paras: {start: 0,
                        duration: 3.401,
                        uniforms: { u_morph0: [0, 0.4],
                                    u_morph2: [0, 0],
                                    a_noise: [0, 3] },
                        ease: undefined} },
              { mtype: xv.XComponent.AnimType.U_MORPHi,
                paras: {start: 1.2,         // ignored
                        duration: 0.202,
                        uniforms: { u_morph0: [0.4, 1],
                                    u_alpha: [1.0, 0.1],
                                    a_noise: [3, 0.02] },
                        ease: undefined} },
              { mtype: xv.XComponent.AnimType.U_MORPHi,
                paras: {start: 0,
                        duration: 0.203,
                        uniforms: { u_morph0: [0, 0],    // clear 0, FIXME should not useful now
                                    u_morph1: [0, 1],
                                    u_alpha: [0.1, 0.5],
                                    a_noise: [0.2, 0.02] },
                         ease: undefined} },
              { mtype: xv.XComponent.AnimType.U_MORPHi,
                paras: {start: 0,
                        duration: 0.204,
                        uniforms: { u_morph1: [0, 0],
                                    u_morph2: [0, 1],
                                    u_alpha: [0.5, 1.0],
                                    a_noise: [0.2, 0.02] },
                        ease: undefined },
                followBy: [{entity: 'points',// repeat

这个动画脚本被解释为创建等距分布的顶点,并使顶点在不同目标位置间取权重。同时还不断更新颜色透明度。

8. 示例

see test/html

TODO docs ...

  • 载入GLTF模型
  • 渲染HTML页面材质
  • 空间动画
  • shader + uniform动画