Babylon.js 中的每个形状都是由三角形或小面的网格构建而成,如题图所示。
NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割
每个面都由三个顶点组成,每个顶点都分配有数据,这些数据不仅会影响面的位置,还会影响其颜色、纹理及其照明方式。 应用着色器将这些数据转换为可视网格的复杂过程全部由Babylon.js 执行。
1、位置和索引
创建具有两个面的网格,一个面的顶点位于 (-5, 2, -3)、(-7, -2, -3)、(-3, -2, -3),另一个面的顶点位于 (5) , 2, 3), (7, -2, 3), (3, -2, 3),要求每个顶点都有唯一的索引。 索引应从 0 开始并连续增加。
index | position |
---|---|
0 | (-5, 2, -3) |
1 | (-7, -2, -3) |
2 | (-3, -2, -3) |
3 | (5, 2, 3) |
4 | (7, -2, 3) |
5 | (3, -2, 3) |
请注意,分配索引时顺序并不重要。
位置数据存储在数字数组中。 索引为 0 的顶点的 x 坐标放置在 array[0] 中,y 坐标放置在 array[1] 中,z 坐标放置在 array[2] 中。 一般来说,索引为 i 的顶点的 x 坐标位于 array[3i],y 位于 array[3i + 1],z 位于 array[3i +2]。
形成面的索引以三元组的形式放置在一起,如上例中的 (0, 1, 2) 和 (3, 4, 5)。 索引数据也存储在数字数组中,每个三元组保存在一起。
在上面的示例中,位置数组为 [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3],索引数组为 [0, 1, 2, 3, 4, 5]。
JavaScript代码如下:
var customMesh = new BABYLON.Mesh("custom", scene);
var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
var indices = [0, 1, 2, 3, 4, 5];
var vertexData = new BABYLON.VertexData();
vertexData.positions = positions;
vertexData.indices = indices;
vertexData.applyToMesh(customMesh);
Playground演示
2、法线
通常,平面的法线是与平面成直角的向量,对于本例来说,这是正确的。 BabylonJS 将计算一个面的法线,对于不与另一个面共享任何顶点的独立面,法线将是数学法线。 有关法线如何影响光照的更多信息,请参阅法线。
- 法线计算
使用 ComputeNormal
方法在 vertexData 对象上计算法线,该方法将位置、索引和法线的数组作为参数。
代码补充:
var customMesh = new BABYLON.Mesh("custom", scene);
var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
var indices = [0, 1, 2, 3, 4, 5];
//Empty array to contain calculated values or normals added
var normals = [];
//Calculations of normals added
BABYLON.VertexData.ComputeNormals(positions, indices, normals);
var vertexData = new BABYLON.VertexData();
vertexData.positions = positions;
vertexData.indices = indices;
vertexData.normals = normals; //Assignment of normal to vertexData added
vertexData.applyToMesh(customMesh);
注意:要使自定义网格体可更新,需要在将网格体应用于顶点数据时添加值为 true 的第二个参数。
vertexData.applyToMesh(customMesh, true);
假设数组法线 = [ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1]。
法线存储在数字数组中,索引 i 引用向量(法线[3i]、法线[3i + 1]、法线[3i + 2]),因此:
index | normal |
---|---|
0 | (0, 0, -1) |
1 | (0, 0, -1) |
2 | (0, 0, -1) |
3 | (0, 0, 1) |
4 | (0, 0, 1) |
5 | (0, 0, 1) |
- 法线方向
显然,每个面的法线都指向相反的方向。 它们都指向远离原点的方向。
从原点看向面 3、4、5,则面周围的索引数组 3、4、5 中的顺序是顺时针方向。
从原点向面 0, 1, 2 观察,则面周围的索引数组 0, 1, 2 中的顺序也是顺时针的。
Playground演示
反转操场中一组或两组面索引的顺序将显示法线如何改变方向。
- 对灯光的影响
距相机最近的面现在为白色,距相机最远的面为黑色。 这是因为法线的添加会影响面上光线的使用。
场景中的光线沿 z 轴正方向传播。
var light = new BABYLON.DirectionalLight("direct", new BABYLON.Vector3(0, 0, 1), scene);
以与法线方向相反的方向传播的白光被反射回来,并且小面被视为白色,而以与法线相同的方向传播的白光被吸收,并且小面被视为黑色。
- 能见度
然后去除线框效果:
相机朝 z 轴正方向看
黑色面看不到。
相机朝负 z 方向看
白色面看不到。
为什么是这样? 每个面有两个面; 法线所指向的面是正面,另一个面是背面。 默认情况下Babylon.js 不会渲染背面。
因此,许多绘制实体的网格体的面不会有可见的背面。
要绘制网格体的背面,请将应用于网格体的材质的 backFaceCulling 设置为 false。
mat.backFaceCulling = false;
注释下面的第 41 行可以看到背面剔除的情况。
Playground演示
3、颜色
为自定义网格指定颜色的最简单方法是将标准材质应用于网格,然后让 Babylon.js 完成所有工作。 但是,可以为顶点数据中的面设置颜色。 有关构建网格时使用的面的排列如何影响颜色显示方式的信息,请参阅将材质应用于面。
每个顶点的颜色以四个一组的形式放置在一个数组中,顺序为红色、绿色、蓝色和透明度透明度。 对于将面 0、1、2 着色为红色,将面 3、4、5 着色为绿色,每个面上的每个顶点都被赋予相同的颜色。
index | color |
---|---|
0 | (1, 0, 0, 1) |
1 | (1, 0, 0, 1) |
2 | (1, 0, 0, 1) |
3 | (0, 1, 0, 1) |
4 | (0, 1, 0, 1) |
5 | (0, 1, 0, 1) |
数组为 [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 ,1]。
添加到代码中:
var customMesh = new BABYLON.Mesh("custom", scene);
var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
var indices = [0, 1, 2, 3, 4, 5];
var colors = [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1]; //color array added
var normals = [];
var vertexData = new BABYLON.VertexData();
BABYLON.VertexData.ComputeNormals(positions, indices, normals);
vertexData.positions = positions;
vertexData.indices = indices;
vertexData.colors = colors; //Assignment of colors to vertexData
vertexData.normals = normals; //Assignment of normal to vertexData added
vertexData.applyToMesh(customMesh);
在Playground中,尝试将红色面上的顶点设置为不同颜色,看看会发生什么。
Playground注意事项
- 由于不再使用材质,因此无法设置 backFaceCulling,因此必须旋转相机才能看到远处的面。 无论应用什么颜色,远端面都将保持黑色,因为所有光仍然被该面吸收。
- 当场景开始时,相机几乎完全面对小面并指向光线传播的方向。 当相机处于此位置时,大部分白光作为高光反射回相机,并且刻面看起来几乎是白色的。
- 当相机围绕小平面旋转时,随着高光效果的消失,小平面将从白色变为红色。
- 为了获得更受控制的光照效果,请使用材质以及设置顶点颜色,或者代替设置顶点颜色。
- 添加一盏方向与当前灯方向相反的灯将照亮两侧。
4、纹理
最简单的方法是仅使用材质并让 Babylon.js 将给定图像应用为纹理。 但是,如果希望更多地控制如何将纹理应用于构面,那么你需要创建并设置 uv 数组。
将任何要用作纹理的图像视为在图像的底部和左侧设置一对轴; 分别为 u 轴和 v 轴。 (u、v 作为 x 和 y 用于位置轴)。 原点是图像的左下角,顶部位于 v = 1 处,右侧边缘位于 u = 1 处,如下图所示。
为了简单起见,下面仅使用构面 0、1、2。
面的每个顶点都分配有图像中的一个 uv 坐标对。
index | color |
---|---|
0 | (0, 1) |
1 | (0, 0) |
2 | (1, 0) |
形成uv数组[0,1,0,0,1,0];。
使用以下代码:
var customMesh = new BABYLON.Mesh("custom", scene);
var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3];
var indices = [0, 1, 2];
var uvs = [0, 1, 0, 0, 1, 0];
var normals = [];
BABYLON.VertexData.ComputeNormals(positions, indices, normals);
var vertexData = new BABYLON.VertexData();
vertexData.positions = positions;
vertexData.indices = indices;
vertexData.normals = normals;
vertexData.uvs = uvs;
vertexData.applyToMesh(customMesh);
结果如下:
请注意,图像是倾斜的,因为三角形面的形状与图像上的形状不匹配。
在适当的点添加这些行:
var colors = [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1];
var vertexData.colors = colors;
结果如下:
在Playground中,单击“下一步”将使你循环浏览 uv 坐标的各种值。
关于Playground的注意事项:上述Playground的摄像头已被禁用。 在纹理图像上显示 uv 值和相对索引。 后续这个Playground可能会有所改善。 你将了解如何在面上实现纹理的反射和旋转。 然而,对于网格来说,当尝试在网格上实现特定的纹理映射时,必须考虑面的排列。
- 计算 UV
计算自定义网格的 uv 显然取决于网格的形状以及你想要将纹理的哪些部分投影到网格的哪个位置。 这是一个相对简单的网格的示例,它基本上是一个带有几个突起的平坦表面。
这个 Playground 将左下角视为与纹理图像的左下角匹配,并从每个顶点的 x、z 位置计算 uv 值,作为 (x, z) 距左下角的分数距离。
一般来说,如果左下角位于 (a, b) 且网格的边界宽度和高度分别为 w 和 h,则对于每个 (x, z):
u = (x - a) / w and v = (z - b) / h
原文链接:Babylon.js程序化建模 - BimAnt