我正在寻找一个可以帮助完善显示星星的代码的人。 我最近看到了异星工厂的更新和他们的网站 - https://factorio.com/galaxy。我对星星的概念很感兴趣,即它们是如何排列的。我自己也尝试过制作类似的星星,但没有成功。我尝试使用 ChatGPT,但它没有给出正确或尖锐的问题。 银河 这是一个例子,正如我们在中间看到的那样,我们有一个正方形,而星星本身是使用正方形和高光制成的。我对第一个渲染没有任何问题,但与第二个渲染效果不太好。目前还不完全清楚如何进一步绘制。
import { useEffect, useRef, useState } from "react";
import Head from "next/head";
import styles from './star.module.css';
import starImage5 from '../public/star/5.png';
import starImage4 from '../public/star/4.png';
import starImage3 from '../public/star/3.png';
import starImage2 from '../public/star/2.png';
import starImage1 from '../public/star/1.png';
const generateRandomCoordinates = (numStars, offsetRange) => {
const coordinates = [];
const positions = [];
for (let i = 0; i < numStars; i++) {
const x = Math.random() * (offsetRange * 2) - offsetRange;
const y = Math.random() * (offsetRange * 2) - offsetRange;
coordinates.push(x);
positions.push(y);
}
return { coordinates, positions };
};
export default function Home() {
const canvasRef = useRef(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [lastMousePosition, setLastMousePosition] = useState({ x: 0, y: 0 });
const starData = {
colors: [...Array(100).keys()].map(() => Math.floor(Math.random() * 16777215)),
ids: [...Array(100).keys()].map(() => Math.floor(Math.random() * 1000000000)),
names: [...Array(100).keys()].map((_, i) => `Star ${i + 1}`),
};
const { coordinates, positions } = generateRandomCoordinates(100, 200);
useEffect(() => {
const images = [starImage1, starImage2, starImage3, starImage4, starImage5].map(src => {
const img = new Image();
img.src = src.src;
return img;
});
Promise.all(images.map(img => new Promise(resolve => img.onload = resolve)))
.then(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.imageSmoothingEnabled = false;
const drawStars = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const numStars = Math.min(starData.colors.length, coordinates.length, positions.length);
for (let index = 0; index < numStars; index++) {
const x = coordinates[index];
const y = positions[index];
const color = `#${starData.colors[index].toString(16).padStart(6, '0')}`;
const starX = x + offset.x + canvas.width / 2;
const starY = y + offset.y + canvas.height / 2;
const randomStarIndex = Math.floor(Math.random() * images.length);
const starImage = images[randomStarIndex];
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = starImage.width;
tempCanvas.height = starImage.height;
tempCtx.drawImage(starImage, 0, 0);
tempCtx.globalCompositeOperation = 'source-in';
tempCtx.fillStyle = color;
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
const starSize = 16;
ctx.drawImage(tempCanvas, starX - starSize / 2, starY - starSize / 2, starSize, starSize);
}
};
drawStars();
const handleMouseMove = (event) => {
if (isDragging) {
const dx = event.clientX - lastMousePosition.x;
const dy = event.clientY - lastMousePosition.y;
setOffset(prev => ({
x: prev.x + dx,
y: prev.y + dy,
}));
setLastMousePosition({ x: event.clientX, y: event.clientY });
drawStars(); // Переотрисовываем звезды
}
};
const handleMouseDown = (event) => {
setIsDragging(true);
setLastMousePosition({ x: event.clientX, y: event.clientY });
};
const handleMouseUp = () => {
setIsDragging(false);
};
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mousedown", handleMouseDown);
canvas.addEventListener("mouseup", handleMouseUp);
canvas.addEventListener("mouseleave", handleMouseUp);
return () => {
canvas.removeEventListener("mousemove", handleMouseMove);
canvas.removeEventListener("mousedown", handleMouseDown);
canvas.removeEventListener("mouseup", handleMouseUp);
canvas.removeEventListener("mouseleave", handleMouseUp);
};
});
}, [offset.x, offset.y]);
return (
<>
<div className={styles.imageContainer}>
<canvas ref={canvasRef} className={styles.pixelCanvas} />
</div>
</>
);
}
开发人员自己使用 Three.js 完成了
我尝试通过在顶部应用图层来创建亮点,但效果不太好
输入star的源代码 我还注意到了网站上star的源代码,通过开发者控制台(网络)
您可以根据具体情况通过多种方式执行此操作。还取决于您需要它的目的。一般来说,在
WebGL
中,此类项目是通过PointsMaterial/Shader
实施的。从您展示的示例来看,在我看来,您不需要三维环境,只需要一个带有随机放置点的简单 Canvas
。如果需要,您可以将这些点转换为纹理。至于粒子本身的形状,你可以用GLSL
来实现,但在你的情况下似乎没有必要。由于这应该是一个星系,因此您很可能会遇到纹理是正方形并且粒子外部有黑色背景的问题。在WebGL
中,我们通过多种方式处理这个问题,例如通过depthTest
。在 2D
上下文中,您可以使用 transparency
,但我不确定结果。
2D 上下文
*{margin:0;background-color:black;}
<canvas id="galaxy"></canvas>
<script>
const canvas = document.getElementById('galaxy');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particleC = 400;
const galaxyR = 200;
const particles = [];
for (let i = 0; i < particleC; i++) {
const radius = Math.random() * galaxyR;
const angle = Math.random() * 2 * Math.PI;
const x = canvas.width / 2 + radius * Math.cos(angle);
const y = canvas.height / 2 + radius * Math.sin(angle);
particles.push({
x: x,
y: y,
color: `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`
});
}
function createGalaxy() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
ctx.beginPath();
ctx.arc(particle.x, particle.y, 2, 0, 2 * Math.PI);
ctx.fillStyle = particle.color;
ctx.fill();
});
}
createGalaxy();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
createGalaxy();
});
</script>
WebGL/三上下文
*{margin:0}
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
// https://stackoverflow.com/questions/79130157/creating-pixel-stars-using-canvas
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
const vertexShader = `
varying vec3 vColor;
void main() {
vColor = color;
gl_PointSize = 6.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
varying vec3 vColor;
uniform float uRadius;
void main() {
float dist = length(gl_PointCoord - vec2(0.5));
float strength = 1.0 - smoothstep(uRadius - 0.22, uRadius + 0.22, dist);
gl_FragColor = vec4(vColor, strength);
}
`;
const particleNo = 400;
const particleG = new THREE.BufferGeometry();
const positions = new Float32Array(particleNo * 3);
const colors = new Float32Array(particleNo * 3);
for (let i = 0; i < particleNo; i++) {
const radius = Math.random() * 20;
const theta = Math.random() * 2 * Math.PI;
const phi = Math.acos(2 * Math.random() - 1);
const x = radius * Math.sin(phi) * Math.cos(theta);
const y = radius * Math.sin(phi) * Math.sin(theta);
const z = radius * Math.cos(phi);
positions[i * 3] = x;
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = z;
colors[i * 3] = Math.random();
colors[i * 3 + 1] = Math.random();
colors[i * 3 + 2] = Math.random();
}
particleG.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particleG.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleM = new THREE.ShaderMaterial({
uniforms: { uRadius: { value: 0.3 } },
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
vertexColors: true,
depthTest: false,
});
const particles = new THREE.Points(particleG, particleM);
scene.add(particles);
camera.position.z = 30;
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
function responsive() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', responsive);
function animate() {
requestAnimationFrame(animate);
particles.rotation.y += 0.002;
controls.update();
renderer.render(scene, camera);
}
animate();
</script>
2D 上下文 => 纹理
<canvas id="galaxy"></canvas>
<script>
const canvas = document.getElementById('galaxy');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particleC = 400;
const galaxyR = 200;
const particleTexture = new Image();
particleTexture.src = 'https://media.craiyon.com/2023-11-26/2ets6-v1SVGOdKAtPxmlMw.webp';
const particles = [];
for (let i = 0; i < particleC; i++) {
const radius = Math.random() * galaxyR;
const angle = Math.random() * 2 * Math.PI;
const x = canvas.width / 2 + radius * Math.cos(angle);
const y = canvas.height / 2 + radius * Math.sin(angle);
particles.push({ x, y });
}
function createGalaxy() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
ctx.drawImage(particleTexture, particle.x - 5, particle.y - 5, 10, 10);
});
}
particleTexture.onload = () => createGalaxy();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
createGalaxy();
});
</script>
WebGL/三 => 纹理
*{margin:0}
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
const textureLoader = new THREE.TextureLoader();
const particleTexture = textureLoader.load("https://media.craiyon.com/2023-11-26/2ets6-v1SVGOdKAtPxmlMw.webp");
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_PointSize = 10.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
varying vec2 vUv;
uniform sampler2D uTexture;
void main() {
vec4 texColor = texture2D(uTexture, gl_PointCoord);
gl_FragColor = texColor;
}
`;
const particleNo = 400;
const particleG = new THREE.BufferGeometry();
const positions = new Float32Array(particleNo * 3);
for (let i = 0; i < particleNo; i++) {
positions[i * 3] = (Math.random() - 0.5) * 40;
positions[i * 3 + 1] = (Math.random() - 0.5) * 40;
positions[i * 3 + 2] = (Math.random() - 0.5) * 40;
}
particleG.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleM = new THREE.ShaderMaterial({
uniforms: {
uTexture: { value: particleTexture },
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
depthTest: false,
});
const particles = new THREE.Points(particleG, particleM);
scene.add(particles);
camera.position.z = 30;
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
function responsive() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', responsive);
function animate() {
requestAnimationFrame(animate);
particles.rotation.y += 0.002;
controls.update();
renderer.render(scene, camera);
}
animate();
</script>