ThreeJS:Geometry与顶点|索引|面

2024-05-04 17:04
文章标签 索引 geometry 顶点 threejs

本文主要是介绍ThreeJS:Geometry与顶点|索引|面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ThreeJS:Mesh回顾

        在前文《ThreeJS:Mesh网格与三维变换》中,我们提到:ThreeJS中,Mesh表示基于以三角形为多边形网格(polygon mesh)的物体的类,同时也作为其它类的基类。

        Mesh可用于表示空间实体,即:通过Mesh网格,我们可以组合Geometry几何体与Material材质属性,在3D世界中,定义一个物体。例如:之前我们创建的立方体物体,

//TODO:创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
//TODO:创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
//TODO:创建网格
const mesh = new THREE.Mesh(geometry, material);

Mesh网格深入

        ThreeJS在绘制Mesh表示的空间实体时,背后必定存在一个数据源提供相应的支持,直观意义上的理解包括两方面:

        ①点、线、面等几何体数据;

        ②颜色/填充色、透明度等数据。

BufferGeometry:几何体

        ThreeJS提供了BufferGeometry接口,用于表述:面片、线、点几何体,其中包括:顶点位置、面片索引、法向量等,此处我们先重点关注“顶点位置”、“面片索引”。

顶点位置

        在前文《ThreeJS:坐标辅助器与轨道控制器》中提过:ThreeJS采用右手规则确定空间直角坐标系,即:即:X轴向右、Y轴向上、Z轴向前。而要确定一个物体在这个坐标系中的何处位置,毫无疑问,坐标是最好的表达方式。

  1. 对于一个空间点,我们可以使用一个坐标对(x,y)来确定其空间位置;
  2. 对于一条空间线,我们可以使用一个连续、有序的坐标对序列[(x1,y1),(x2,y2),(x3,y3),...(xn,yn)]来确定其空间位置;
  3. 对于一个空间面,我们可以使用一个坐标对集合{(x1,y1),(x2,y2),(x3,y3),...(xn,yn)}来确定其空间位置。

        我们可以拿之前的mesh立方体做个简单的验证,查看一下它的坐标序列,

        可以看到,BufferGeometry的attributes属性存储了该几何体相关的属性,我们来尝试性打印一下,

//TODO:查验坐标序列
console.log('mesh-attributes::',mesh.geometry.attributes)

        attributes属性的position以定型数组形式提供了顶点数据,其中:每3个数据项组成了一个坐标顶点(因为一个Vector3空间向量包含X、Y、Z三个分量),共计8个顶点,这与立方体的顶点个数是相一致的

        综上可知:ThreeJS中,通过顶点位置可以描述空间实体的空间位置信息或者说是空间实体的形状信息。

面片索引
何为面片?

        为了直观地理解面片,我们先通过lil-gui,实现一个控制Mesh开启/关闭线框模式的控制按钮/checkbox,当我们勾选时,显示mesh立方体的线框模式;当我们取消勾选时,关闭mesh立方体的线框模式

        修改lil-gui添加调试按钮的代码,如下,

//TODO:lil-gui添加调试按钮
const myObject = {// myBoolean: true,fullScreenBtnFunction: function () {document.body.requestFullscreen();},exitFullScreenBtnFunction: function () {document.exitFullscreen();},wireframeMode: false,
};gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
//TODO:开启/关闭线框模式
gui.add(myObject, "wireframeMode").name("线框模式").onChange(function (value) {console.log(value);mesh.material.wireframe = value;});

        添加完毕。此时,我们可通过勾选"线框模式",开启mesh立方体的线框模式。

        可以看到,线框模式下,立方体变成了由一个个三角形框线构成的立方体(共计12个三角形), 这一个个三角形,就称之为“面片”。

