ThreeJS-3D教学二:基础形状展示

three中提供了22 个基础模型,此案例除了 EdgesGeometry、ExtrudeGeometry、TextGeometry、WireframeGeometry,涵盖 17 个形状。

 Fog  雾化设置,这是scene场景效果
 
 EdgesGeometry , WireframeGeometry 更多地可能作为辅助功能去查看几何体的边和线框
 
 ExtrudeGeometry 则是将一个二维图形沿 z 轴拉伸出一个三维图形
 
 TextGeometry 则需要从外部加载特定格式的字体文件(可在 typeface.js_three 网站上进行转换)进行渲染,其内部依然使用 ExtrudeGeometry 对字体进行拉伸,从而形成三维字体。另外,该类字体的本质是一系列类似SVG 的指令。所以,字体越简单(如直线越多),就越容易被正确渲染。

 planeGeometry  底层
  
 BoxGeometry(长方体)
 
 CircleGeometry(圆形)
 
 ConeGeometry(圆锥体)
 
 CylinderGeometry(圆柱体)
 
 DodecahedronGeometry(十二面体)
 
 IcosahedronGeometry(二十面体)

 LatheGeometry(让任意曲线绕 y 轴旋转生成一个形状,如花瓶)
 
 OctahedronGeometry(八面体)

 ParametricGeometry(根据参数生成形状)
 
 PolyhedronGeometry(多面体)

 RingGeometry(环形)
 
 ShapeGeometry(二维形状)

 SphereGeometry(球体)

 TetrahedronGeometry(四面体)
 
 TorusGeometry(圆环体)
 
 TorusKnotGeometry(换面纽结体)
 
 TubeGeometry(管道)

