threejs 记录风的样子

2023-12-19 22:27:30

threejs 记录风的样子

风的样子还是非常漂亮的,很多网站都有用风力图来展示风向和风速,一直觉得还挺有意思的,之前研究过一阵子,不过后来放下就忘记了,最近又开始捣鼓threejs,又想起来弄这个来了,老样子,先上效果图。

请添加图片描述

项目地址

项目基于vue+threejs。

思路

大体思路如下:

  1. 在屏幕上随机生成很多的点。
  2. 根据风力场数据更新点的位置。
  3. 风速快的地方标成红色,要是弄成颜色映射就更好了,下期再优化一下。
  4. 新建一个纹理显示在后边记录风的行迹。

实现

1. 随机生成点

首先,我们需要生成很多的点,以便于后续计算风力。

var colorData = []
var particleCount = 10000;
for (var i = 0; i < particleCount; i++) {
    var x = THREE.MathUtils.randFloatSpread(window.innerWidth );
    var y = THREE.MathUtils.randFloatSpread(window.innerHeight );
    var z = 0;
    positionVertices.push(x, y, z);
    colorData.push(1,1,1)
}

geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionVertices, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorData, 3));

var material = new THREE.PointsMaterial({vertexColors: true})
var points = new THREE.Points(geometry, material);
scene.add(points);

其中的 vertexColors 是必须要设置的,意思是使用顶点的颜色,如果不设置的话就无法修改点的颜色。

2. 根据风力场数据更新点的位置

接下来,我们需要根据风力场数据更新点的位置。并且根据风速来标记成红色。

风场数据用的 Echart 例子的,不过我也不太了解风场的坐标关系,就一股脑给画上去了,以后再研究一下数据代表什么意思吧,那样才能把点画到正确的地方。

function animate() {
  requestAnimationFrame(animate);

  var positions = points.geometry.attributes.position.array;
  var colors = points.geometry.attributes.color.array;
  for (var i = 0; i < positions.length; i += 3) {
    // 随机删除一些点,避免都扎到一堆里边去
    if (Math.random() < 0.01) {
      let x = THREE.MathUtils.randFloatSpread(window.innerWidth );
      let y = THREE.MathUtils.randFloatSpread(window.innerHeight );
      positions[i] = x
      positions[i + 1] = y
      continue
    }

    let xratio = wind.nx / window.innerWidth;
    let yration =  wind.ny / window.innerHeight;
    let x = parseInt((positions[i] + window.innerWidth/2) * xratio)
    let y = parseInt((positions[i + 1] + window.innerHeight/2) * yration)

    if (wind.data[y*wind.nx + x]) {
      let deltax = wind.data[y*wind.nx + x][0] / wind.max
      let deltay = wind.data[y*wind.nx + x][1] / wind.max
      positions[i] += deltax
      positions[i + 1] += deltay
    
      // 风速快的地方标成红色
      let alpha = Math.sqrt(Math.pow(deltax, 2) + Math.pow(deltay, 2))
      if (alpha > 0.3) {
        colors[i] = 1
        colors[i + 1] = 0
        colors[i + 2] = 0
      } else {
        colors[i] = alpha
        colors[i + 1] = alpha
        colors[i + 2] = alpha
      }
    }
  }
  points.geometry.attributes.position.needsUpdate = true;
  points.geometry.attributes.color.needsUpdate = true;

  renderer.render(scene, camera);

  renderer.copyFramebufferToTexture(new THREE.Vector2(0, 0), tx)
}
animate();

风速数据如下:

地址

export const wind = {
  "nx": 360, // 经度
  "ny": 181, // 纬度
  "max": 28.700000762939453,
  "data": [
      [
          -2.9,
          4.2
      ],
      ...
  ]
}

目前就是直接从左上角开始映射,看起来跟echart的图有点不一样,可能还是经纬度的计算上有点区别,回头好好研究一下这个数据。

3.新建一个纹理显示在后边记录风的行迹

主要就是把点的数据重复的画到一个纹理上,每次都往上画,并且设置一个透明度,透明度设置次数多了,基本上就消失了。

var tx = new THREE.FramebufferTexture(window.innerWidth, window.innerHeight);