面片索引

        ThreeJS这里的面片索引,我姑且先理解为WebGL中的顶点索引(如果后续学习中发现不对的话,会重新进行补充说明)。

        在WebGL中,顶点索引是一种顶点数据的复用方式,它是用于指定三角形面的顶点顺序的一种技术。它通过使用一个索引缓冲区来存储顶点的索引值,而不是以顺序排列的方式传递顶点数据。 使用顶点索引的主要优点包括:

  1. 内存效率:通过使用索引,可以避免重复存储相同的顶点数据。相同的顶点只需存储一次,并通过索引进行引用,这样可以减少内存使用量。
  2. 性能优化:使用顶点索引可以提高渲染性能。由于顶点数据的数量减少,数据传输和处理的开销也相应减少。此外,顶点索引还可以减少图形管线中的顶点处理次数,从而提高渲染速度。
  3. 灵活性:使用顶点索引可以更灵活地定义几何图形。通过重新组织和修改索引,可以轻松地生成不同形状和拓扑结构的模型。 需要注意的是,使用顶点索引也存在一些限制。例如,当需要修改几何图形或进行骨骼动画时,顶点索引的使用可能会带来一些复杂性。此外,如果几何图形中存在较多的共享顶点,使用顶点索引可能不会带来明显的优化效果。因此,在选择使用顶点索引时,需要根据具体场景进行权衡和评估。

自定义几何体|顶点顺序问题

        理解了以上基本概念之后,我们就可以通过BufferGeometry,自定义一个几何体。例如:我们可以创建一个基础的三角形,如下图所示,顶点位置分别为:(-1.0,-1.0,1.0)、( 1.0,-1.0,1.0)、(1.0,1.0,1.0)。

代码实现        

        代码部分如下,

//TODO:隐藏mesh立方体(方便显示triangle三角形,并且保证现有代码不报错)
mesh.material.visible = false;//TODO:创建三角形
const triangleGeometry = new THREE.BufferGeometry();
//设置顶点数据-[每3个一组]
const vertexes = new Float32Array([-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0
]);
triangleGeometry.setAttribute('position',
new THREE.BufferAttribute(vertexes,3));
//设置材质信息
const material2 = new THREE.MeshBasicMaterial({color:0x0000ff});
const triangleMesh = new THREE.Mesh(triangleGeometry,material2);
scene.add(triangleMesh);

        显示效果如下,

顶点顺序与正反面

        其实,WebGL中的三角形有正反面的概念,正面三角形的顶点顺序是逆时针方向, 反面三角形是顺时针方向。如下图所示,

        所以,同样的代码片段,当我们将视角切换到三角形背面时,就会发现“三角形消失了”,其实不是消失了,而是我们的顶点顺序是逆时针方向,默认只会显示正面,而不会显示背面。

       为了同时显示正面、背面,我们可以设置其material材质的side属性,

        修改代码片段如下,

//TODO:隐藏mesh
mesh.material.visible = false;//TODO:创建三角形
const triangleGeometry = new THREE.BufferGeometry();
//设置顶点数据-[每3个一组]
const vertexes = new Float32Array([-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0
]);
triangleGeometry.setAttribute('position',
new THREE.BufferAttribute(vertexes,3));
//设置材质信息
const material2 = new THREE.MeshBasicMaterial({color:0x0000ff,side:THREE.DoubleSide});
const triangleMesh = new THREE.Mesh(triangleGeometry,material2);
scene.add(triangleMesh);

        此时,无论是正面视角,还是背面视角,都可以看到三角形了。

正面视角
背面视角

lil-gui控制正反面渲染模式

        为了便于后续修改正反面渲染模式,我们可以通过lil-gui控制面板进行控制,修改lil-gui调试面板部分代码如下,

//TODO:lil-gui添加调试按钮
const myObject = {// myBoolean: true,fullScreenBtnFunction: function () {document.body.requestFullscreen();},exitFullScreenBtnFunction: function () {document.exitFullscreen();},wireframeMode: false,renderMode: 1,
};gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
//TODO:开启/关闭线框模式
gui.add(myObject, "wireframeMode").name("线框模式").onChange(function (value) {console.log(value);mesh.material.wireframe = value;});
// gui.add(myObject, "myString"); // Text Field
// gui.add(myObject, "myNumber"); // Number Field//TODO:控制Mesh网格正反面显示模式
gui.add(myObject, "renderMode", { 双面模式: 0, 正面模式: 1, 背面模式: 2 }).name("三角形正反面显示模式").onChange(function (value) {console.log(value);switch (value) {case 0: {triangleMesh.material.side = THREE.DoubleSide;break;}case 1: {triangleMesh.material.side = THREE.FrontSide;break;}case 2: {triangleMesh.material.side = THREE.BackSide;break;}}})