我们先看下效果图
在这里插入图片描述
看代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      width: 100%;
      height: 100%;
    }
    * {
      margin: 0;
      padding: 0;
    }
    .label {
      font-size: 20px;
      color: #000;
      font-weight: 700;
    }
  </style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
  {
    "imports": {
      "three": "../three-155/build/three.module.js",
      "three/addons/": "../three-155/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { createMultiMaterialObject } from 'three/addons/utils/SceneUtils.js';
import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
import { ParametricGeometries } from 'three/addons/geometries/ParametricGeometries.js';
let stats, labelRenderer, gpuPanel;
let camera, scene, renderer, mesh, target, controls, meshs = [];
const group = new THREE.Group();
/**
 在 Three.js 中可以渲染着色器的材质有两种:RawShaderMaterial 和 ShaderMaterial,
 它们之间的区别是 ShaderMaterial 会自动将一些初始化着色器的参数添加到代码中(内置 attributes 和 uniforms),
 而 RawShaderMaterial 则什么都不会添加。
 */
init();
initHelp();
initLight();
axesHelperWord();
animate();

meshs = addGeometries();
addPlane();

function addPlane() {
  // 创建一个平面 PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(120, 140, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshLambertMaterial({
    color: 0xffffff
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  scene.add(plane);
}

function addGeometries() {
  const geoms = [];
  const meshs = [];
  // 共 22 个形状,除了 EdgesGeometry、ExtrudeGeometry、TextGeometry、WireframeGeometry,下面涵盖 17 个(PlaneGeometry在上面)
  // Box 长方体
  geoms.push(new THREE.BoxGeometry(8, 8, 8));

  // Circle 圆形
  geoms.push(new THREE.CircleGeometry(8, 32));

  // Cone 圆锥体
  geoms.push(new THREE.ConeGeometry(8, 20, 32));

  // Cylinder 圆柱体
  geoms.push(new THREE.CylinderGeometry(5, 5, 20, 32));

  // Dodecahedron 十二面体
  geoms.push(new THREE.DodecahedronGeometry(8));

  // Icosahedron 二十面体
  geoms.push(new THREE.IcosahedronGeometry(8));

  // Lathe 让任意曲线绕 y 轴旋转生成一个形状,如花瓶
  const lathePoints = [];
  for (let i = 0; i < 10; i++) {
    lathePoints.push(new THREE.Vector2(Math.sin(i * 0.2) * 5 + 2.5, (i - 5) * 2));
  }
  geoms.push(new THREE.LatheGeometry(lathePoints));

  // Octahedron 八面体
  geoms.push(new THREE.OctahedronGeometry(8));

  // Parametric:根据参数生成形状,THREE.ParametricGeometries.klein 是 ParametricGeometries.js_three 库提供
  geoms.push(new ParametricGeometry(ParametricGeometries.klein, 10, 10));

  const verticesOfCube = [
    -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
    -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
  ];

  const indicesOfFaces = [
    2, 1, 0, 0, 3, 2,
    0, 4, 7, 7, 3, 0,
    0, 1, 5, 5, 4, 0,
    1, 2, 6, 6, 5, 1,
    2, 3, 7, 7, 6, 2,
    4, 5, 6, 6, 7, 4
  ];

  // Polyhedron 多面体
  geoms.push(new THREE.PolyhedronGeometry(verticesOfCube, indicesOfFaces, 8, 1));

  // Ring 环形
  geoms.push(new THREE.RingGeometry(3, 8, 16));

  // Shape 二维形状(心形)
  let x = 0, y = 0;
  let heartShape = new THREE.Shape();

  heartShape.moveTo(x + 5, y + 5);
  heartShape.bezierCurveTo(x + 5, y + 5, x + 4, y, x, y);
  heartShape.bezierCurveTo(x - 6, y, x - 6, y + 7, x - 6, y + 7);
  heartShape.bezierCurveTo(x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19);
  heartShape.bezierCurveTo(x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7);
  heartShape.bezierCurveTo(x + 16, y + 7, x + 16, y, x + 10, y);
  heartShape.bezierCurveTo(x + 7, y, x + 5, y + 5, x + 5, y + 5);

  geoms.push(new THREE.ShapeGeometry(heartShape));

  // Sphere 球体
  geoms.push(new THREE.SphereGeometry(8, 24, 24));

  // Tetrahedron 四面体
  geoms.push(new THREE.TetrahedronGeometry(8));

  // Torus 圆环体
  geoms.push(new THREE.TorusGeometry(6, 2, 16, 100));

  // TorusKnot 环面纽结体
  geoms.push(new THREE.TorusKnotGeometry(6, 2, 100, 16));

  // Tube 管道
  const points = [];
  for (let i = 0; i < 5; i++) {
    const randomX = -10 + Math.round(Math.random() * 20);
    const randomY = -7 + Math.round(Math.random() * 20);
    const randomZ = -10 + Math.round(Math.random() * 20);
    points.push(new THREE.Vector3(randomX, randomY, randomZ));
  }

  geoms.push(new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), 20, 2, 8, false));
  let j = 0;
  // 为几何体添加材质
  for (let i = 0; i < geoms.length; i++) {
    let materials = [
      // 内侧
      new THREE.MeshLambertMaterial({
        color: Math.random() * 0xffffff,
        flatShading: true, // 定义是否使用平面着色渲染材质
        side: THREE.DoubleSide,
        wireframe: true // 将几何体渲染为线框。默认值为false(即渲染为平面多边形)
      }),
      // 外侧
      new THREE.MeshBasicMaterial({
        color: Math.random() * 0xffffff,
        wireframe: false
      })
    ];

    let mesh = createMultiMaterialObject(geoms[i], materials);

    meshs.push(mesh);

    // 设置每个物体的位置
    mesh.position.x = -36 + ((i % 4) * 24);
    mesh.position.y = 4;
    mesh.position.z = -36 + (j * 24);

    if ((i + 1) % 4 == 0) {
      j++
    }
    scene.add(mesh)
  }
  return meshs
}