var planeGeo = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
var planeMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0.0 },
        tx: { value: tx }
    },
    vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        uniform float time;
        uniform sampler2D tx;
        varying vec2 vUv;
        void main() {
            gl_FragColor = vec4(texture2D(tx, vUv).xyz, 0.975);
        }
    `,
    blending: THREE.AdditiveBlending,
    transparent: true,
});
var plane = new THREE.Mesh(planeGeo, planeMaterial);
scene.add(plane)

结束语

看起来效果还挺好的,虽说也不一定对,哈哈。针对于这个还有很大的改进空间。这个人的思路还是挺好的,把计算都扔个gpu来做,下次试试这种情况,不过她这个需要先处理一下风速的数据。但是他说这种情况就能把粒子数提升到一百万,还是挺厉害的。

再次那个风速还是弄个颜色映射比较好,当然了也是用shader来写效果会比较好,毕竟用shader做颜色映射这个方式比较简单,避免了中间计算。

欢迎大家交流,谢谢。

完整代码

<script setup>
import * as THREE from 'three';
import { onMounted } from 'vue';
import { wind } from './wind'

const scene = new THREE.Scene();

var camera = new THREE.OrthographicCamera(-window.innerWidth/2, window.innerWidth/2, window.innerHeight/2, -window.innerHeight/2, -1000, 1000);
var renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

var geometry = new THREE.BufferGeometry();
var positionVertices = [];

// Create particles
var colorData = []
var particleCount = 10000;
for (var i = 0; i < particleCount; i++) {
    var x = THREE.MathUtils.randFloatSpread(window.innerWidth );
    var y = THREE.MathUtils.randFloatSpread(window.innerHeight );
    var z = 0;
    positionVertices.push(x, y, z);
    colorData.push(1,1,1)
}

geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionVertices, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorData, 3));

var material = new THREE.PointsMaterial({vertexColors: true})
var points = new THREE.Points(geometry, material);
scene.add(points);



var tx = new THREE.FramebufferTexture(window.innerWidth, window.innerHeight);

var planeGeo = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
var planeMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0.0 },
        tx: { value: tx }
    },
    vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        uniform float time;
        uniform sampler2D tx;
        varying vec2 vUv;
        void main() {
            gl_FragColor = vec4(texture2D(tx, vUv).xyz, 0.975);
        }
    `,
    blending: THREE.AdditiveBlending,
    transparent: true,
});
var plane = new THREE.Mesh(planeGeo, planeMaterial);
scene.add(plane)

function animate() {
  requestAnimationFrame(animate);

  var positions = points.geometry.attributes.position.array;
  var colors = points.geometry.attributes.color.array;
  for (var i = 0; i < positions.length; i += 3) {
    if (Math.random() < 0.01) {
      let x = THREE.MathUtils.randFloatSpread(window.innerWidth );
      let y = THREE.MathUtils.randFloatSpread(window.innerHeight );
      positions[i] = x
      positions[i + 1] = y
      continue
    }

    let xratio = wind.nx / window.innerWidth;
    let yration =  wind.ny / window.innerHeight;
    let x = parseInt((positions[i] + window.innerWidth/2) * xratio)
    let y = parseInt((positions[i + 1] + window.innerHeight/2) * yration)

    if (wind.data[y*wind.nx + x]) {
      let deltax = wind.data[y*wind.nx + x][0] / wind.max
      let deltay = wind.data[y*wind.nx + x][1] / wind.max
      positions[i] += deltax
      positions[i + 1] += deltay
    
      let alpha = Math.sqrt(Math.pow(deltax, 2) + Math.pow(deltay, 2))
      if (alpha > 0.3) {
        colors[i] = 1
        colors[i + 1] = 0
        colors[i + 2] = 0
      } else {
        colors[i] = alpha
        colors[i + 1] = alpha
        colors[i + 2] = alpha
      }
    }
  }
  points.geometry.attributes.position.needsUpdate = true;
  points.geometry.attributes.color.needsUpdate = true;

  // material.uniforms.time.value += 0.1

  renderer.render(scene, camera);

  renderer.copyFramebufferToTexture(new THREE.Vector2(0, 0), tx)
}
animate();

onMounted(() => {
  let canvas = document.getElementById('can');
  renderer = new THREE.WebGLRenderer({canvas: canvas});
  renderer.setSize(window.innerWidth, window.innerHeight);
});

</script>

<template>
  <div id="content">
    <canvas id="can"></canvas>
  </div>
</template>

<style scoped>
#content {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#can {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

文章来源:https://blog.csdn.net/xo19882011/article/details/135087061
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。