三角形升级为四边形

        要将三角形升级为四边形,其实只需要修改顶点数组即可,

//TODO:隐藏mesh
mesh.material.visible = false;//TODO:创建三角形
const triangleGeometry = new THREE.BufferGeometry();
//设置顶点数据-[每3个一组]
const vertexes = new Float32Array([//三角面1-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0,//三角面2-1.0,-1.0,1.0,1.0,1.0,1.0,-1.0,1.0,1.0,
]);
triangleGeometry.setAttribute('position',
new THREE.BufferAttribute(vertexes,3));
//设置材质信息
const material2 = new THREE.MeshBasicMaterial({color:0x0000ff,side:THREE.DoubleSide});
const triangleMesh = new THREE.Mesh(triangleGeometry,material2);
scene.add(triangleMesh);

        显示效果如下, 

使用顶点索引实现顶点数据复用

        前面提到“使用顶点索引”可以复用现有顶点数据,那么就可以尽可能减少顶点数据的重复定义,降低数据从CPU到GPU的传输开销,从而可以提高渲染性能。

        在前面的四边形,它只有4个顶点,但是我们却定义了6个,这显然是有待优化的。以下,我们通过顶点索引数组,来进行优化处理,

//TODO:隐藏mesh
mesh.material.visible = false;//TODO:创建三角形
const triangleGeometry = new THREE.BufferGeometry();
//设置顶点数据
const vertexes = new Float32Array([-1.0,-1.0,1.0, //01.0,-1.0,1.0, //11.0,1.0,1.0, //2-1.0,1.0,1.0, //3
]);
triangleGeometry.setAttribute("position",new THREE.BufferAttribute(vertexes, 3)
);//设置索引数据
const indices = [//三角面10, 1, 2,//三角面22, 3,0,
];
triangleGeometry.setIndex(indices);
//设置材质信息
const material2 = new THREE.MeshBasicMaterial({color: 0x0000ff,side: THREE.DoubleSide,
});
const triangleMesh = new THREE.Mesh(triangleGeometry, material2);
scene.add(triangleMesh);

        显示效果如下,

自定义几何体:顶点分组设置不同材质

        ThreeJS提供了对单个Mesh物体按照顶点进行分组渲染的API接口,

        通过addGroup来增加分组,

        例如:我们将上面自定义的四边形进行分组渲染,

//TODO:设置分组渲染
triangleGeometry.addGroup(0,3,0);
triangleGeometry.addGroup(3,3,1);

        然后在构造Mesh时,指定Material材质数组,

        代码如下,

//TODO:隐藏mesh
mesh.material.visible = false;//TODO:创建三角形
const triangleGeometry = new THREE.BufferGeometry();//设置顶点数据
const vertexes = new Float32Array([-1.0,-1.0,1.0, //01.0,-1.0,1.0, //11.0,1.0,1.0, //2-1.0,1.0,1.0, //3
]);
triangleGeometry.setAttribute("position",new THREE.BufferAttribute(vertexes, 3)
);//设置索引数据
const indices = [//三角面10, 1, 2,//三角面22, 3,0,
];
triangleGeometry.setIndex(indices);
//TODO:设置分组渲染
triangleGeometry.addGroup(0,3,0);
triangleGeometry.addGroup(3,3,1);//设置材质信息
const material2 = new THREE.MeshBasicMaterial({color: 0x0000ff,side: THREE.DoubleSide,
});
const material3 = new THREE.MeshBasicMaterial({color: 0x00ffff,side: THREE.DoubleSide,
});
// const triangleMesh = new THREE.Mesh(triangleGeometry, material2);
const triangleMesh = new THREE.Mesh(triangleGeometry, [material2,material3
]);
scene.add(triangleMesh);

        显示效果如下,

