三维空间中绘制点、线、面、UV贴图,万能的BufferGeometry(three.js实战4)

1. 序言

three.js 中提供了一系列绘制几何体的类,如BoxGeometry、SphereGeometry,PlaneGeometry、CircleGeometry、CylinderGeometry 等,使用这些类,可以快速创建对应的几何体,three.js同时提供了对应的 BoxBufferGeometry、SphereBufferGeometry,PlaneBufferGeometry、CircleBufferGeometry、CylinderBufferGeometry等直接使用缓存的构建几何体的 xxxBufferGeometry 类簇,这些类簇相比于 xxxGeometry 类簇,可以有效减少向 GPU 传输数据所需的开销,极大提高效率,但如果提供的这些几何都不能满足需求怎么办,这时就需要用到万能的 BufferGeometry ,通过它可以向缓存中传输几何体的顶点坐标、面索引、顶点颜色、顶点法向量、顶点UV甚至是自定义属性, 使用自定义属性和着色器材质配合使用强大到无所不能

这里顺便说说ShaderMaterial-着色器材质和RawShaderMaterial-原始着色器材质,它们都支持GLSL语言编写的shader,不同之处是ShaderMaterial会把three.js内置attributes和uniforms一起传给shader,而RawShaderMaterial 不会 向shader中传递任何内置属性

2. 如何使用BufferGeometry

2.1 创建BufferGeometry对象

创建BufferGeometry对象与创建其他THREE对象一样,非常简单,如下

const bufferGeom = new THREE.BufferGeometry();

2.2 向BufferGeometry对象添加属性

这一步其实就是初始化绘制几何体所需的顶点坐标、面索引、顶点颜色、顶点法向量、顶点UV等信息,这些属性three.js都是内置属性,属性名已定死,例如:position,color,normal,index等

这一步可向BufferGeometry对象添加任意属性,我们以position属性和index为例说明一下

  • 添加position属性
    首先通过Float32Array类创建序列化数组,示例中是每3个数构成一个点,然后使用BufferAttribute类创建于BufferGeometry对象关联的存储缓存,最后使用setAttribute方法关联缓存,示例代码如下
//初始化存放顶点信息的序列化数组
const positions = new Float32Array([
  -5.0, 3.0, 0.0, //point0
  5.0, 3.0, 0.0, //point1
  6.0, -3.0, 0.0, //point2
  -6.0, -3.0, 0.0 //point3
]);

//设置顶点信息,第二个参数3表示三个数确定一个顶点
bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

其他一些相关的属性color,normal等以及自定义属性的添加都可以参数以上方式

  • 添加index属性
    index 属性与其他属性添加有一些不一样,序列化数组的类型是Uint16Array,不是通过setAttribute设置属性,而是直接设置到BufferGeometry实例的index属性下,使用BufferAttribute创建缓存数据是第二个参数为1,代表一个数就是一个索引信息
const indexs = new Uint16Array([
  0, 1, 2,
  3, 0
]);


//设置画面的索引
bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

2.3 创建Mesh

创建mesh需要两个参数一个几何体一个材质,几何体通过上述两步创建,创建材质时,如果设置的顶点颜色属性,且需要使用自定义的也是着色,要将材质的vertexColors属性设置为 THREE.VertexColors。表示使用缓存中的颜色着色

 //创建材质
 const material = new THREE.MeshBasicMaterial({
   vertexColors: THREE.VertexColors, //使用缓存中的颜色
   side: THREE.DoubleSide
 });

 const mesh = new THREE.Mesh(bufferGeom, material);

3. BufferGeometry使用示例

3.1 绘制点

首先创建BufferGeometry实例,然后创建存放顶信息的序列化数组,接着设置position属性,最后创建PointsMaterial材质并使用该材质创建Points对象,就可以完成点的绘制

function drawPointByBufferGeometry() {
  //创建BufferGeometry实例
  const bufferGeom = new THREE.BufferGeometry();

  //初始化存放顶点信息的序列化数组
  const positions = new Float32Array([
    -5.0, 3.0, 0.0, //point0
    5.0, 3.0, 0.0, //point1
    6.0, -3.0, 0.0, //point2
    -6.0, -3.0, 0.0 //point3
  ]);

  //设置顶点信息
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  //创建点材质
  const material = new THREE.PointsMaterial({
    color: 'red',
    size: 2
  });

  const mesh = new THREE.Points(bufferGeom, material);
  scene.add(mesh);
}

绘制效果如下:
在这里插入图片描述

