ThreeJS学习6_几何体相关(BufferGeometry)
ThreeJS学习6_几何体相关(BufferGeometry)
使用 BufferGeometry 可以有效减少向 GPU 传输几何体相关数据所需的开销
可以自定义顶点位置, 面片索引, 法向量, 颜色值

1. BufferGeometry使用初体验
在之前的学习中, 我是已经了解到建立一个3d场景, 不知道屏幕前的你是否有了解到, threejs需要做的有, 第一: 渲染器renderer; 第二: 场景Scene; 第三, 光源Light; 第四, 物质有点线面三个部分.
在实际的开发过程中, 自己创建几何体这种情况很少见, 大部分情况是载入已有的模型, 对模型进行操作, 导入的模型可能很大, 这个时候就需要优化, 优化可以从几何体入手, 也可以从材质入手, 但是优化主要针对的就是几何体, 占内存的也是几何体而不是材质, 因此了解几何体是非常有必要的, 几何体英文( geometry ).
对于Threejs, 官方说明, 使用buffergeometry能够有效减少向GPU传输几何体相关数据所需要的开销, 同时, 用户可以自定义集合体的顶点位置, 名片索引, 法向量, 颜色值
下面创建一个简单的buffergeometry吧
// 顶点个数
var particles = 500000;
var geometry = new THREE.BufferGeometry();
// 每个顶点位置
let positions = [];
// 颜色值
var colors = [];
// 临时颜色类型
var color = new THREE.Color();
var n = 1000, n2 = n / 2;
for ( var i = 0; i < particles; i ++ ) {
// positions, 形成一个长方体, x, y, z的范围都是从-500到500, 形成的长方体的长宽高都为500
var x = Math.random() * n - n2;
var y = Math.random() * n - n2;
var z = Math.random() * n - n2;
positions.push( x, y, z );
// colors, 设置颜色, 同理, 从0到1
var vx = ( x / n ) + 0.5;
var vy = ( y / n ) + 0.5;
var vz = ( z / n ) + 0.5;
color.setRGB( vx, vy, vz );
colors.push( color.r, color.g, color.b );
}
// 设置位置信息
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
// 设置颜色信息
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
// 计算边界球体
geometry.computeBoundingSphere();
// 创建物资
var material = new THREE.PointsMaterial( { size: 15, vertexColors: true } );
// 创建点云
points = new THREE.Points( geometry, material );
scene.add( points );
效果如下图所示

下面对代码进行简单的分析, 并进行汇总
代码主要分为三步
- 创建所有点的位置数组, 每三个值形成x, y, z确定三维世界点的坐标
- 对应positions = [],
- positions.push()
- 创建所有点的颜色数组, 每三个值形成r, g, b确定三维世界点的颜色
- colors = []
- colors.push()
- 将位置数组和颜色数组导入到集合体中
- geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
- geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
根据代码, 将建好的点云加入场景中, 就有效果了, 完整代码附在文章末尾处
2. 简单压缩几何体的方法
threejs给我们提供了一些可以直接引用的方法降低GPU渲染几何体的开销, 这里展示官方给的3种类型的代码
里面第一行代码是用于计算mesh在GPU中所占内存
// 计算这个mesh在gpu中所占内存
BufferGeometryUtils.estimateBytesUsed( mesh.geometry ) + " bytes"
// 使用DefaultUVEncoding降低内存数
GeometryCompressionUtils.compressUvs( mesh );
// 使用QuantizePosEncoding降低内存数
GeometryCompressionUtils.compressPositions( mesh );
// 使用NormEncodingMethods降低内存数
// [ "None", "DEFAULT", "OCT1Byte", "OCT2Byte", "ANGLES" ]
GeometryCompressionUtils.compressNormals( mesh, 'None' );
3. 创建由点到线的几何体
var geometry = new THREE.BufferGeometry();
var material = new THREE.LineBasicMaterial( { vertexColors: true, morphTargets: true } );
var positions = [];
var colors = [];
for ( var i = 0; i < segments; i ++ ) {
var x = Math.random() * r - r / 2;
var y = Math.random() * r - r / 2;
var z = Math.random() * r - r / 2;
// positions
positions.push( x, y, z );
// colors
colors.push( ( x / r ) + 0.5 );
colors.push( ( y / r ) + 0.5 );
colors.push( ( z / r ) + 0.5 );
}
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
geometry.computeBoundingSphere();
line = new THREE.Line( geometry, material );
scene.add( line );
效果图如下