Mesh网格案例:彩色四边形

        在了解了以上的基本概念之后,我们来看一个官网的案例:彩色多边形,

        案例main.js源码如下,

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; //轨道控制器
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js"; //lil-gui调试工具
// import Stats from "three/examples/libs/stats.module.js";const gui = new GUI();//TODO:打印版本
console.warn("threejs版本:", THREE.REVISION);//TODO:创建场景
const scene = new THREE.Scene();
//TODO:创建透视相机
const camera = new THREE.PerspectiveCamera(45, //视角window.innerWidth / window.innerHeight,0.1, //近平面1000.0 //远平面
);//TODO:创建渲染器
const renderer = new THREE.WebGLRenderer({antialias:true});//抗锯齿antialias
renderer.setSize(window.innerWidth, window.innerHeight);//TODO:轨道控制器
const orbitControls = new OrbitControls(camera, renderer.domElement);
//设置阻尼效果
// orbitControls.enableDamping = true;
orbitControls.update();document.body.appendChild(renderer.domElement);//TODO:添加光源
const light = new THREE.HemisphereLight();
light.intensity = 3;
scene.add(light);//TODO:添加BufferGeometry-彩色四边形
const addMultiColorGeometry = function () {const geometry = new THREE.BufferGeometry();//declareconst indices = [];const vertices = [];const normals = [];const colors = [];const size = 20;const segments = 10;const halfSize = size / 2;const segmentSize = size / segments;const _color = new THREE.Color();//generate vertices, normals and color data for a simple grid geometryfor (let i = 0; i <= segments; i++) {const y = i * segmentSize - halfSize;for (let j = 0; j <= segments; j++) {const x = j * segmentSize - halfSize;vertices.push(x, -y, 0);normals.push(0, 0, 1);const r = x / size + 0.5;const g = y / size + 0.5;_color.setRGB(r, g, 1, THREE.SRGBColorSpace);colors.push(_color.r, _color.g, _color.b);}}// generate indices (data for element array buffer)for (let i = 0; i < segments; i++) {for (let j = 0; j < segments; j++) {const a = i * (segments + 1) + (j + 1);const b = i * (segments + 1) + j;const c = (i + 1) * (segments + 1) + j;const d = (i + 1) * (segments + 1) + (j + 1);// generate two faces (triangles) per iterationindices.push(a, b, d); // face oneindices.push(b, c, d); // face two}}// 设置顶点索引geometry.setIndex(indices);geometry.setAttribute("position",new THREE.Float32BufferAttribute(vertices, 3));geometry.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3));geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));//Material材质const material = new THREE.MeshPhongMaterial({side: THREE.DoubleSide,vertexColors: true,});//Meshconst mesh = new THREE.Mesh(geometry, material);scene.add(mesh);return mesh;
};const mesh = addMultiColorGeometry();
//TODO:设置相机位置
camera.position.z = 50.0;
camera.position.y = 20.0;
camera.position.x = 20.0;
camera.lookAt(0, 0, 0);//TODO:创建坐标辅助器
const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);//TODO:渲染函数
function animate() {requestAnimationFrame(animate);//TODO:旋转立方体// mesh.rotation.x += 0.01;// mesh.rotation.y += 0.01;//TODO:更新轨道控制器orbitControls.update();//TODO:渲染renderer.render(scene, camera);
}
window.onresize = function () {//TODO:重置渲染器宽高比renderer.setSize(window.innerWidth, window.innerHeight);//TODO:重置相机宽高比camera.aspect = window.innerWidth / window.innerHeight;//TODO:更新相机投影矩阵camera.updateProjectionMatrix();
};animate();//TODO:lil-gui添加调试按钮
const myObject = {// myBoolean: true,fullScreenBtnFunction: function () {document.body.requestFullscreen();},exitFullScreenBtnFunction: function () {document.exitFullscreen();},wireframeMode: false,renderMode: 1,
};gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
//TODO:开启/关闭线框模式
gui.add(myObject, "wireframeMode").name("线框模式").onChange(function (value) {console.log(value);mesh.material.wireframe = value;});

