跳至主要內容

ThreeJS入门

yyshino大约 6 分钟

ThreeJS入门学习

概念

基础知识

场景:

// 1. 创建场景
const scene = new THREE.Scene();

相机:

// 1. 创建场景
const scene = new THREE.Scene();

// 2. 创建相机
// const camera = new THREE.Camera()
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度
// aspect — 摄像机视锥体长宽比
// near — 摄像机视锥体近端面
// far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 透视相机
// 添加相机到场景中,否则不会显示
scene.add(camera);

物体|材质|纹理:

// 导入纹理
const textureLoader = new THREE.TextureLoader();
const doorColorTexture = textureLoader.load('./textures/door/color.jpg');

// 添加物体
const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
// 材质
const basicMaterial = new THREE.MeshBasicMaterial({
  color: '#ffff00'
});
// 创建几何体 点线面
const cube = new THREE.Mesh(cubeGeometry, basicMaterial);
// 将物体添加到场景中,否则不会显示
scene.add(cube);

渲染器:

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// console.log(renderer);
// 将webgl渲染的canvas内容选择到body上
document.body.appendChild(renderer.domElement);

function render() {
  controls.update()
  // 相机旋转
  renderer.render(scene, camera);
  // 渲染下一帧的时候就会调用render函数
  requestAnimationFrame(render);
  // window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
  // 参数为需要触发的函数
};
render();

辅助工具:GUI(第三方)、坐标轴辅助器

dat.gui

// 导入dat.gui
import * as dat from "dat.gui";

// 创建对象
const gui = new dat.GUI()

// 添加属性
gui
  .add(directionalLight.shadow.camera, "near")
  .min(0)
  .max(10)
  .step(0.1)
  .onChange(() => {
    // 请注意,在大多数属性发生改变之后,你将需要调用.updateProjectionMatrix来使得这些改变生效。
    directionalLight.shadow.camera.updateProjectionMatrix();
  })

灯光阴影

需满足以下条件:

// 目标:灯光与阴影
// 灯光阴影
// 1、材质要满足能够对光照有反应
// 2、设置渲染器开启阴影的计算 renderer.shadowMap.enabled = true;
// 3、设置光照投射阴影 directionalLight.castShadow = true;
// 4、设置物体投射阴影 sphere.castShadow = true;
// 5、设置物体接收阴影 plane.receiveShadow = true;

粒子效果

将粒子绑定在普通几何体上

// 绑定的几何体
const sphereGeometry = new THREE.SphereBufferGeometry(3, 30, 30);
// 设置点材质的大小
const pointsMaterial = new THREE.PointsMaterial()
pointsMaterial.size = 0.05;
pointsMaterial.color.set(0xfff000);
// pointsMaterial.sizeAttenuation = false; // 是否因相机的距离而衰减

// 载入纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("./textures/particles/2.png");
// 设置点材质纹理
pointsMaterial.map = texture; // 使用来自Texture的数据设置点的颜色。可以选择包括一个alpha通道,通常与 .transparent或.alphaTest。
pointsMaterial.alphaMap = texture; // alpha贴图是一张灰度纹理,用于控制整个表面的不透明度。(黑色:完全透明;白色:完全不透明)。 默认值为null。
pointsMaterial.transparent = true; // 定义此材质是否透明。这对渲染有影响,因为透明对象需要特殊处理,并在非透明对象之后渲染。设置为true时,通过设置材质的opacity属性来控制材质透明的程度。默认值为false。
pointsMaterial.depthWrite = false; // 渲染此材质是否对深度缓冲区有任何影响。默认为true。
pointsMaterial.blending = THREE.AdditiveBlending; // 在使用此材质显示对象时要使用何种混合。必须将其设置为CustomBlending才能使用自定义blendSrc, blendDst 或者 [page:Constant blendEquation]。 混合模式所有可能的取值请参阅constants。默认值为NormalBlending。

// 设置点粒子绑定的几何体和材质
const points = new THREE.Points(sphereGeometry,pointsMaterial)
// 将粒子添加到场景
scene.add(points);

将粒子绑定在BufferGeometry上

BufferAttribute( array : TypedArray, itemSize : Integer, normalized : Boolean )

array -- 必须是 TypedArrayopen in new window. 类型,用于实例化缓存。 该队列应该包含:itemSize * numVertices个元素,numVertices 是 BufferGeometryopen in new window中的顶点数目

itemSize -- 队列中与顶点相关的数据值的大小。举例,如果 attribute 存储的是三元组(例如顶点空间坐标、法向量或颜色值)则itemSize的值应该是3。

