无限灯光(1)

翻译自: https://tympanus.net/codrops/2019/11/13/high-speed-light-trails-in-three-js/

代码: https://github.com/dslming/learningComputerGraphics/tree/master/ThreejsLearning/017-InfiniteLights

无限灯光

灵感来自 海报
在这里插入图片描述

[完整代码]( step6-end 终结)
最后效果:
在这里插入图片描述

第一阶段

[完整代码](step1-Lights radius and length)
最后效果:
在这里插入图片描述

1、准备舞台

Three.js 工作的必要准备。

export class Stage {
  constructor(container) {
    this.container = container;
    // 场景
    this.scene = new THREE.Scene();
    this.scene.name = "moumade";
    window.scene = this.scene;

    // 环境光
    var ambient = new THREE.AmbientLight(0xffffff, 1);
    ambient.name = "ambient";
    this.scene.add(ambient);

    // 渲染器
    this.containerEle = document.querySelector(container);
    let vW = this.containerEle.clientWidth;
    let vH = this.containerEle.clientHeight;
    this.renderer = new THREE.WebGLRenderer();
    this.renderer.setClearColor(0x000000, 0.0);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(vW, vH, false);
    this.containerEle.appendChild(this.renderer.domElement);

    // 相机
    this.camera = new THREE.PerspectiveCamera(45, this.containerEle.clientWidth / this.containerEle.clientHeight, 10, 2000);
    this.camera.name = "camera";
    this.camera.position.set(0, 7, -5)
    this.camera.rotation.set(0, 0, 0)
    this.scene.add(this.camera);
    window.addEventListener("resize", this.handleResize);
    this.handleResize();
  }

  handleResize() {
    // 获取新的大小
    let vpW = that.containerEle.clientWidth;
    let vpH = that.containerEle.clientHeight;
    // 设置场景
    that.renderer.domElement.width = vpW;
    that.renderer.domElement.height = vpH;
    that.renderer.setSize(that.containerEle.clientWidth, that.containerEle.clientHeight);
    // 设置相机
    that.camera.aspect = vpW / vpH;
    that.camera.updateProjectionMatrix();
  }


  run() {
    setInterval(v => {
      that.renderer.render(that.scene, that.camera);
    }, 200)
  }
}
2、准备道路

新建一个平面模拟道路,PlaneBufferGeometry 的宽高分别对应道路的宽和长。

我们希望这个平面垂直于屏幕相里延伸,但是在Tree.js场景中心创建了一个垂直的屏幕。因此我们需要让其沿X轴旋转以使其垂直于屏幕。

const options = {
  length: 200,
  width: 20,
};

const geometry = new THREE.PlaneBufferGeometry(
  options.width,
  options.length,
  10,
  500
);

var material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
mesh.position.z = -options.length / 2;
mesh.name = "road"
this.scene.add(mesh);

在这里插入图片描述

3、创建一个车灯
addObjLightsOne() {
  let curve = new THREE.LineCurve3(
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, 0, -1)
  );

  // 绘制一个圆柱体,圆柱体的半径1,圆柱体的走向为曲线curve,就是一个管道。
  let baseGeometry = new THREE.TubeBufferGeometry(curve, 64, 1, 8, false);
  let material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
  let mesh = new THREE.Mesh(baseGeometry, material);
  mesh.position.set(0, 0, -30)
  this.camera.position.set(0, 0, -5)

  this.scene.add(mesh)
}

这是一个管道,长度为1,方向为垂直屏幕向外。
在这里插入图片描述

4、创建更多的车灯
const options = {
  length: 200,
  width: 20,
  // 车道总宽 9
  roadWidth: 9,
  islandWidth: 2,
  // 圆环的对儿数
  nPairs: 10,
  // 一共3个车道
  roadSections: 3
};
addObjLightsNoBuffer(o) {
  const options = o
  // 每个车道的宽度 3
  let sectionWidth = options.roadWidth / options.roadSections;
  let material = new THREE.MeshBasicMaterial({ color: 0xfafafa });

  for (let i = 0; i < options.nPairs; i++) {
    let section = i % 3; // 0,1,2
    let sectionX =
      section * sectionWidth - options.roadWidth / 2 + sectionWidth / 2;
    let carWidth = 0.5 * sectionWidth;
    let offsetX = 0.5 * Math.random();
    let offsetY = 1.3;
    let offsetZ = Math.random() * options.length;

    let curve1 = {
      x: sectionX - carWidth / 2 + offsetX,
      y: offsetY,
      z: -offsetZ
    }

    let curve2 = {
      x: sectionX + carWidth / 2 + offsetX,
      y: offsetY,
      z: -offsetZ
    }
    // console.error(curve1, curve2);

    let curve11 = new THREE.LineCurve3(
      new THREE.Vector3(curve1.x, curve1.y, curve1.z - 1),
      new THREE.Vector3(curve1.x, curve1.y, curve1.z),
    );
    let curve22 = new THREE.LineCurve3(
      new THREE.Vector3(curve2.x, curve2.y, curve2.z - 1),
      new THREE.Vector3(curve2.x, curve2.y, curve2.z),
    );
    let geom11 = new THREE.TubeBufferGeometry(curve11, 25, 1, 8, true);
    let mesh11 = new THREE.Mesh(geom11, material);
    this.scene.add(mesh11)

    let geom22 = new THREE.TubeBufferGeometry(curve22, 25, 1, 8, true);
    let mesh22 = new THREE.Mesh(geom22, material);
    this.scene.add(mesh22)
  }
}