这篇关于ThreeJS:Geometry与顶点|索引|面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/959682

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

POJ3041 最小顶点覆盖

N*N的矩阵,有些格子有物体,每次消除一行或一列,最少要几次消灭完。 行i - >列j 连边,表示(i,j)处有物体,即 边表示 物体。 import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWriter;impo

贝壳面试:什么是回表?什么是索引下推?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题: 1.谈谈你对MySQL 索引下推 的认识? 2.在MySQL中,索引下推 是如何实现的?请简述其工作原理。 3、说说什么是 回表,什么是 索引下推 ? 最近有小伙伴在面试 贝壳、soul,又遇到了相关的

Mysql高级篇(中)——索引介绍

Mysql高级篇(中)——索引介绍 一、索引本质二、索引优缺点三、索引分类(1)按数据结构分类(2)按功能分类(3) 按存储引擎分类(4) 按存储方式分类(5) 按使用方式分类 四、 索引基本语法(1)创建索引(2)查看索引(3)删除索引(4)ALTER 关键字创建/删除索引 五、适合创建索引的情况思考题 六、不适合创建索引的情况 一、索引本质 索引本质 是 一种数据结构,它用

OPENGL顶点数组, glDrawArrays,glDrawElements

顶点数组, glDrawArrays,glDrawElements  前两天接触OpenGL ES的时候发现里面没有了熟悉的glBegin(), glEnd(),glVertex3f()函数,取而代之的是glDrawArrays()。有问题问google,终于找到答案:因为OpenGL ES是针对嵌入式设备这些对性能要求比较高的平台,因此把很多影响性能的函数都去掉了,上述的几个函数都被移除了。接

ElasticSearch 6.1.1 通过Head插件,新建索引,添加文档,及其查询数据

ElasticSearch 6.1.1 通过Head插件,新建索引,添加文档,及其查询; 一、首先启动相关服务: 二、新建一个film索引: 三、建立映射: 1、通过Head插件: POST http://192.168.1.111:9200/film/_mapping/dongzuo/ {"properties": {"title": {"type":

ElasticSearch 6.1.1运用代码添加索引及其添加,修改,删除文档

1、新建一个MAVEN项目:ElasticSearchTest 2、修改pom.xml文件内容: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.or

postgres数据库中如何看查询是否走索引,以及在什么情况下走索引

在 PostgreSQL 中,可以通过 EXPLAIN 或 EXPLAIN ANALYZE 查看查询计划,以判断查询是否使用了索引。除此之外,了解索引的使用条件对于优化查询性能也很重要。 1. 如何查看查询是否使用索引 使用 EXPLAIN 查看查询计划 EXPLAIN 显示 PostgreSQL 如何执行查询,包括是否使用索引。 EXPLAIN SELECT * FROM users WH

数据库系统 第42节 数据库索引简介

数据库索引是数据库表中一个或多个列的数据结构,用于加快数据检索速度。除了基础的B-Tree索引,其他类型的索引针对特定的数据类型和查询模式提供了优化。以下是几种不同类型的索引及其使用场景的详细说明和示例代码。 1. 位图索引 (Bitmap Index) 位图索引适用于具有少量不同值的列(例如性别、国家代码等),它使用位图来表示数据,从而提高查询效率。 适用场景:当列中的值域较小,且数据分布

PostgreSQL索引介绍

梦中彩虹   博客园首页新随笔联系管理 随笔 - 131  文章 - 1  评论 - 14 PostgreSQL索引介绍 INDEX 索引是增强数据库性能的常用方法。索引使得数据库在查找和检索数据库的特定行的时候比没有索引快的多。但索引也增加了整个数据库系统的开销,所以应该合理使用。 介绍 假设我们有一个类似这样的表: CREATE TABLE test1 (id integ