웹솔루션개발 25년 노하우! 해피CGI의 모든것

[해피CGI][cgimall] GenArt / Simplex noise 본문

웹프로그램밍 자료실/HTML 자료

[해피CGI][cgimall] GenArt / Simplex noise

해피CGI윤실장 2025. 9. 11. 09:05

 JavaScript를 사용하여 캔버스에 노이즈 애니메이션을 추가하는 디자인 입니다.
노이즈를 추가하는 이미지가 있으면 사용하기 좋을거 같습니다.
자세한 내용은 데모를 참고해 주시기 바랍니다.




HTML

<script type="importmap">
  {
    "imports": {
    }
  }
</script>
<canvas id="cnv"/>
 


CSS

body{
  overflow: hidden;
  margin:0;
  background-color: #ccc;
}
 
#cnv{
  position: absolute;
  top:50%;
  left:50%;
  transform: translate(-50%, -50%);
  border: 3px solid #333;
  border-radius: 150px;
  background-color: #aab;
}

 

JS


import { Vector2 as vec2, MathUtils as mu, Clock } from "three";
import { SimplexNoise } from "three/addons/math/SimplexNoise.js";
 
console.clear();
 
// load fonts
await (async function () {
  async function loadFont(fontface) {
    await fontface.load();
    document.fonts.add(fontface);
  }
  let fonts = [
    new FontFace(
      "MajorMonoDisplay",
    )
  ];
  for (let font in fonts) {
    await loadFont(fonts[font]);
  }
})();
 
class ImgData{
  constructor(grid, params){
    
    let logoCnv = document.createElement("canvas");
    logoCnv.width = grid.ctx.canvas.width;
    logoCnv.height = grid.ctx.canvas.height;
    //document.body.appendChild(logoCnv)
    let logoCtx = logoCnv.getContext("2d");
    let u = (val) => val * logoCnv.height * 0.01;
    
    logoCtx.font = `${u(params.size)}px MajorMonoDisplay`;
    logoCtx.fillStyle = params.color;
    logoCtx.textAlign = "center";
    logoCtx.textBaseline = "middle";
    logoCtx.fillText(params.text, u(params.position.x), u(params.position.y));
    
    this.logoData = logoCtx.getImageData(0, 0, logoCnv.width, logoCnv.height).data;
    this.width = logoCnv.width;
    this.height = logoCnv.height;
    
  }
  
  sampleData(u, v){
    let x = Math.floor(u * this.width);
    let y = Math.floor(v * this.height);
    let dataIndex = (y * this.width + x) * 4;
    return this.logoData.slice(dataIndex, dataIndex + 4);
  }
  
  fetchData(x, y){
    let dataIndex = (y * this.width + x) * 4;
    return this.logoData.slice(dataIndex, dataIndex + 4);
  }
}
 
class Particle extends vec2 {
  constructor(grid, x = 0, y = 0) {
    super(x, y);
    this.grid = grid;
 
    this.speed = mu.randFloat(1, 1.1);
    let step = 1 / grid.simplex.length;
    let yIdx = Math.floor((this.y / this.grid.ctx.canvas.height) / step);
    let xIdx = Math.floor((this.x / this.grid.ctx.canvas.width) / step);
    this.noise = (/*xIdx +*/ yIdx) % this.grid.texture.length; //mu.randInt(0, this.grid.simplex.length - 1);
    //console.log(step, this.noise);
    this.color = 0;
    this.opacity = 0;
    this.thickness = 0;
    this.initPos = new vec2(x, y);
  }
 
  draw() {
    let ctx = this.grid.ctx;
    let color = `hsla(${(this.color) * 360}, 0%, ${ 70 + 30 * this.color}%, ${this.opacity})`;
 
    let tex = this.grid.texture[this.noise];
    
    let fetchImgData = tex.fetchData(Math.floor(this.x), Math.floor(this.y));
    
    color = (fetchImgData[3] == 0) ? color : `rgb(${fetchImgData[0]}, ${fetchImgData[1]}, ${fetchImgData[2]}, ${this.opacity})`;
    
    ctx.fillStyle = color;
    ctx.beginPath();
    
    let x = this.x;
    let y = this.y;
    
    if (this.grid.rasterization == true){
      
      let rst = this.grid.rasterizationStep;
      //x = Math.floor(x / rst) * rst;
      y = Math.floor(y / rst) * rst;
    }
    
    ctx.arc(x, y, this.grid.thickness + this.thickness, 0, Math.PI * 2);
    ctx.fill();
  }
}
 
