V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
flyPig9527
V2EX  ›  V2EX

pixijs + webgl 实现城市交通模拟

  •  1
     
  •   flyPig9527 · 2022-10-24 17:05:05 +08:00 · 1377 次点击
    这是一个创建于 747 天前的主题,其中的信息可能已经有所发展或是发生改变。

    交通模拟器

    汽车的行驶以及等红绿灯的场景进行模拟,使用了pixiwebgl去实现的。
    webgl 主要是用在 pixi 中 spite 的着色器的编写。

    预览

    github 预览地址
    gitee 预览地址
    网速慢的慢的话建议gitee 预览地址,效果会更好
    如果感觉不错的话,给 star⭐️

    界面

    功能

    功能

    功能标注 👉🏻右侧区域:

    • 可以看到当前的帧率
    • 暂停按钮可以让动画暂停,再次点击即可继续动画

    👈🏻左侧区域: 汽车的模式目前是有四种:原始模式多彩模式多彩闪光模式简笔画模式

    • 多彩模式是可以让原始的汽车颜色进行更换多种颜色的更换
    • 多彩闪光模式是让汽车颜色一直随机变
    • 简笔画模式是让汽车是简笔画的形态 这三部分是通过pixisprite用的webgl来写了一部分sharder完成的。

    🚗车辆管理区域:
    目前就两款车型,可以控制道路上行驶的车辆类型,禁用可以让该类型的车辆不在道路上行驶,启用则是相反,允许该车辆行驶。

    技术实现

    首先车辆的行驶方向是上下左右四个方向,这四个方向的车辆我这里是采用了链表的数据结构。
    为什么要使用链表这种数据结构呢?
    回答:老子乐意!!!
    其实是平时只有刷算法题的时候才用到链表,平时工作用不到,所以就想用用链表来实现。当然用数组也能实现。由于车辆要不断地删除添加的操作所以链表的效率会更高一些。这个项目中用到的链表也挺不难,就是链表的添加和删除,会这两个就能进行车辆的添加和删除。

    车辆添加

    车辆是pixijssprite,每种类型的车辆都是分为上下左右四张图片。

    未命名.png

    添加车辆就是在链表的最后添加上 sprite ,存储数据是 carData,它是个 useRef 。是分为left right top bottom四个字段,代表四个方向,每个字段都是一个链表,是每个方向的车辆。根据每个方向要对 sprite 进行 x ,y 点进行初始化,根据前一个链表节点的 x 和 y ,计算添加车辆的 x 和 y 。

    // direction: 方向  WIDTH 画布的宽度 HEIGHT 画布的高度  ROADWIDTH 道路的宽度
    // 该链表如果存在节点最后添加节点,没有节点直接放入
    if (carData.current[direction]) {
      let current = carData.current[direction]!;
      // 拿到最后一个节点
      while (current.next) {
        current = current.next!;
      }
      // 根据最后一个节点计算出要添加节点的位置
      switch (direction) {
        case 'left':
          sprite.y = (HEIGHT - ROADWIDTH) / 2 + ROADWIDTH / 4;
          if (current.val.x >= WIDTH - CARLENGTH / 2) {
            sprite.x = current.val.x + CARLENGTH * 1.5;
          } else {
            sprite.x = WIDTH - CARLENGTH / 2;
          }
          break;
        case 'right':
          sprite.y = HEIGHT / 2 + ROADWIDTH / 4;
          if (current.val.x <= -CARLENGTH / 2) {
            sprite.x = current.val.x - CARLENGTH * 1.5;
          } else {
            sprite.x = -CARLENGTH / 2;
          }
          break;
        case 'top':
          sprite.x = WIDTH / 2 + ROADWIDTH / 4;
          if (current.val.y >= HEIGHT + CARLENGTH / 2) {
            sprite.y = current.val.y + CARLENGTH * 1.5;
          } else {
            sprite.y = HEIGHT + CARLENGTH / 2;
          }
          break;
        case 'bottom':
          sprite.x = WIDTH / 2 - ROADWIDTH / 4;
          if (current.val.y <= -CARLENGTH / 2) {
            sprite.y = current.val.y - CARLENGTH * 1.5;
          } else {
            sprite.y = -CARLENGTH / 2;
          }
          break;
      }
      current.next = new ListNode(sprite);
    } else {
      if (direction === 'left') { // 向左行驶的车辆位置
        sprite.y = (HEIGHT - ROADWIDTH) / 2 + ROADWIDTH / 4;
        sprite.x = WIDTH - CARLENGTH / 2;
      } else if (direction === 'right') { // 向右行驶的车辆位置
        sprite.y = HEIGHT / 2 + ROADWIDTH / 4;
        sprite.x = -CARLENGTH / 2;
      } else if (direction === 'top') {  // 向上行驶的车辆位置
        sprite.x = WIDTH / 2 + ROADWIDTH / 4;
        sprite.y = HEIGHT + CARLENGTH / 2;
      } else if (direction === 'bottom') {  // 向下行驶的车辆位置
        sprite.x = WIDTH / 2 - ROADWIDTH / 4;
        sprite.y = -CARLENGTH / 2;
      }
      carData.current[direction] = new ListNode(sprite);
    }
    

    车辆行驶

    车辆行驶需要考虑几点:

    • 当前面红灯怎么办?
    • 后面的车辆要撞到前一个车辆怎么办?
    • 为了灵活性,汽车什么时候加速,什么时候减速?

    下面一一解答:

    • 当为红灯的时候,下一步就要闯红灯了,这时需要让车辆停止,位置一直保持为原来的位置
    • 后面的车要撞到前面的车,有两种情况:一种是前面的车在等红绿灯,另一种是都是行驶状态,后面车的车速大于前面车的车速。这时候需要设置一个阈值 min,当后车与前车的距离小于这个min的时候,需要让后车相对于前车速度进行减速,如果减速还是会小于这个min的话,说明前车在等红绿灯,这时候后车的位置等于这个min就行了,保证不小于这个min
    • 上面说设置一个车距之间的最小阈值 min,是为了让车减速,加速则是要设置车距之间的最大阈值 max,车距超过这个max就进行加速操作。
      代码就不展示了,主要是链表的遍历去对每辆车进行计算。

    车辆模式

    因为pixijs默认是使用webgl去进行渲染的,sprite支持使用 webgl 编写着色器的。根据选择的不同模式进行着色器 sharder 的编写完成的。

    多彩模式:生成一个随机的 rgb三个通道的值,然后到通过uniform传入到片元着色器与当前的色值进行相乘,深色部分打算是保留下来的,所以设置一个阈值,超过这个阈值代表浅色,浅色部分会去跟随机 rgb 进行相乘。

    多彩闪光模式:和多彩模式实现是一样的,区别是动画每帧都会使用多彩,所以有了多彩闪光的一个效果。

    简笔画模式:这个是我写多彩模式的 sharder 无意间实现出来的,原理也是很简单的,将浅色部分都变成白色,只留下深色部分,就是简笔画的效果。

    /**
     * 汽车颜色滤镜
     * @param sprite
     */
    export const carFilterColor = (sprite: Sprite, type?: number) => {
      const fragStr = `
        varying vec2 vTextureCoord;
        uniform sampler2D uSampler;
        uniform vec2 size;
        uniform vec3 secondaryColor;
        uniform float type;
        
        void main(void){
          vec2 uv = size;
          vec4 color = texture2D(uSampler, vTextureCoord);
          // 阈值
          float num = 0.3;
          if(type != 0.0){
            if(color.r > num || color.g  >= num || color.b >= num){
                if(type == 1.0 || type == 2.0 ){ / 多彩或多彩闪光模式
                    color.rgb *= secondaryColor;
                }else if (type == 3.0){  // 简笔画模式
                    color.rgb = vec3(1.0);
                }
            }
            color.rgb = clamp(vec3(0.0),color.rgb,vec3(1.0));
          }
          gl_FragColor = color;
        }
      `;
      let filter = new PIXI.Filter(undefined, fragStr, {
        secondaryColor: [
          Math.random() + 0.6,
          Math.random() + 0.6,
          Math.random() + 0.6,
        ],
        type: type || 0,
      });
      sprite.filters = [filter];
    };
    

    还有一些细节点就不一一赘述了,如:交通灯的设计和控制、车辆类型的控制等等。

    第 1 条附言  ·  2022-10-25 09:58:14 +08:00

    项目的GitHub地址忘了发出来了, https://github.com/dearDreamWeb/traffic_simulator.github.io
    喜欢的话,请给个star

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1766 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 16:38 · PVG 00:38 · LAX 08:38 · JFK 11:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.