function init() {

  camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 10, 2000 );
  camera.position.set(0, 125, 125);
  camera.up.set(0, 1, 0);
  camera.lookAt(0, 0, 0);

  scene = new THREE.Scene();
  scene.background = new THREE.Color( '#ccc' );

  // Fog( hex, near, far ),线性雾化。
  // near 表示哪里开始应用雾化效果
  // far 表示雾化效果在哪里结束
  scene.fog = new THREE.Fog(0xffffff, 0.015, 100);
  // FogExp2( hex, density ),指数雾化
  // density 是雾化强度
  scene.fog = new THREE.FogExp2(0xffffff, 0.005); //0.01 雾化就很重了
  // 雾化效果默认是全局影响的,若某个材质不受雾化效果影响,则可为材质的 fog 属性设置为 false(默认值 true)
  // var material = new THREE.Material({
  //   fog: false
  // });

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize( window.innerWidth, window.innerHeight );
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.getElementById( 'container' ).appendChild( labelRenderer.domElement );

  window.addEventListener( 'resize', onWindowResize );

  controls = new OrbitControls( camera, renderer.domElement );
  controls.minDistance = 10;
  controls.maxDistance = 1000;
  // 设置为true可启用阻尼(惯性),可用于为控件提供重量感。默认值为false。
  // 请注意,如果启用了此选项,则必须在动画循环中调用.update()。
  controls.enableDamping = false;
  controls.screenSpacePanning = false; // 定义平移时如何平移相机的位置 控制不上下移动

  stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  document.body.appendChild( stats.dom );

  gpuPanel = new GPUStatsPanel( renderer.getContext() );
  stats.addPanel( gpuPanel );
  stats.showPanel( 0 );

  scene.add( group );
}

function initLight() {
  const light = new THREE.DirectionalLight(new THREE.Color('rgb(253,253,253)'));
  light.position.set(100, 100, -10);
  light.intensity = 3; // 光线强度
  light.castShadow = true; // 是否有阴影
  light.shadow.mapSize.width = 2048; // 阴影像素
  light.shadow.mapSize.height = 2048;
  // 阴影范围
  const d = 80;
  light.shadow.camera.left = -d;
  light.shadow.camera.right = d;
  light.shadow.camera.top = d;
  light.shadow.camera.bottom = -d;
  light.shadow.bias = -0.0005; // 解决条纹阴影的出现
  // 最大可视距和最小可视距
  light.shadow.camera.near = 0.01;
  light.shadow.camera.far = 2000;
  const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'));
  scene.add( light );
  scene.add( AmbientLight );
}

function initHelp() {
  // const size = 100;
  // const divisions = 5;
  // const gridHelper = new THREE.GridHelper( size, divisions );
  // scene.add( gridHelper );

  // The X axis is red. The Y axis is green. The Z axis is blue.
  const axesHelper = new THREE.AxesHelper( 100 );
  scene.add( axesHelper );
}

function axesHelperWord() {
  let xP = addWord('X轴');
  let yP = addWord('Y轴');
  let zP = addWord('Z轴');
  xP.position.set(50, 0, 0);
  yP.position.set(0, 50, 0);
  zP.position.set(0, 0, 50);
}

function addWord(word) {
  let name = `<span>${word}</span>`;
  let moonDiv = document.createElement( 'div' );
  moonDiv.className = 'label';
  // moonDiv.textContent = 'Moon';
  // moonDiv.style.marginTop = '-1em';
  moonDiv.innerHTML = name;
  const label = new CSS2DObject( moonDiv );
  group.add( label );
  return label;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );

  // 各个网格(mesh)绕自身中心的 y 轴自转
  if (meshs.length > 0) {
    for (let i = 0; i < meshs.length; i++) {
      meshs[i].rotation.y += 0.01;
    }
  }

  stats.update();
  controls.update();
  labelRenderer.render( scene, camera );
  renderer.render( scene, camera );
}
</script>
</body>
</html>

