当前位置:网站首页>Three.JS程序化建模入门
Three.JS程序化建模入门
2022-08-02 10:24:00 【新缸中之脑】
在这个教程中,我们将学习如何在 three.js 中制作自己的自定义程序几何,以及如何利用程序几何来制作吸引人的低多边形地形。
学习本教程需要你具备以下基本技能:
- 基本熟悉three.js应用的结构
- 基础编程知识
- 3d几何的基本知识(顶点,uvs,法线等)
- 任何具有 webgl 兼容网络浏览器的机器
- 关于如何运行 javascript 代码的知识(本地或使用类似jsfiddle的东西)
如果你阅读了我之前的教程,会看到three.js 提供了许多不同的内置基本几何类型,再加上你可以轻松地将自己的3D 模型导入到three.js 中以及为什么需要制作你自己的几何?好吧,一个答案当然是为了好玩!如果你问我这就是你需要的唯一答案。
但是,对于更务实的读者来说,如果你想构建每次运行程序时都独一无二的几何图形,那么生成自己的几何图形也是有益的。或者,如果几何图形需要适应你事先不知道的某些约束,并且使其不适合存储。
1、基本几何体
首先,我们从一个基本Geometry实例开始。在上一个教程中,我们查看了许多不同类型的几何图形,例如SphereGeometry和CylinderGeometry,但出于我们的目的,我们需要一个普通的空Geometry对象。
var geometry = new THREE.Geometry();
var material = new THREE.MeshBasicMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
现在,我们开始制作实际的几何图形。Three.js 至少需要两件东西才能使用Geometry。首先它需要vertices,如你所知,3D 图形是通过光栅化三角形绘制的。所以我们至少需要一个三角形来绘制一些东西(严格来说这不是真的,但就我们的目的而言,这是一个公平的假设)。我们需要 3D 空间中的 3 个点。其次,我们需要告诉 three.js 如何使用 three.js 将这些点连接在一起。Face3需要 3个点并将它们连接在一起以绘制一个三角形。现在让我们来看看。首先,让我们定义一个简单的三角形。
geometry.vertices.push(new THREE.Vector3( 1, -1, 0));
geometry.vertices.push(new THREE.Vector3( 0, 1, 0));
geometry.vertices.push(new THREE.Vector3(-1, -1, 0));
geometry.faces.push(new THREE.Face3(0, 1, 2));
请注意直接访问顶点数组和面数组是多么容易?这是three.js中Geometry类的好处。如果我们使用 BufferGeometry这将更加复杂。但是BufferGeometry速度稍快,因此你面临权衡取舍。
在我们继续之前还有几点。注意我们的面是如何取三个整数的。这些是我们三个顶点中每个顶点的数组索引。我们在事务中定义它们的顺序。在多个面中引用同一个顶点并没有错。事实上,这允许我们重用一些顶点并节省一些空间。让我们看看它现在是如何工作的。
geometry.vertices.push(new THREE.Vector3( 1, -1, 0));
geometry.vertices.push(new THREE.Vector3(-1, 1, 0));
geometry.vertices.push(new THREE.Vector3(-1, -1, 0));
geometry.vertices.push(new THREE.Vector3( 1, 1, 0));
geometry.faces.push(new THREE.Face3(0, 1, 2));
geometry.faces.push(new THREE.Face3(0, 3, 1));
正如在此处看到的,我们现在已经定义了两个三角形,但只添加了一个顶点。这是在不占用顶点数组中太多额外空间的情况下定义对象的好方法。
这是在 three.js 中创建自定义几何图形的基本方法,我们可以以任何我们想要的方式扩展它。可以使用可以设计的任何方法添加顶点和面。但是还有一些有用的考虑因素。为了充分利用 three.js 的特性,我们希望我们的几何体具有法线、顶点颜色和 uv。
2、法线
法线是我们最容易添加的属性。一旦我们定义了一个顶点数组,就可以通过调用computeVertexNormals或computeFlatNormals来来为我们计算法线。让我们看看两者:
geometry.computeVertexNormals();
geometry.normalsNeedUpdate = true;
顶点法线计算在三角形面上插值的每个顶点法线。顶点法线通常用于高保真外观模型。这是因为插值使法线看起来像是在三角形面上发生了变化,这使你可以计算更准确的三角形面上的光照。
geometry.computeFaceNormals();
geometry.normalsNeedUpdate = true;
对于面法线,整个三角形的法线保持不变。当计算照明时,我们将能够非常清楚地看到三角形本身。当做出特定的风格选择来展示三角形本身时,面法线很好。
两者都很容易调用。为了说明差异,让我们稍微弯曲一下我们的四边形,使三角形面向不同的方向,如下所示:
geometry.vertices.push(new THREE.Vector3( 1, -1, 0));
geometry.vertices.push(new THREE.Vector3(-1, 1, 0));
geometry.vertices.push(new THREE.Vector3(-1, -1, -1));
geometry.vertices.push(new THREE.Vector3( 1, 1, -1));
geometry.faces.push(new THREE.Face3(0, 1, 2));
geometry.faces.push(new THREE.Face3(0, 3, 1));
还有一件事。让我们将材质更改为 MeshNormalMaterial,以便我们可以说明法线的样子。
现在我们可以看到,当我们使用顶点法线时,它看起来像这样:
注意远角的颜色不同,而中间的两个顶点颜色相同?
现在让我们看一下带有面法线的弯曲四边形:
每个三角形都突出并具有不同的颜色。
3、UV
与法线密切相关的是 uv。将 2D 纹理映射到 3D 对象时使用 Uv。不幸的是,给定顶点数组,three.js 不会为我们计算 uv。因此,在构建几何体时,我们需要在顶点数组旁边构建一个 uv 数组。
three.js 中的 uv 数组是在Geometry的faceVertexUvs属性下访问的。faceVertexUvs是一个 uv 数组的数组。它们又是长度等于 faces 数组的数组,包含三个 uv,一个对应面中的每个顶点。现在这听起来很复杂,但实际上它非常简单。我们将顶点定义为法线,然后当我们定义我们的面时,我们会多做一步。
geometry.faces.push(new THREE.Face3(0, 1, 2));
var uvs1 = [new THREE.Vector2(1, 0), new THREE.Vector2(0, 1), new THREE.Vector2(0, 0)];
geometry.faceVertexUvs[0].push(uvs1); //remember faceVertexUvs is an array of arrays
geometry.faces.push(new THREE.Face3(0, 3, 1));
var uvs2 = [new THREE.Vector2(1, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1)];
geometry.faceVertexUvs[0].push(uvs2);
这样做可以让我们Geometry纹理化,我们可以轻松地在three.js 中应用纹理,我们只需将Texture添加到map材质中的属性。
var material = new THREE.MeshBasicMaterial({map: texture});
我们通过从磁盘加载一个纹理来获得一个纹理(虽然我们也可以创建自己的,但这对于本教程来说太高级了)。
var texture = new THREE.TextureLoader().load('uv_texture.jpg');
我们得到的是:
4、顶点颜色
几何的最后一个共同属性是顶点颜色。有时我们不想使用纹理或复杂的着色器。有时我们想提前存储一个顶点的颜色,然后简单地从中读取我们的材料。幸运的是,three.js 为我们提供了一种简单的方法来提前提供顶点颜色。
我们可以通过两种方式设置它们。第一种是将单一颜色传递给Color我们的Face3定义,从而为整个三角形提供单一颜色。或者我们可以传入一个颜色数组,以便每个顶点都有自己的颜色,该颜色被插在面上。
//Face3 takes three vertex indices, a normal and then a color
//We aren't worried about normals right now so we pass null in instead
geometry.faces.push(new THREE.Face3(0, 1, 2, null, new THREE.Color(0.9, 0.7, 0.75)));
geometry.faces.push(new THREE.Face3(0, 3, 1, null, new THREE.Color(0.6, 0.85, 0.7)));
如你所见,每个三角形都有自己的颜色。现在,如果我们希望每个顶点都有自己的颜色,我们只需创建一个包含三种颜色的数组并将其传入而不是单一的Color。
请注意,可以为每个面使用不同的数组,我只是选择不这样做。
你可以看到颜色在顶点面上混合。如果想在简单对象上平滑过渡颜色,这将变得特别有用。
5、Low-Poly地形
为了利用我们在上面学到的一切,我们将编写一个小脚本来生成一个不规则的平面,该平面被Perlin噪声抵消,从而产生一些漂亮的 Low-Poly 风格的地形。
上面没有提到的制作自己Geometry的额外好处之一是你只需要处理自己绝对需要的数据量。如果Geometry不需要法线,请不要浪费时间计算它们。如果不想添加纹理,请不要在 uvs 上浪费空间。
我们将绘制一小块 Low-Poly 风格的地形,因此我们不需要 uvs 或顶点颜色,我们将只使用每个面的法线并将单一颜色传递到材质中。
6、制作自己的平面
当 three.js 提供了如此简单的方法来制作我们自己的平面时,这似乎是一种浪费。但我们的平面将有一个显着的不同。我们不会统一传播我们的点。通常,平面由点网格定义,就像在 three.js 中一样。但是我们将把我们的点分散一点,这样当我们把平面变成地形时,它看起来就不会被锁定在网格中。
首先,让我们来一张平面图。我们将从一个常规的网格式平面开始,然后随着我们的进步移动到不同的东西。你已经了解了如何定义单个四边形。现在我们将把它扩展到更大的东西。毕竟,平面只是一堆排列在一起的四边形。让我们从一段代码开始,然后我们将它分开。
makeTile = function(size, res) {
geometry = new THREE.Geometry();
for (var i = 0; i <= res; i++) {
for (var j = 0; j <= res; j++) {
var z = j * size;
var x = i * size;
var position = new THREE.Vector3(x, 0, z);
var addFace = (i > 0) && (j > 0);
this.makeQuad(geometry, position, addFace, res + 1);
}
}
geometry.computeFaceNormals();
geometry.normalsNeedUpdate = true;
return geometry;
};
该函数makeTile有两个参数size和res。size定义构建平面的每个四边形的大小,res定义平面的宽度。很容易。然后我们有一个嵌套循环,我们在二维中计数 0 到res。很简单,你现在可以看到网格形成的开始。
魔法发生在循环内。我们将变量x和z设置为我们的顶点位置,我们通过将四边形位置乘以四边形i, j的大小来计算。我们将其保存到Vector3.
但是addFace呢。这实际上是一个聪明的小技巧。你看,我们的第一行顶点不足以形成四边形。所以在我们平面的边缘,我们告诉我们的脚本不要形成三角形,但是一旦我们进入第二行,我们就可以开始组合顶点来形成四边形和三角形。
然后我们调用makeQuad并将它传递给我们在这个循环中计算的所有信息。简单的!还有一件事,一旦我们完成循环,我们告诉three.js 为我们计算法线。然后我们返回新的几何对象以在程序的主要部分中使用。太棒了!现在让我们看看makeQuad function.
makeQuad = function(geometry, position, addFace, verts) {
geometry.vertices.push(position);
if (addFace) {
var index1 = geometry.vertices.length - 1;
var index2 = index1 - 1;
var index3 = index1 - verts;
var index4 = index1 - verts - 1;
geometry.faces.push(new THREE.Face3(index2, index3, index1));
geometry.faces.push(new THREE.Face3(index2, index4, index3));
}
};
好的,首先我们将顶点添加到顶点数组中。这很容易理解。如果addFace是 False,那么我们停止。否则,我们计算周围顶点的位置并形成两个面,就像我们上面所做的那样。只是现在我们必须计算索引位置。这就是它的全部!使用这两个函数,你最终应该得到如下结果:
7、为平面添加一些噪声
为了让事情变得有趣,我们将向顶点添加一些噪声以稍微改变高度。如果你需要复习在 javascript 中使用噪声,请查看此教程。
var position = new THREE.Vector3(x, noise.perlin2(x, z)*size, z);
我将噪声乘以大小,以便它与你的平面大小很好地缩放。如果你使用 MeshNormalMaterial你的平面现在看起来像这样:
其实有点无聊。你可以看到它只是一个顶点被上下推的网格。让我们通过更多地分解顶点位置使其更有趣。我们将通过调用 Math.random 随机偏移x和位置来做到这一点。
var z = j * size + (Math.random() - 0.5) * size;
var x = i * size + (Math.random() - 0.5) * size;
现在,如果将高度重置为 0,你可以看到三角形的变化程度。
如果你再次添加高度。
这就是创建自己的小地形所需要做的一切!尝试使用不同的设置,特别是尝试使四边形非常小并且分辨率大。x然后尝试通过乘以不同大小的数字来稍微混合噪声z以缩放噪声。你应该能够创建所有不同类型的 Low-Poly 地形。对于示例,这里是使用MeshPhongMaterial.
8、结束语
我知道上面有很多东西。程序几何是一个非常深奥的话题。但是现在你有了开始在three.js 中进一步探索它的工具!希望你已经学会:
- 如何使用 three.js 的Geometry类来制作自己的自定义几何图形,包括:
- 如何定义自己的uvs,
- 如何定义自己的顶点颜色,
- 以及如何使用 three.js 计算法线
- 如何自动创建抖动平面
边栏推荐
- One Summer of Open Source | How to Quickly Integrate Log Modules in GO Language Framework
- 软件工程国考总结——选择题
- 【术语科普】关于集成工作台那些难懂的词儿,看这篇秒懂!
- 初探zend引擎
- 重磅大咖来袭!阿里云生命科学与智能计算峰会精彩内容剧透
- After 21 years of graduation, I switched to software testing. From 0 income to a monthly salary of over 10,000, I am really lucky...
- How to encapsulate the wx.request() request of WeChat applet
- Turning and anti-climbing attack and defense
- npm ERR! 400 Bad Request - PUT xxx - Cannot publish over previously published version “1.0.0“.
- R语言ggplot2可视化:基于aes函数中的fill参数和shape参数自定义绘制分组折线图并添加数据点(散点)、使用theme函数的legend.position函数配置图例到图像右侧
猜你喜欢
斯皮尔曼相关系数
3年测试在职,月薪还不足2w,最近被裁员,用亲身经历给大家提个醒...
阿里巴巴 CTO 程立:开源是基础软件的源头!
重磅大咖来袭!阿里云生命科学与智能计算峰会精彩内容剧透
只问耕耘,不问收获,其实收获却在耕耘中
转转反爬攻防战
MSYS2 QtCreator Clangd code analysis can not find mm_malloc.h problem remedy
Weak yen turns game consoles into "financial products" in Japan: scalpers make big profits
8年软件测试工程师的感悟:与薪资相匹配的永远是实力
org.apache.ibatis.binding.BindingException Invalidbound statement (not found)的解决方案和造成原因分析(超详细)
随机推荐
The realization of the list
5G基础学习1、5G网络架构、网络接口及协议栈
Jay Chou's new song is released, crawl the "Mojito" MV barrage, and see what the fans have to say!
After 21 years of graduation, I switched to software testing. From 0 income to a monthly salary of over 10,000, I am really lucky...
从零开始Blazor Server(5)--权限验证
R language time series data arithmetic operation: use the log function to log the time series data, and use the diff function to calculate the successive difference of the logarithmic time series data
字节跳动软件测试岗,收到offer后我却拒绝了~给面试的人一些忠告....
软件测试的基本理论知识(软件测试面试基础知识)
LayaBox---TypeScript---迭代器和生成器
TimerTask(addin timer语音)
games202:三,实时环境光照IBL + PRT
详细总结SoC、DSP、MCU、GPU和FPGA等基础概念
小几届的学弟问我,软件测试岗是选11k的华为还是20k的小公司,我直呼受不了,太凡尔赛了~
WPF 截图控件之文字(七)「仿微信」
LayaBox---TypeScript---命名空间和模块
软件测试岗位巨坑?阿里在职7年测试人告诉你千万别上当
重磅大咖来袭!阿里云生命科学与智能计算峰会精彩内容剧透
Geoffery Hinton:深度学习的下一个大事件
c#反射和特性
sqlmap安装教程用w+r打开(sqlyog安装步骤)