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