首页 > 解决方案 > 如何将粒子系统初始化为球形帽?

问题描述

我的 Three.js 项目包括在一个经典的圣诞雪球(一个 sphereGeometry)内制作雪(带有一些粒子)。我已经创建了粒子,THREE.BufferGeometry()并初始化了它们,为每个参数(x,y,z)提供了一个 initial_position。

有没有办法让它们只在球体内可见?我解决了给球体外部的粒子与背景颜色相同的问题,但它不能完美地工作。

有没有办法让球体外的粒子不可见?也许让它们变得透明。

否则我怎么能将粒子初始化为球形帽?谢谢!

这就是我初始化粒子位置的方式(作为平行六面体):

for(i=0; i<n; i++){
    init_pos_y[i] = 50 + (Math.random()-0.5)*20;
    init_pos_x[i] = (Math.random()-0.5)*100;
    init_pos_z[i] = (Math.random()-0.5)*100;
    acceleration[i] = Math.random()*1;

在顶点着色器中,这就是我如何使粒子下落并赋予它们颜色(并根据其在球体内或球外的位置改变其不透明度):

void main(){

    vec3 p = position; 
    p.x = initial_position_x;
    p.z = initial_position_z;

    if (initial_position_y - time * acceleration > -32.8 + min_level){  
        p.y = initial_position_y - time * acceleration;
    }
    else{
        p.y = -33.8 + min_level;
    }
    float opacity;

    if (p.x*p.x + p.y*p.y + p.z*p.z > 2490.0){ 
        opacity = 0.40;
        vColor = vec4( customColor, opacity );              
    }
    else{
        opacity = 1.0;
        vColor = vec4( customColor2, opacity );
    }

    gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);            
    vUv = projectionMatrix * vec4(p, 1.0);
    gl_PointSize = 3.0*acceleration;
}

标签: javascriptthree.js

解决方案


首先,您需要将您的点放在一个圆柱体中(在一个圆圈内随机,在高度上随机)。为此,请参阅功能setInCircle()

然后需要修改shader的代码。我更喜欢用 来做.onBeforeCompile(),因此你可以修改必要的部分,保留材料的所有其他功能。

在着色器中,您通过添加距离(时间 * 速度)将 y 值除以 10(mod()函数)来更改 y 值,因此您将其置于一个循环中,在 5 到 -5 的范围内从上到下移动。

最后一件事是检查给定变换向量的长度是否小于或等于给定半径,将结果传递给片段着色器,如果结果小于 0.5,则丢弃一个像素。

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.setScalar(10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

scene.add(new THREE.GridHelper(10, 10));

var pts = [];
var radius = 5;
var pointsCount = 5000;

for (i = 0; i < pointsCount; i++) {
  let v2 = setInCircle().multiplyScalar(radius);
  let v3 = new THREE.Vector3(v2.x, THREE.Math.randFloat(-radius, radius), v2.y);
  pts.push(v3);
}

var geom = new THREE.BufferGeometry().setFromPoints(pts);
var uniforms = {
  speed: {
    value: 1
  },
  time: {
    value: 0
  },
  radius: {
    value: radius
  }
}
var mat = new THREE.PointsMaterial({
  color: "magenta",
  size: 0.05
});
mat.onBeforeCompile = shader => {
  shader.uniforms.speed = uniforms.speed;
  shader.uniforms.time = uniforms.time;
  shader.uniforms.radius = uniforms.radius;
  shader.vertexShader = `
    uniform float speed;
    uniform float time;
    uniform float radius;
    varying float vVisible;
  ` + shader.vertexShader;
  //console.log(shader.vertexShader);
  shader.vertexShader = shader.vertexShader.replace(
    `#include <begin_vertex>`,
    `#include <begin_vertex>
    transformed.y = mod((transformed.y - speed * time) - 5., 10.) - 5.;
    vVisible = length(transformed) <= radius ? 1.: 0.;
  `
  );
  shader.fragmentShader = `
    varying float vVisible;
  ` + shader.fragmentShader;
  console.log(shader.fragmentShader);
  shader.fragmentShader = shader.fragmentShader.replace(
    `void main() {`,
    `void main() {
      if (vVisible < 0.5) discard;
    `
  );

}
var points = new THREE.Points(geom, mat);
scene.add(points);

function setInCircle() {
  let v = new THREE.Vector2();
  v.set(
    THREE.Math.randFloat(-1, 1),
    THREE.Math.randFloat(-1, 1)
  )
  return v.length() <= 1 ? v : setInCircle();
}

var clock = new THREE.Clock();

renderer.setAnimationLoop(() => {
  uniforms.time.value = clock.getElapsedTime();
  renderer.render(scene, camera)
});
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>


推荐阅读