class Grid {
  constructor(
    ctx,
    params = {
      spacing: 5, // how many pixels between points (grid) on x and y
      
      quant: 0.001, // this.accumulate += quant
      accumulateRatio: 0.4, // for this.accumulate
      
      quantOpacity: 0.005, // increment of point's opacity per render
      
      noiseAmount: 2,
      coordRatio: 0.005, // the greater, the more density of noise
      
      curliness: 1, // noise * Math.PI * curliness
      
      rasterization: !!0, // if you want rasterization effect
      rasterizationStep: 3,
      
      thickness: 1, // point radius
      
      anew: 1 // when this.accumulative is greater than `anew`'s value
    }
  ) {
    this.ctx = ctx;
    this.hw = ctx.canvas.width * 0.5;
    this.hh = ctx.canvas.height * 0.5;
 
    this.spacing = params.spacing;
    this.quant = params.quant;
    this.accumulate = 0;
    this.accumulateRatio = params.accumulateRatio;
    this.quantOpacity = params.quantOpacity;
    
    this.noiseAmount = params.noiseAmount;
    this.coordRatio = params.coordRatio;
 
    this.curliness = params.curliness;
    
    this.rasterization = params.rasterization;
    this.rasterizationStep = params.rasterizationStep;
    
    this.thickness = params.thickness;
    
    this.anew = params.anew;
 
    this.particles = [];
    
    this.texture = null;
    
    this.simplex = null;
    
    this.init();
 
  }
  
  init(){
    
    this.texture = [
      new ImgData(this, {text: "GEN", size: 45, position: new vec2(50, 30), color: "#e30"}),
      new ImgData(this, {text: "ART", size: 45, position: new vec2(50, 70), color: "#000"})
    ];
    this.setNoise();
    
    for (let row = 0; row < Math.floor(cnv.height / this.spacing); row++) {
      for (let col = 0; col < Math.floor(cnv.width / this.spacing); col++) {
        let particle = new Particle(
          this,
          col * this.spacing,
          row * this.spacing
        );
        particle.color = mu.clamp(particle.y / cnv.height, 0, 1) * 0.3 + 0.5;
        this.particles.push(particle);
      }
    }
    this.particles.reverse();
    console.log(this.particles.length);
    
  }
  
  setNoise(){
    this.simplex = Array.from({ length: this.noiseAmount }, (_, idx) => {
      return new SimplexNoise();
    });
  }
  
  update() {
    this.accumulate += this.quant;
    
    if (this.accumulate > this.anew){ // anew
      this.accumulate = 0;
      this.setNoise();
      this.particles.forEach(p => {p.copy(p.initPos); p.opacity = 0;});
      this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    }
 
    let hw = this.hw;
    let hh = this.hh;
 
    let cr = this.coordRatio;
 
    this.particles.forEach((p) => {
      let n = this.simplex[p.noise].noise3d(p.x * cr, p.y * cr, this.accumulate * p.speed * this.accumulateRatio);
      //n = Math.sign(n) * (Math.abs(n)**0.5);
      let a = n * this.curliness * Math.PI;
 
      p.x += Math.cos(a);
      p.y += Math.sin(a);
      p.opacity += this.quantOpacity;
      p.opacity = Math.min(p.opacity, 1);
      //p.thickness = (n * 0.5 + 0.5) * 2;
 
      // cycle the particle on a torus
 
      if(p.x < 0) p.x = hw * 2;
      if(p.x > hw * 2) p.x = 0;
      if(p.y < 0) p.y = hh * 2;
      if(p.y > hh * 2) p.y = 0;
 
      ////////////////////////////////
      p.draw();
    });
  }
}
 
cnv.width = 800;
cnv.height = 800;
let ctx = cnv.getContext("2d");
 
let grid = new Grid(ctx);
 
(function draw() {
  requestAnimationFrame(draw);
  grid.update();
})();
 


 

 

Comments