是不是觉得这个代码与第一章节的代码十分类似呢, 实际上就是完全一样的代码
不同点在于
- 线几何体的 material 是THREE.LineBasicMaterial
- 创建线几何体mesh使用的是 THREE.Line, 而点云使用的是THREE.Points
有了创建点几何体的知识, 就能创建线几何体
4. 创建由线到面的几何体
// 点数
var triangles = 160000;
var geometry = new THREE.BufferGeometry();
var positions = [];
// 点的法向量
var normals = [];
var colors = [];
var color = new THREE.Color();
// 正方体, 长宽高都为800
var n = 800, n2 = n / 2;
// 三角形三个点点在正方体内, 这个正方体长宽高都为12
var d = 12, d2 = d / 2;
// abc, 三个顶点位置
var pA = new THREE.Vector3();
var pB = new THREE.Vector3();
var pC = new THREE.Vector3();
// c点到b点的方向向量
var cb = new THREE.Vector3();
// a点到b点的方向向量
var ab = new THREE.Vector3();
for ( var i = 0; i < triangles; i ++ ) {
// positions
var x = Math.random() * n - n2;
var y = Math.random() * n - n2;
var z = Math.random() * n - n2;
var ax = x + Math.random() * d - d2;
var ay = y + Math.random() * d - d2;
var az = z + Math.random() * d - d2;
var bx = x + Math.random() * d - d2;
var by = y + Math.random() * d - d2;
var bz = z + Math.random() * d - d2;
var cx = x + Math.random() * d - d2;
var cy = y + Math.random() * d - d2;
var cz = z + Math.random() * d - d2;
// 添加一个三角形的3个顶点, 每个顶点有xyz三个数据
positions.push( ax, ay, az );
positions.push( bx, by, bz );
positions.push( cx, cy, cz );
// 求法向量, 首先设置三角形的三个顶点
pA.set( ax, ay, az );
pB.set( bx, by, bz );
pC.set( cx, cy, cz );
// 求出两个方向向量
cb.subVectors( pC, pB );
ab.subVectors( pA, pB );
// 叉积, 求法向量
cb.cross( ab );
// 单位化这个法向量
cb.normalize();
var nx = cb.x;
var ny = cb.y;
var nz = cb.z;
// 添加法向量到法向量数组中
// 三角形的三个顶点的法向量相同, 因此复制三份
normals.push( nx, ny, nz );
normals.push( nx, ny, nz );
normals.push( nx, ny, nz );
// colors
var vx = ( x / n ) + 0.5;
var vy = ( y / n ) + 0.5;
var vz = ( z / n ) + 0.5;
color.setRGB( vx, vy, vz );
colors.push( color.r, color.g, color.b );
colors.push( color.r, color.g, color.b );
colors.push( color.r, color.g, color.b );
}
// 加入位置信息
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ).onUpload( disposeArray ) );
// 加入法向量信息
geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ).onUpload( disposeArray ) );
// 加入颜色信息
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ).onUpload( disposeArray ) );
geometry.computeBoundingSphere();
var material = new THREE.MeshPhongMaterial( {
color: 0xaaaaaa, specular: 0xffffff, shininess: 250,
side: THREE.DoubleSide, vertexColors: true
} );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
效果图如下