直接复制粘贴到html文件即可看到效果,当然前提是已经提前下载了three的代码库,然后在script type="importmap"标签中改下路径就可以了。
到这一步,我们开始往场景 scene 中添加模型,也就是mesh,案例中用到了 SceneUtils.js 中的createMultiMaterialObject 创建 mesh,这个在官网中用法是这样的:
在这里插入图片描述
我们发现按官网看调用应该是SceneUtils.createMultiMaterialObject,但是这样会报错,我们看three 155版本中发现源码为:
在这里插入图片描述
看来新的版本已经更简洁了直接使用createMultiMaterialObject即可
从这个解错的过程中,我们可以总结一些规律,当我们发现有时官网提供的方法也会报错,解错思路是什么呢?
1、百度查问题,事实我也是这么做的,但由于这是最新版本,查询的结果大家也是推荐的官网用法,一通折腾没结果,这也是使用最新版本可能出现的一些无法预知的问题
2、这时大家可以调转思路,为什么不看一眼源码,虽然大部分源码晦涩难懂,多层嵌套,比较难发现问题,本身我也不喜欢读源码,但结果是看完源码几秒钟这个问题就解决了,所以当我们遇到问题,第一步不一定要百度,看一眼源码也是很好的

因为要写案例,在这个过程中遇到了一些问题,我就把解决问题的思路写下来分享给大家,我想这远比教会大家怎么实现效果更有助于大家成长。

回到主题我们在讲createMultiMaterialObject做了什么时,先看下一般情况下mesh是怎么做的,在代码中有一个addPlane函数:

  // 创建一个平面  PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(120, 140, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshLambertMaterial({
    color: 0xffffff
  });
  // 这里new THREE.Mesh 大部分情况我们都是这样做的
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  scene.add(plane);

Mesh

表示基于三角形多边形网格的对象的类。还可以作为其他类(如SkinnedMesh)的基础。

很显然PlaneGeometry 和 MeshLambertMaterial大家可能是第一次见到

Geometry

对原生WebGL中的顶点位置position、顶点法向量normal、顶点颜色color、顶点纹理坐标uv、顶点索引index等顶点数据进行了封装
立方体BoxGeometry、圆柱体CylinderGeometry、球体SphereGeometry等Three.js几何体类都是基于基类BufferGeometry二次封装

Materials

材质就像对象的蒙皮。它定义了几何图形的外观。Three.js提供了许多工作材料。我们应该根据我们的需要选择材料的类型。

Three.js中最常用的材料:

1、MeshBasicMateria
Three.js中非常基本的材料

2、MeshDepthMaterial
它使用与摄影机的距离来确定如何以灰度为网格着色

3、MeshNormalMaterial
此材质使用面法线向量的x/y/z值的大小来计算和设置面上显示的颜色的红/绿/蓝值

4、MeshLambertMaterial
您可以使用此材质创建外观暗淡、无光泽的表面

5、MeshPhongMaterial
此材质类似于MeshLambertMaterial,但可以创建更有光泽的曲面

6、MeshStandardMaterial
它与MeshLambertMaterial或MeshPhongMaterial类似,但提供了更准确、更逼真的外观结果。它有两个特性:粗糙度和金属性,而不是光泽

7、MeshPhysicalMaterial
它与MeshStandardMaterial非常相似。可以控制材质的反射率

以上介绍了const plane = new THREE.Mesh(planeGeometry, planeMaterial);各个部分的大概定义,
所以可以看到一个立方体就是由geometry(什么形状)、materia(什么样子),代入到Mesh中实现。
后面对各个材质大部分都会用到,具体的后面再说,想了解更深入些,大家可以用这个例子根据官方文档依次使用下,想学会这点主观能动性还是要有的

createMultiMaterialObject再看这个就按理解了,官方定义是
为材质中定义的每个材质创建一个包含新网格的新组。请注意,这与为1个网格定义多个材质的材质数组不同。这对于同时需要材质和线框实现的对象非常有用。其实就是可以实现一个模型同时具有两种材质的效果。

猜你喜欢

转载自blog.csdn.net/weixin_44384273/article/details/133071271