zoukankan      html  css  js  c++  java
  • 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 );
    

    效果如下图所示

    简单示意图

    下面对代码进行简单的分析, 并进行汇总

    代码主要分为三步

    1. 创建所有点的位置数组, 每三个值形成x, y, z确定三维世界点的坐标
      • 对应positions = [],
      • positions.push()
    2. 创建所有点的颜色数组, 每三个值形成r, g, b确定三维世界点的颜色
      • colors = []
      • colors.push()
    3. 将位置数组和颜色数组导入到集合体中
      • 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 );
    

    效果图如下

    线物资

    是不是觉得这个代码与第一章节的代码十分类似呢, 实际上就是完全一样的代码

    不同点在于

    1. 线几何体的 material 是THREE.LineBasicMaterial
    2. 创建线几何体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>
    
  • 相关阅读:
    JavaScript事件处理
    JavaScript模拟"类"的三种方法
    非构造函数的继承和拷贝
    构造函数的继承
    vim开发环境
    socket之非阻塞
    网络编程
    多线程
    消息队列
    信号
  • 原文地址:https://www.cnblogs.com/xiaxiangx/p/13849204.html
Copyright © 2011-2022 走看看