这种方法性能比较浪费。下面看改进的方法。
在这里插入图片描述

5、 InstancedBufferGeometry 创建多车灯
addObjLights(o) {
  const options = o

  // 三维线段曲线
  let curve = new THREE.LineCurve3(
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, 0, -1)
  );

  // 管道缓冲几何体 (path, tubularSegments, radius, radialSegments, closed)
  let baseGeometry = new THREE.TubeBufferGeometry(curve, 25, 1, 8, true);

  // 创建一个instancedBufferGeometry
  let instanced = new THREE.InstancedBufferGeometry().copy(baseGeometry);
  instanced.maxInstancedCount = options.nPairs * 2;

  let aOffset = [];
  // 每个车道的宽度 3
  let sectionWidth = options.roadWidth / options.roadSections;

  for (let i = 0; i < options.nPairs; i++) {
    // 1a. 获取车道索引,保持每个车道的灯光一致,而不是随机照明
    let section = i % 3; // 0,1,2

    // 1b. 获取车道的中心位置 -3,0,3
    let sectionX =
      section * sectionWidth - options.roadWidth / 2 + sectionWidth / 2;

    // 车宽是路宽的一半
    let carWidth = 0.5 * sectionWidth;

    // 生成 0~0.5的随机数
    let offsetX = 0.5 * Math.random();
    let offsetY = 1.3;
    let offsetZ = Math.random() * options.length;

    aOffset.push(sectionX - carWidth / 2 + offsetX);
    aOffset.push(offsetY);
    aOffset.push(-offsetZ);

    aOffset.push(sectionX + carWidth / 2 + offsetX);
    aOffset.push(offsetY);
    aOffset.push(-offsetZ);
  }


  // Add the offset to the instanced geometry.
  instanced.addAttribute(
    "aOffset",
    new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false)
  );

  const material = new THREE.ShaderMaterial({
    fragmentShader,
    vertexShader,
    uniforms: {
      uColor: new THREE.Uniform(new THREE.Color("0xfafafa"))
    }
  });
  let mesh = new THREE.Mesh(instanced, material);
  mesh.frustumCulled = false;
  mesh.name = "carLights"
  this.scene.add(mesh);
}

效果应该和上面的是相同的。
在这里插入图片描述

6、InstancedBufferGeometry 的用法
const fragmentShader = `
uniform vec3 uColor;
  void main() {
      vec3 color = vec3(uColor);
      gl_FragColor = vec4(color,1.);
  }
`;

const vertexShader = `
attribute vec3 aOffset;
  void main() {
    vec3 transformed = position.xyz;
    transformed.xyz += aOffset.xyz;
    vec4 mvPosition = modelViewMatrix * vec4(transformed,1.);
    gl_Position = projectionMatrix * mvPosition;
	}
`;

addObjLightsInstanced(o) {
  // 三维线段曲线
  let curve = new THREE.LineCurve3(
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, 0, -1)
  );

  // 管道缓冲几何体 (path, tubularSegments, radius, radialSegments, closed)
  let baseGeometry = new THREE.TubeBufferGeometry(curve, 25, 1, 8, true);

  // 创建一个instancedBufferGeometry
  let instanced = new THREE.InstancedBufferGeometry().copy(baseGeometry);

  let aOffset = [0, 0, -50, 0, 0, -80, 0, 0, -100];
  let arr = new Float32Array(aOffset)
  instanced.addAttribute(
    "aOffset",
    new THREE.InstancedBufferAttribute(arr, 3, false)
  );

  const material = new THREE.ShaderMaterial({
    fragmentShader,
    vertexShader,
    uniforms: {
      uColor: new THREE.Uniform(new THREE.Color("0xfafafa"))
    }
  });
  let mesh = new THREE.Mesh(instanced, material);
  // 相机视界之外的会被踢出
  mesh.frustumCulled = false;
  mesh.name = "carLights"
  this.scene.add(mesh);
}

原理是对单个车灯进行平移,然后产生了3个管道。
在这里插入图片描述

<全文结束, 多多点赞会变好看, 多多评论会变有钱>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值