3.2 绘制线

  • 创建BufferGeometry实例
    使用 new THREE.BufferGeometry() 创建bufferGeom实例
  • 设置position,color,index属性
    position,color属性设置与之前的一样,请注意设置index属性时,index序列化数组的值为0, 1, 2, 3, 0 最终还要回到原点否则最后一条线无法绘制
  • 创建Mesh
    这里使用LineBasicMaterial创建材质,同样将材质的vertexColors属性设置为 THREE.VertexColors。表示使用缓存中的颜色着色,并使用Line创建线
function drawLineByBufferGeometry() {
  //创建BufferGeometry实例
  const bufferGeom = new THREE.BufferGeometry();

  //初始化存放顶点信息的序列化数组
  const positions = new Float32Array([
    -5.0, 3.0, 0.0, //point0
    5.0, 3.0, 0.0, //point1
    6.0, -3.0, 0.0, //point2
    -6.0, -3.0, 0.0 //point3
  ]);

  //设置顶点信息
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

  //初始化存放颜色信息的序列化数组
  const colors = new Float32Array([
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    0.0, 0.5, 0.5
  ]);
  //设置颜色信息
  bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));

  const indexs = new Uint16Array([
    0, 1, 2,
    3, 0
  ]);


  //设置画面的索引
  bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

  //创建材质
  const material = new THREE.LineBasicMaterial({
    vertexColors: THREE.VertexColors, //使用缓存中的颜色
    side: THREE.DoubleSide
  });

  const mesh = new THREE.Line(bufferGeom, material);
  scene.add(mesh);
}

在这里插入图片描述
绘制的结果如上如,会发现绘制的线和使用WebGL绘制线一样。会根据顶点颜色作插值计算

3.3 绘制面

  • 创建BufferGeometry实例
    使用 new THREE.BufferGeometry() 创建bufferGeom实例
  • 设置position,color,index属性
    position,color属性设置与之前的一样,需要注意的是设置index属性时,index序列化数组的值为0, 1, 2, 0, 2, 3 表示使用索引为0,1,2的点绘制一个三角面和使用索引为0,2,3的点绘制另一个三角面
  • 创建Mesh
    使用MeshBasicMaterial创建材质,也需要将材质的vertexColors属性设置为 THREE.VertexColors。表示使用缓存中的颜色着色,然后创建Mesh对象
function drawPlaneByBufferGeometry() {
  //创建BufferGeometry实例
  const bufferGeom = new THREE.BufferGeometry();

  //初始化存放顶点信息的序列化数组
  const positions = new Float32Array([
    -5.0, 3.0, 0.0, //point0
    5.0, 3.0, 0.0, //point1
    6.0, -3.0, 0.0, //point2
    -6.0, -3.0, 0.0 //point3
  ]);

  //设置顶点信息
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

  //初始化存放颜色信息的序列化数组
  const colors = new Float32Array([
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    0.0, 0.5, 0.5
  ]);
  //设置颜色信息
  bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));

  const indexs = new Uint16Array([
    0, 1, 2,
    0, 2, 3
  ]);


  //设置画面的索引
  bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

  //创建材质
  const material = new THREE.MeshBasicMaterial({
    vertexColors: THREE.VertexColors, //使用缓存中的颜色
    side: THREE.DoubleSide
  });

  const mesh = new THREE.Mesh(bufferGeom, material);
  scene.add(mesh);
}

在这里插入图片描述
绘制结果如上图,绘制了一个梯形,颜色同样根据顶点颜色进行了线性插值

3.4 绘制自定义UV贴图

其他的步骤与绘制面一样,不同的是需要添加uv属性,添加uv属性前先来捋一下uv坐标与顶点坐标的关系
在这里插入图片描述
上图是uv坐标与梯形顶点坐标的关系图,只需要按这个顺添加uv属性即可,需要注意的数uv坐标是一个Vector2类型,设置的时候需要注意,创建BufferAttribute时第二个参数为2,具体实现代码如下:

function drawPlaneByBufferGeometryUV() {
  //创建BufferGeometry实例
  const bufferGeom = new THREE.BufferGeometry();

  //初始化存放顶点信息的序列化数组
  const positions = new Float32Array([
    -5.0, 3.0, 0.0, //point0
    5.0, 3.0, 0.0, //point1
    6.0, -3.0, 0.0, //point2
    -6.0, -3.0, 0.0, //point3

  ]);

  //设置顶点信息
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

  //初始化存放颜色信息的序列化数组
  const colors = new Float32Array([
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,
    0.5, 0.3, 0.6,

  ]);
  //设置颜色信息
  bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));


  const indexs = new Uint16Array([
    0, 1, 2,
    0, 2, 3,
    4, 5, 6,
    4, 6, 7
  ]);


  //设置画面的索引
  bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

  const uvs = new Uint16Array([
    0, 1,
    1, 1,
    1, 0,
    0, 0,

  ]);
  //设置UV
  bufferGeom.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

  const planetTexture = new THREE.TextureLoader().load("../assets/textures/test.png");

  //创建材质
  const material = new THREE.MeshBasicMaterial({
    map: planetTexture,
    vertexColors: THREE.VertexColors, //使用缓存中的颜色
    side: THREE.DoubleSide
  });

  const mesh = new THREE.Mesh(bufferGeom, material);
  scene.add(mesh);
}

