使用画布创建像素星星

问题描述 投票:0回答:1

我正在寻找一个可以帮助完善显示星星的代码的人。 我最近看到了异星工厂的更新和他们的网站 - 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的源代码,通过开发者控制台(网络)

javascript html canvas three.js
1个回答
0
投票

您可以根据具体情况通过多种方式执行此操作。还取决于您需要它的目的。一般来说,在

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>

© www.soinside.com 2019 - 2024. All rights reserved.