particles-gpu by bbeierle12/skill-mcp-claude
npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill particles-gpu使用 GPU 实例化和自定义着色器高效渲染大量粒子(10k-1M+)。
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function Particles({ count = 10000 }) {
const points = useRef<THREE.Points>(null!);
const positions = useMemo(() => {
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
pos[i * 3] = (Math.random() - 0.5) * 10;
pos[i * 3 + 1] = (Math.random() - 0.5) * 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 10;
}
return pos;
}, [count]);
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial size={0.05} color="#ffffff" />
</points>
);
}
| 方法 | 粒子数量 | 复杂度 | 使用场景 |
|---|---|---|---|
| 点 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 10k - 500k |
| 低 |
| 简单粒子、星星 |
| 实例化网格 | 1k - 100k | 中 | 3D 几何体粒子 |
| 自定义着色器 | 100k - 10M | 高 | 最大控制度 |
最简单的方法——每个粒子都是一个面向屏幕的点精灵。
function BasicPoints({ count = 5000 }) {
const positions = useMemo(() => {
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = Math.cbrt(Math.random()) * 5;
pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
pos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
pos[i * 3 + 2] = r * Math.cos(phi);
}
return pos;
}, [count]);
return (
<points>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.1}
sizeAttenuation={true}
transparent={true}
opacity={0.8}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
function TexturedPoints({ count = 5000 }) {
const texture = useTexture('/particle.png');
return (
<points>
<bufferGeometry>
{/* ... positions ... */}
</bufferGeometry>
<pointsMaterial
size={0.5}
map={texture}
transparent={true}
alphaTest={0.01}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
添加每个粒子的数据,如颜色、大小、速度:
function ColoredParticles({ count = 10000 }) {
const { positions, colors, sizes } = useMemo(() => {
const pos = new Float32Array(count * 3);
const col = new Float32Array(count * 3);
const siz = new Float32Array(count);
for (let i = 0; i < count; i++) {
// 位置
pos[i * 3] = (Math.random() - 0.5) * 10;
pos[i * 3 + 1] = (Math.random() - 0.5) * 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 10;
// 颜色(HSL 转 RGB)
const color = new THREE.Color();
color.setHSL(Math.random(), 0.8, 0.5);
col[i * 3] = color.r;
col[i * 3 + 1] = color.g;
col[i * 3 + 2] = color.b;
// 大小
siz[i] = 0.05 + Math.random() * 0.1;
}
return { positions: pos, colors: col, sizes: siz };
}, [count]);
return (
<points>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={count}
array={colors}
itemSize={3}
/>
<bufferAttribute
attach="attributes-size"
count={count}
array={sizes}
itemSize={1}
/>
</bufferGeometry>
<pointsMaterial
vertexColors
size={0.1}
sizeAttenuation
transparent
depthWrite={false}
/>
</points>
);
}
对粒子外观和动画的最大控制:
const vertexShader = `
attribute float aSize;
attribute vec3 aColor;
attribute float aAlpha;
uniform float uTime;
uniform float uPixelRatio;
varying vec3 vColor;
varying float vAlpha;
void main() {
vColor = aColor;
vAlpha = aAlpha;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
// 大小衰减
gl_PointSize = aSize * uPixelRatio * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
varying vec3 vColor;
varying float vAlpha;
void main() {
// 圆形粒子
float dist = length(gl_PointCoord - 0.5);
if (dist > 0.5) discard;
// 软边缘
float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
gl_FragColor = vec4(vColor, alpha * vAlpha);
}
`;
function ShaderParticles({ count = 50000 }) {
const points = useRef<THREE.Points>(null!);
const { positions, sizes, colors, alphas } = useMemo(() => {
const pos = new Float32Array(count * 3);
const siz = new Float32Array(count);
const col = new Float32Array(count * 3);
const alp = new Float32Array(count);
for (let i = 0; i < count; i++) {
pos[i * 3] = (Math.random() - 0.5) * 20;
pos[i * 3 + 1] = (Math.random() - 0.5) * 20;
pos[i * 3 + 2] = (Math.random() - 0.5) * 20;
siz[i] = 10 + Math.random() * 20;
const color = new THREE.Color();
color.setHSL(0.6 + Math.random() * 0.2, 0.8, 0.5);
col[i * 3] = color.r;
col[i * 3 + 1] = color.g;
col[i * 3 + 2] = color.b;
alp[i] = 0.3 + Math.random() * 0.7;
}
return { positions: pos, sizes: siz, colors: col, alphas: alp };
}, [count]);
useFrame(({ clock }) => {
points.current.material.uniforms.uTime.value = clock.elapsedTime;
});
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute attach="attributes-position" count={count} array={positions} itemSize={3} />
<bufferAttribute attach="attributes-aSize" count={count} array={sizes} itemSize={1} />
<bufferAttribute attach="attributes-aColor" count={count} array={colors} itemSize={3} />
<bufferAttribute attach="attributes-aAlpha" count={count} array={alphas} itemSize={1} />
</bufferGeometry>
<shaderMaterial
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={{
uTime: { value: 0 },
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) }
}}
transparent
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
// 带动画的顶点着色器
attribute vec3 aVelocity;
attribute float aPhase;
uniform float uTime;
void main() {
vec3 pos = position;
// 简单振荡
pos.y += sin(uTime * 2.0 + aPhase) * 0.5;
// 基于速度的运动
pos += aVelocity * uTime;
// 边界环绕
pos = mod(pos + 10.0, 20.0) - 10.0;
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = 10.0 * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
function AnimatedParticles({ count = 10000 }) {
const points = useRef<THREE.Points>(null!);
const velocities = useMemo(() => {
const vel = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
vel[i * 3] = (Math.random() - 0.5) * 0.02;
vel[i * 3 + 1] = (Math.random() - 0.5) * 0.02;
vel[i * 3 + 2] = (Math.random() - 0.5) * 0.02;
}
return vel;
}, [count]);
useFrame(() => {
const positions = points.current.geometry.attributes.position.array as Float32Array;
for (let i = 0; i < count; i++) {
positions[i * 3] += velocities[i * 3];
positions[i * 3 + 1] += velocities[i * 3 + 1];
positions[i * 3 + 2] += velocities[i * 3 + 2];
// 环绕
for (let j = 0; j < 3; j++) {
if (positions[i * 3 + j] > 5) positions[i * 3 + j] = -5;
if (positions[i * 3 + j] < -5) positions[i * 3 + j] = 5;
}
}
points.current.geometry.attributes.position.needsUpdate = true;
});
// ... 几何体设置
}
用于 3D 几何体粒子(不仅仅是点):
function InstancedParticles({ count = 1000 }) {
const mesh = useRef<THREE.InstancedMesh>(null!);
const dummy = useMemo(() => new THREE.Object3D(), []);
useEffect(() => {
for (let i = 0; i < count; i++) {
dummy.position.set(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
dummy.scale.setScalar(0.05 + Math.random() * 0.1);
dummy.updateMatrix();
mesh.current.setMatrixAt(i, dummy.matrix);
}
mesh.current.instanceMatrix.needsUpdate = true;
}, [count, dummy]);
useFrame(({ clock }) => {
for (let i = 0; i < count; i++) {
mesh.current.getMatrixAt(i, dummy.matrix);
dummy.matrix.decompose(dummy.position, dummy.quaternion, dummy.scale);
dummy.rotation.x += 0.01;
dummy.rotation.y += 0.01;
dummy.updateMatrix();
mesh.current.setMatrixAt(i, dummy.matrix);
}
mesh.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
<icosahedronGeometry args={[1, 0]} />
<meshStandardMaterial color="#ff6b6b" />
</instancedMesh>
);
}
function spherePositions(count: number, radius: number) {
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = Math.cbrt(Math.random()) * radius; // 立方根用于均匀体积分布
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = r * Math.cos(phi);
}
return positions;
}
function galaxyPositions(count: number, arms: number, spin: number) {
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const armIndex = i % arms;
const armAngle = (armIndex / arms) * Math.PI * 2;
const radius = Math.random() * 5;
const spinAngle = radius * spin;
const angle = armAngle + spinAngle;
// 添加随机性
const randomX = (Math.random() - 0.5) * 0.5 * radius;
const randomY = (Math.random() - 0.5) * 0.2;
const randomZ = (Math.random() - 0.5) * 0.5 * radius;
positions[i * 3] = Math.cos(angle) * radius + randomX;
positions[i * 3 + 1] = randomY;
positions[i * 3 + 2] = Math.sin(angle) * radius + randomZ;
}
return positions;
}
function gridPositions(countPerAxis: number, spacing: number) {
const count = countPerAxis ** 3;
const positions = new Float32Array(count * 3);
const offset = (countPerAxis - 1) * spacing * 0.5;
let index = 0;
for (let x = 0; x < countPerAxis; x++) {
for (let y = 0; y < countPerAxis; y++) {
for (let z = 0; z < countPerAxis; z++) {
positions[index * 3] = x * spacing - offset;
positions[index * 3 + 1] = y * spacing - offset;
positions[index * 3 + 2] = z * spacing - offset;
index++;
}
}
}
return positions;
}
| 技术 | 影响 |
|---|---|
| 使用点而非实例化网格 | 对于简单粒子快 5-10 倍 |
| GPU 动画(着色器)对比 CPU | 大规模时快 10-100 倍 |
| 禁用深度写入 | 混合更快 |
| 使用 Float32Array | 缓冲区必需 |
| 视锥体剔除(默认开启) | 跳过屏幕外粒子 |
<pointsMaterial
transparent
depthWrite={false} // 混合更快
blending={THREE.AdditiveBlending} // 适用于发光粒子
sizeAttenuation // 透视正确的大小
/>
particles-gpu/
├── SKILL.md
├── references/
│ ├── buffer-patterns.md # 分布模式
│ └── shader-examples.md # 完整着色器示例
└── scripts/
├── particles/
│ ├── basic-points.tsx # 简单点设置
│ ├── shader-points.tsx # 自定义着色器粒子
│ └── instanced.tsx # 实例化网格粒子
└── distributions/
├── sphere.ts # 球体分布
├── galaxy.ts # 星系螺旋
└── grid.ts # 网格分布
references/buffer-patterns.md — 位置分布模式references/shader-examples.md — 完整粒子着色器每周安装数
57
仓库
GitHub 星标数
7
首次出现
2026年1月23日
安全审计
安装于
opencode47
codex46
gemini-cli44
claude-code42
cursor42
github-copilot39
Render massive particle counts (10k-1M+) efficiently using GPU instancing and custom shaders.
import { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function Particles({ count = 10000 }) {
const points = useRef<THREE.Points>(null!);
const positions = useMemo(() => {
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
pos[i * 3] = (Math.random() - 0.5) * 10;
pos[i * 3 + 1] = (Math.random() - 0.5) * 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 10;
}
return pos;
}, [count]);
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial size={0.05} color="#ffffff" />
</points>
);
}
| Approach | Particle Count | Complexity | Use Case |
|---|---|---|---|
| Points | 10k - 500k | Low | Simple particles, stars |
| Instanced Mesh | 1k - 100k | Medium | 3D geometry particles |
| Custom Shader | 100k - 10M | High | Maximum control |
Simplest approach—each particle is a screen-facing point sprite.
function BasicPoints({ count = 5000 }) {
const positions = useMemo(() => {
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = Math.cbrt(Math.random()) * 5;
pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
pos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
pos[i * 3 + 2] = r * Math.cos(phi);
}
return pos;
}, [count]);
return (
<points>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.1}
sizeAttenuation={true}
transparent={true}
opacity={0.8}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
function TexturedPoints({ count = 5000 }) {
const texture = useTexture('/particle.png');
return (
<points>
<bufferGeometry>
{/* ... positions ... */}
</bufferGeometry>
<pointsMaterial
size={0.5}
map={texture}
transparent={true}
alphaTest={0.01}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
Add per-particle data like color, size, velocity:
function ColoredParticles({ count = 10000 }) {
const { positions, colors, sizes } = useMemo(() => {
const pos = new Float32Array(count * 3);
const col = new Float32Array(count * 3);
const siz = new Float32Array(count);
for (let i = 0; i < count; i++) {
// Position
pos[i * 3] = (Math.random() - 0.5) * 10;
pos[i * 3 + 1] = (Math.random() - 0.5) * 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 10;
// Color (HSL to RGB)
const color = new THREE.Color();
color.setHSL(Math.random(), 0.8, 0.5);
col[i * 3] = color.r;
col[i * 3 + 1] = color.g;
col[i * 3 + 2] = color.b;
// Size
siz[i] = 0.05 + Math.random() * 0.1;
}
return { positions: pos, colors: col, sizes: siz };
}, [count]);
return (
<points>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={count}
array={colors}
itemSize={3}
/>
<bufferAttribute
attach="attributes-size"
count={count}
array={sizes}
itemSize={1}
/>
</bufferGeometry>
<pointsMaterial
vertexColors
size={0.1}
sizeAttenuation
transparent
depthWrite={false}
/>
</points>
);
}
Maximum control over particle appearance and animation:
const vertexShader = `
attribute float aSize;
attribute vec3 aColor;
attribute float aAlpha;
uniform float uTime;
uniform float uPixelRatio;
varying vec3 vColor;
varying float vAlpha;
void main() {
vColor = aColor;
vAlpha = aAlpha;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
// Size attenuation
gl_PointSize = aSize * uPixelRatio * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
varying vec3 vColor;
varying float vAlpha;
void main() {
// Circular particle
float dist = length(gl_PointCoord - 0.5);
if (dist > 0.5) discard;
// Soft edge
float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
gl_FragColor = vec4(vColor, alpha * vAlpha);
}
`;
function ShaderParticles({ count = 50000 }) {
const points = useRef<THREE.Points>(null!);
const { positions, sizes, colors, alphas } = useMemo(() => {
const pos = new Float32Array(count * 3);
const siz = new Float32Array(count);
const col = new Float32Array(count * 3);
const alp = new Float32Array(count);
for (let i = 0; i < count; i++) {
pos[i * 3] = (Math.random() - 0.5) * 20;
pos[i * 3 + 1] = (Math.random() - 0.5) * 20;
pos[i * 3 + 2] = (Math.random() - 0.5) * 20;
siz[i] = 10 + Math.random() * 20;
const color = new THREE.Color();
color.setHSL(0.6 + Math.random() * 0.2, 0.8, 0.5);
col[i * 3] = color.r;
col[i * 3 + 1] = color.g;
col[i * 3 + 2] = color.b;
alp[i] = 0.3 + Math.random() * 0.7;
}
return { positions: pos, sizes: siz, colors: col, alphas: alp };
}, [count]);
useFrame(({ clock }) => {
points.current.material.uniforms.uTime.value = clock.elapsedTime;
});
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute attach="attributes-position" count={count} array={positions} itemSize={3} />
<bufferAttribute attach="attributes-aSize" count={count} array={sizes} itemSize={1} />
<bufferAttribute attach="attributes-aColor" count={count} array={colors} itemSize={3} />
<bufferAttribute attach="attributes-aAlpha" count={count} array={alphas} itemSize={1} />
</bufferGeometry>
<shaderMaterial
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={{
uTime: { value: 0 },
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) }
}}
transparent
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
// Vertex shader with animation
attribute vec3 aVelocity;
attribute float aPhase;
uniform float uTime;
void main() {
vec3 pos = position;
// Simple oscillation
pos.y += sin(uTime * 2.0 + aPhase) * 0.5;
// Velocity-based movement
pos += aVelocity * uTime;
// Wrap around bounds
pos = mod(pos + 10.0, 20.0) - 10.0;
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = 10.0 * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
function AnimatedParticles({ count = 10000 }) {
const points = useRef<THREE.Points>(null!);
const velocities = useMemo(() => {
const vel = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
vel[i * 3] = (Math.random() - 0.5) * 0.02;
vel[i * 3 + 1] = (Math.random() - 0.5) * 0.02;
vel[i * 3 + 2] = (Math.random() - 0.5) * 0.02;
}
return vel;
}, [count]);
useFrame(() => {
const positions = points.current.geometry.attributes.position.array as Float32Array;
for (let i = 0; i < count; i++) {
positions[i * 3] += velocities[i * 3];
positions[i * 3 + 1] += velocities[i * 3 + 1];
positions[i * 3 + 2] += velocities[i * 3 + 2];
// Wrap around
for (let j = 0; j < 3; j++) {
if (positions[i * 3 + j] > 5) positions[i * 3 + j] = -5;
if (positions[i * 3 + j] < -5) positions[i * 3 + j] = 5;
}
}
points.current.geometry.attributes.position.needsUpdate = true;
});
// ... geometry setup
}
For 3D geometry particles (not just points):
function InstancedParticles({ count = 1000 }) {
const mesh = useRef<THREE.InstancedMesh>(null!);
const dummy = useMemo(() => new THREE.Object3D(), []);
useEffect(() => {
for (let i = 0; i < count; i++) {
dummy.position.set(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
dummy.scale.setScalar(0.05 + Math.random() * 0.1);
dummy.updateMatrix();
mesh.current.setMatrixAt(i, dummy.matrix);
}
mesh.current.instanceMatrix.needsUpdate = true;
}, [count, dummy]);
useFrame(({ clock }) => {
for (let i = 0; i < count; i++) {
mesh.current.getMatrixAt(i, dummy.matrix);
dummy.matrix.decompose(dummy.position, dummy.quaternion, dummy.scale);
dummy.rotation.x += 0.01;
dummy.rotation.y += 0.01;
dummy.updateMatrix();
mesh.current.setMatrixAt(i, dummy.matrix);
}
mesh.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
<icosahedronGeometry args={[1, 0]} />
<meshStandardMaterial color="#ff6b6b" />
</instancedMesh>
);
}
function spherePositions(count: number, radius: number) {
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = Math.cbrt(Math.random()) * radius; // Cube root for uniform volume
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = r * Math.cos(phi);
}
return positions;
}
function galaxyPositions(count: number, arms: number, spin: number) {
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const armIndex = i % arms;
const armAngle = (armIndex / arms) * Math.PI * 2;
const radius = Math.random() * 5;
const spinAngle = radius * spin;
const angle = armAngle + spinAngle;
// Add randomness
const randomX = (Math.random() - 0.5) * 0.5 * radius;
const randomY = (Math.random() - 0.5) * 0.2;
const randomZ = (Math.random() - 0.5) * 0.5 * radius;
positions[i * 3] = Math.cos(angle) * radius + randomX;
positions[i * 3 + 1] = randomY;
positions[i * 3 + 2] = Math.sin(angle) * radius + randomZ;
}
return positions;
}
function gridPositions(countPerAxis: number, spacing: number) {
const count = countPerAxis ** 3;
const positions = new Float32Array(count * 3);
const offset = (countPerAxis - 1) * spacing * 0.5;
let index = 0;
for (let x = 0; x < countPerAxis; x++) {
for (let y = 0; y < countPerAxis; y++) {
for (let z = 0; z < countPerAxis; z++) {
positions[index * 3] = x * spacing - offset;
positions[index * 3 + 1] = y * spacing - offset;
positions[index * 3 + 2] = z * spacing - offset;
index++;
}
}
}
return positions;
}
| Technique | Impact |
|---|---|
| Use Points over InstancedMesh | 5-10x faster for simple particles |
| GPU animation (shader) vs CPU | 10-100x faster at scale |
| Disable depthWrite | Faster blending |
| Use Float32Array | Required for buffers |
| Frustum culling (default on) | Skip off-screen |
<pointsMaterial
transparent
depthWrite={false} // Faster blending
blending={THREE.AdditiveBlending} // Good for glowing particles
sizeAttenuation // Perspective-correct size
/>
particles-gpu/
├── SKILL.md
├── references/
│ ├── buffer-patterns.md # Distribution patterns
│ └── shader-examples.md # Complete shader examples
└── scripts/
├── particles/
│ ├── basic-points.tsx # Simple points setup
│ ├── shader-points.tsx # Custom shader particles
│ └── instanced.tsx # Instanced mesh particles
└── distributions/
├── sphere.ts # Sphere distribution
├── galaxy.ts # Galaxy spiral
└── grid.ts # Grid distribution
references/buffer-patterns.md — Position distribution patternsreferences/shader-examples.md — Complete particle shadersWeekly Installs
57
Repository
GitHub Stars
7
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketFailSnykPass
Installed on
opencode47
codex46
gemini-cli44
claude-code42
cursor42
github-copilot39
GSAP 框架集成指南:Vue、Svelte 等框架中 GSAP 动画最佳实践
3,200 周安装