面几何体与前两种几何体很大的不同在于, 面几何体需要法向量信息
在代码中我也是添加了很多注释便于理解, 这里我再大致解释一下
已知三个点, 求出两条边的方向向量, 这两个方向向量做叉乘, 结果变为由三个点构成的三角形的法向量
5. 创建点云的源码
由点到线, 由线到面, 希望读者自己可以模仿写出来
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container">
</div>
<script type="module">
import * as THREE from '../build/three.module.js';
import {OrbitControls} from "./jsm/controls/OrbitControls.js";
import {GUI} from "./jsm/libs/dat.gui.module.js";
let container, camera, scene, renderer, stats;
let points;
init();
animation();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x050505);
scene.fog = new THREE.Fog(0x050505, 2000, 3000);
scene.add(new THREE.AmbientLight(0x8FBCD4, 0.4));
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
camera.position.z = 2750;
scene.add(camera);
let particles = 500000;
let geometry = new THREE.BufferGeometry();
let positions = [];
let colors = [];
let color = new THREE.Color();
let n = 1000, n2 = n / 2;
for (let i = 0; i < particles; i++) {
let x = Math.random() * n - n2;
let y = Math.random() * n - n2;
let z = Math.random() * n - n2;
positions.push(x, y, z);
let vx = (x / n) + 0.5;
let vy = (y / n) + 0.5;
let vz = (z / n) + 0.5;
color.setRGB(vx, vy, vz);
colors.push(color.r, color.g, color.b);
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
geometry.computeBoundingSphere();
let material = new THREE.PointsMaterial({size:15, vertexColors: true});
points = new THREE.Points(geometry, material);
scene.add(points);
// let pointLight = new THREE.PointLight(0xffffff, 1);
// // 灯跟着相机走, 效果不错
// camera.add(pointLight);
scene.add(new THREE.AxesHelper(5));
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.enabledZoom = false;
window.addEventListener('resize', onWindowResize, false);
}
function animation(){
render();
requestAnimationFrame(animation);
}
function render() {
let time = Date.now() * 0.001;
points.rotation.x = time * 0.25;
points.rotation.y = time * 0.5;
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
ThreeJS学习6_几何体相关(BufferGeometry)的更多相关文章
- MySQL学习笔记-事务相关话题
事务机制 事务(Transaction)是数据库区别于文件系统的重要特性之一.事务会把数据库从一种一致状态转换为另一个种一致状态.在数据库提交工作时,可以确保其要么所有修改都已经保存了,要么所有修改都 ...
- Spark学习之基础相关组件(1)
Spark学习之基础相关组件(1) 1. Spark是一个用来实现快速而通用的集群计算的平台. 2. Spark的一个主要特点是能够在内存中进行计算,因而更快. 3. RDD(resilient di ...
- # ThreeJS学习7_裁剪平面(clipping)
ThreeJS学习7_裁剪平面(clipping) 目录 ThreeJS学习7_裁剪平面(clipping) 1. 裁剪平面简介 2. 全局裁剪和局部裁剪 3. 被多个裁剪平面裁剪后 4. 被多个裁剪 ...
- WebGL和ThreeJs学习5--ThreeJS基本功能控件
Threejs 2017年6月6日 15:06 Stats: new Stats();性能监视器,性能测试的方法,引入 Stats.js http://www.hewebgl.com ...
- 关于OpenStack的学习路线及相关资源汇总
首先我们想学习openstack,那么openstack是什么?能干什么?涉及的初衷是什么?由什么来组成?刚接触openstack,说openstack不是一个软件,而是由多个组件进行组合,这是一个更 ...
- AngularJS的学习网站及相关资源整理
学习angularjs的网站及相关资源的整理,会不断更新. angularJs的官网:https://angularjs.org/ API文档:https://docs.angularjs ...
- Scala学习(三)----数组相关操作
数组相关操作 摘要: 本篇主要学习如何在Scala中操作数组.Java和C++程序员通常会选用数组或近似的结构(比如数组列表或向量)来收集一组元素.在Scala中,我们的选择更多,不过现在我们先假定不 ...
- Linux 路由 学习笔记 之一 相关的数据结构
http://blog.csdn.net/lickylin/article/details/38326719 从现在开始学习路由相关的代码,在分析代码之前, 我们还是先分析数据结构,把数据结构之间的关 ...
- 侯捷STL学习(12)--STL相关内容hash+tuple
layout: post title: 侯捷STL学习(12) date: 2017-08-01 tag: 侯捷STL --- 第四讲 STL相关的内容 Hash Function 将hash函数封装 ...
随机推荐
- 3.CDN加速简介
什么是CDN CDN的全称是Content Delivery Network,即内容分发网络.CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问 ...
- [LeetCode]152. 乘积最大子序列(DP)
题目 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数). 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6. 示 ...
- maximo开发小结
maximo的后台开发 从0开始的 就把自己写的一些代码放者 1. setWhere 的效果是在原有的sql上添加一个and 以及这个条件mboSetRemote.setWhere("EN ...
- java 集合删除数据
public static void main(String[] args) { List<Integer> list = new ArrayList<>(Arrays.asL ...
- 【vue】---- 新版脚手架搭建项目流程详解
一.概述 本文将介绍vue项目完整的搭建流程,在使用新版本的脚手架基础上,进行了一系列的完善和配置.主要内容如下: 1.项目初始化 安装脚手架 创建项目 项目结构 2.项目搭建配置 引入第三方插件 路 ...
- IPSecVPN介绍 & (Cisco Packet Tracer)IPSecVPN实验演示
一.基础知识 VPN(Virtual Private Network)虚拟专有网络,即虚拟专网.VPN可以实现在不安全的网络上,安全的传输数据,好像专网!VPN只是一个技术,使用PKI技术,来保证数据 ...
- Linux服务器内存监控—每小时检查&超出发送邮件&重启占用最高的Java程式
简介与优点 使用该脚本能自行判断系统内存使用情况是否超出设定百分比 能在超出预警值时执行重启程式的操作 能记录重启过程,并将具体LOG邮件发送给指定收信人 可以设定Crontab排程,达成每隔一段时间 ...
- LINQ中的OrderBy实现按照两个字段升序、降序排序操作
在公司或许有这种需求,先根据第一个某个字段按照升序排序,然后如果相同,在按照第二个某个字降序排序,我们该怎么去实现呢? 现在来教教大家分别使用Labmda和LINQ进行这种操作. 1.先按照第一个字段 ...
- Java 审计之XXE篇
Java 审计之XXE篇 0x00 前言 在以前XXE漏洞了解得并不多,只是有一个初步的认识和靶机里面遇到过.下面来 深入了解一下该漏洞的产生和利用. 0x01 XXE漏洞 当程序在解析XML输入时, ...
- 可变参数__VA_ARGS__使用
1. 调试功能一般会使用到宏+可变参数的方式 1.1 ##__VA_ARGS__ 之详细解析 例如: case A. #define my_print1(...) printf(__V ...