normalized -- (可选) 指明缓存中的数据如何与GLSL代码中的数据对应。例如,如果array是 UInt16Array类型,且normalized的值是 true,则队列中的值将会从 0 - +65535 映射为 GLSL 中的 0.0f - +1.0f。 如果array是 Int16Array (有符号),则值将会从 -32768 - +32767 映射为 -1.0f - +1.0f。若 normalized 的值为 false,则数据映射不会归一化,而会直接映射为 float 值,例如,32767 将会映射为 32767.0f.

// 是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。使用 BufferGeometry 可以有效减少向 GPU 传输上述数据所需的开销。
// 读取或编辑 BufferGeometry 中的数据,见 BufferAttribute 文档。
const particlesGeometry = new THREE.BufferGeometry();
// 粒子数量
const count = 5000;

// Float32Array 类型数组代表的是平台字节顺序为 32 位的浮点数型数组 (对应于 C 浮点数据类型) 。如果需要控制字节顺序,使用 DataView 替代。其内容初始化为 0。一旦建立起来,你可以使用这个对象的方法对其元素进行操作,或者使用标准数组索引语法 (使用方括号)。

// 设置缓存区数组
const positions = new Float32Array(count * 3);
// 设置顶点颜色
const colors = new Float32Array(count * 3);
// 设置顶点
for (let i = 0; i < count * 3; i++) {
    // x,y,z坐标随机
  positions[i] = (Math.random() - 0.5) * 100 - 5;
    // 颜色随机
  colors[i] = Math.random();
};

// setAttribute:为当前几何体设置一个 attribute 属性。在类的内部,有一个存储 .attributes 的 hashmap, 通过该 hashmap,遍历 attributes 的速度会更快。而使用该方法,可以向 hashmap 内部增加 attribute。 所以,你需要使用该方法来添加 attributes。
// 这个类用于存储与BufferGeometry相关联的 attribute(例如顶点位置向量,面片索引,法向量,颜色值,UV坐标以及任何自定义 attribute )。 利用 BufferAttribute,可以更高效的向GPU传递数据。详情和例子见该页。

// 在 BufferAttribute 中,数据被存储为任意长度的矢量(通过itemSize进行定义),下列函数如无特别说明, 函数参数中的index会自动乘以矢量长度进行计算。 当想要处理类似向量的数据时, 可以使用在Vector2,Vector3, Vector4以及Color这些类中的.fromBufferAttribute( attribute, index ) 方法来更为便捷地处理。
particlesGeometry.setAttribute(
  "position",
  new THREE.BufferAttribute(positions, 3)
);
particlesGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
// 设置点材质
const pointsMaterial = new THREE.PointsMaterial();
pointsMaterial.size = 0.5;
// pointsMaterial.color.set(0xfff000);
// 相机深度而衰减
pointsMaterial.sizeAttenuation = true;

// 载入纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("./textures/particles/1.png");
// 设置点材质纹理
pointsMaterial.map = texture;
pointsMaterial.alphaMap = texture;
pointsMaterial.transparent = true;
pointsMaterial.depthWrite = false;
pointsMaterial.blending = THREE.AdditiveBlending;
// 设置启动顶点颜色
pointsMaterial.vertexColors = true;
// console.log(THREE.VertexColors);

const points = new THREE.Points(particlesGeometry, pointsMaterial)
scene.add(points);

光线投射Raycaster

const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
  wireframe: true,
});
const redMaterial = new THREE.MeshBasicMaterial({
  color: "#ff0000",
});

// 1000立方体
let cubeArr = [];
for (let i = -5; i < 5; i++) {
  for (let j = -5; j < 5; j++) {
    for (let z = -5; z < 5; z++) {
      const cube = new THREE.Mesh(cubeGeometry, material);
      cube.position.set(i, j, z);
      scene.add(cube);
      cubeArr.push(cube);
    }
  }
}

// 创建投射光线对象
const raycaster = new THREE.Raycaster();

// 鼠标的位置对象
const mouse = new THREE.Vector2();

// 监听鼠标的位置
// window.addEventListener("mousemove", (event) => {
//   //   console.log(event);
//   mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
//   mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
//   raycaster.setFromCamera(mouse, camera);
//   let result = raycaster.intersectObjects(cubeArr);
//   //   console.log(result);
//   //   result[0].object.material = redMaterial;
//   result.forEach((item) => {
//     item.object.material = redMaterial;
//   });
// });

// 监听鼠标的位置
window.addEventListener("click", (event) => {
  //   console.log(event);
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
  raycaster.setFromCamera(mouse, camera);
  let result = raycaster.intersectObjects(cubeArr);
  //   console.log(result);
  //   result[0].object.material = redMaterial;
  result.forEach((item) => {
    item.object.material = redMaterial;
  });
});