在这里插入图片描述
上图为代码执行结果,可以看到狗狗贴图被正确加载到梯形上了

4. 示例代码

<!DOCTYPE html>

<html>

<head>
  <title>使用BufferGeometry</title>
  <script type="text/javascript" src="../three/build/three.js"></script>
  <script type="text/javascript" src="../three/examples/js/controls/OrbitControls.js"></script>
  <script type="text/javascript" src="../three/examples/js/libs/stats.min.js"></script>
  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
  </style>
</head>

<body>

  <div id="Stats-output"></div>
  <div id="WebGL-output"></div>

  <script type="text/javascript">
    var scene, camera, renderer, arrowLineTexture, flowingLineTexture, stats, controls, clock;

    function initScene() {
      scene = new THREE.Scene();
      //用一张图加载为纹理作为场景背景
      scene.background = new THREE.TextureLoader().load("../assets/textures/starry-deep-outer-space-galaxy.jpg");
    }

    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(20, 30, 50);
      camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

    function initLight() {
      //添加环境光
      const ambientLight = new THREE.AmbientLight(0x0c0c0c);
      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight('#fff')
      directionalLight.position.set(30, 30, 30).normalize()
      scene.add(directionalLight)

      //添加聚光灯
      const spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(-40, 60, -10);
      spotLight.castShadow = true;
      scene.add(spotLight);
    }

    function initModel() {
      //drawPointByBufferGeometry();
      //drawLineByBufferGeometry();
      //drawPlaneByBufferGeometry();
      drawPlaneByBufferGeometryUV();
      initPlane();

    }

    function drawPlaneByBufferGeometryUV() {
      //创建BufferGeometry实例
      const bufferGeom = new THREE.BufferGeometry();

      //初始化存放顶点信息的序列化数组
      const positions = new Float32Array([
        -5.0, 3.0, 0.0, //point0
        5.0, 3.0, 0.0, //point1
        6.0, -3.0, 0.0, //point2
        -6.0, -3.0, 0.0, //point3

      ]);

      //设置顶点信息
      bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

      //初始化存放颜色信息的序列化数组
      const colors = new Float32Array([
        0.5, 0.3, 0.6,
        0.5, 0.3, 0.6,
        0.5, 0.3, 0.6,
        0.5, 0.3, 0.6,

      ]);
      //设置颜色信息
      bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));

      const indexs = new Uint16Array([
        0, 1, 2,
        0, 2, 3,
        4, 5, 6,
        4, 6, 7
      ]);

      //设置画面的索引
      bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

      const uvs = new Uint16Array([
        0, 1,
        1, 1,
        1, 0,
        0, 0,

      ]);
      //设置UV
      bufferGeom.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

      const planetTexture = new THREE.TextureLoader().load("../assets/textures/test.png");

      //创建材质
      const material = new THREE.MeshBasicMaterial({
        map: planetTexture,
        vertexColors: THREE.VertexColors, //使用缓存中的颜色
        side: THREE.DoubleSide
      });

      const mesh = new THREE.Mesh(bufferGeom, material);
      scene.add(mesh);
    }

    function drawPointByBufferGeometry() {
      //创建BufferGeometry实例
      const bufferGeom = new THREE.BufferGeometry();

      //初始化存放顶点信息的序列化数组
      const positions = new Float32Array([
        -5.0, 3.0, 0.0, //point0
        5.0, 3.0, 0.0, //point1
        6.0, -3.0, 0.0, //point2
        -6.0, -3.0, 0.0 //point3
      ]);

      //设置顶点信息
      bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

      //创建点材质
      const material = new THREE.PointsMaterial({
        color: 'red',
        size: 2
      });

      const mesh = new THREE.Points(bufferGeom, material);

      scene.add(mesh);
    }

    function drawLineByBufferGeometry() {
      //创建BufferGeometry实例
      const bufferGeom = new THREE.BufferGeometry();

      //初始化存放顶点信息的序列化数组
      const positions = new Float32Array([
        -5.0, 3.0, 0.0, //point0
        5.0, 3.0, 0.0, //point1
        6.0, -3.0, 0.0, //point2
        -6.0, -3.0, 0.0 //point3
      ]);

      //设置顶点信息
      bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

      //初始化存放颜色信息的序列化数组
      const colors = new Float32Array([
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0,
        0.0, 0.5, 0.5
      ]);
      //设置颜色信息
      bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));

      const indexs = new Uint16Array([
        0, 1, 2,
        3, 0
      ]);


      //设置画面的索引
      bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

      //创建材质
      const material = new THREE.LineBasicMaterial({
        vertexColors: THREE.VertexColors, //使用缓存中的颜色
        side: THREE.DoubleSide
      });

      const mesh = new THREE.Line(bufferGeom, material);
      scene.add(mesh);
    }

    function drawPlaneByBufferGeometry() {
      //创建BufferGeometry实例
      const bufferGeom = new THREE.BufferGeometry();

      //初始化存放顶点信息的序列化数组
      const positions = new Float32Array([
        -5.0, 3.0, 0.0, //point0
        5.0, 3.0, 0.0, //point1
        6.0, -3.0, 0.0, //point2
        -6.0, -3.0, 0.0 //point3
      ]);

      //设置顶点信息
      bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));

      //初始化存放颜色信息的序列化数组
      const colors = new Float32Array([
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0,
        0.0, 0.5, 0.5
      ]);
      //设置颜色信息
      bufferGeom.setAttribute('color', new THREE.BufferAttribute(colors, 3));

      const indexs = new Uint16Array([
        0, 1, 2,
        0, 2, 3
      ]);


      //设置画面的索引
      bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

      //创建材质
      const material = new THREE.MeshBasicMaterial({
        vertexColors: THREE.VertexColors, //使用缓存中的颜色
        side: THREE.DoubleSide
      });

      const mesh = new THREE.Mesh(bufferGeom, material);
      scene.add(mesh);
    }

    //创建底面
    function initPlane() {
      const planeGeometry = new THREE.PlaneGeometry(50, 50, 1, 1); //创建一个平面几何对象

      //材质
      const planeMaterial = new THREE.MeshLambertMaterial({
        color: 0x080631,
        transparent: true,
        opacity: 0.8
      });
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);

      //设置平面位置
      plane.rotation.x = -0.5 * Math.PI;
      plane.position.set(0, -4, 0);

      //平面添加到场景中
      scene.add(plane);
    }

    //初始化渲染器
    function initRender() {
      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      });
      renderer.setClearColor(0x111111, 1); //设置背景颜色
      renderer.setSize(window.innerWidth, window.innerHeight);
      //renderer.shadowMap.enabled = true; //显示阴影
      document.getElementById("WebGL-output").appendChild(renderer.domElement);
    }
    //初始化轨道控制器
    function initControls() {
      clock = new THREE.Clock(); //创建THREE.Clock对象,用于计算上次调用经过的时间
      controls = new THREE.OrbitControls(camera, renderer.domElement);
      //controls.autoRotate = true; //是否自动旋转
    }


    //性能监控
    function initStats() {
      stats = new Stats();
      stats.setMode(0); //0: fps, 1: ms
      document.getElementById("Stats-output").appendChild(stats.domElement);
    }

    function render() {

      const delta = clock.getDelta(); //获取自上次调用的时间差
      controls.update(delta); //控制器更新
      stats.update();
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    //页面初始化
    function init() {
      initScene();
      initCamera();
      initLight();
      initModel();
      initRender();
      initStats();
      initControls();
      render();
    }

    window.onload = init;
  </script>
</body>

</html>
  • 9
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Three.js是一个用于创建和展示3D图形的JavaScript库。它提供了丰富的功能和工具,可以轻松地在Web浏览器创建交互式的3D场景。 要使用Three.js绘制三棱锥,首先需要创建一个场景(Scene)、相机(Camera)和渲染器(Renderer)。然后,可以创建一个几何体(Geometry)来表示三棱锥的形状,并将其添加到场景。最后,通过渲染器将场景和相机渲染到HTML页上。 以下是一个使用Three.js绘制三棱锥的示例代码: ```javascript // 创建场景 var scene = new THREE.Scene(); // 创建相机 var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; // 创建渲染器 var renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建三棱锥的几何体 var geometry = new THREE.ConeGeometry(1, 2, 3); // 创建材质 var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 创建网格对象 var cone = new THREE.Mesh(geometry, material); // 将三棱锥添加到场景 scene.add(cone); // 渲染场景和相机 function animate() { requestAnimationFrame(animate); cone.rotation.x += 0.01; cone.rotation.y += 0.01; renderer.render(scene, camera); } animate(); ``` 这段代码创建了一个场景、相机和渲染器,并使用ConeGeometry创建了一个三棱锥的几何体。然后,将几何体和材质创建一个网格对象,并将其添加到场景。最后,通过渲染器将场景和相机渲染到HTML页上。 希望这个示例能帮助你理解如何使用Three.js绘制三棱锥。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值