“使用 Three.js 的 WebGL 小实验。美丽的魔鬼之花罂粟。”
HTML:
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.168.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.168.0/examples/jsm/"
}
}
</script>
<canvas id="cnv"></canvas>
body{
overflow: hidden;
margin: 0;
background-color: black;
}
canvas {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/*border: 1px solid red;*/
}
JAVASCRIPT:
import {Vector2, Color, Clock} from 'three';
import {SimplexNoise} from "three/addons/math/SimplexNoise.js";
console.clear();
let simplex = new SimplexNoise();
// https://www.color-meanings.com/neon-color-palettes/
let palettes = [
["#FC1FF9", "#BC0EEF", "#443061", "#E42536", "#FC5E31"],
["#B7239B", "#ED3DC6", "#FFE91A", "#FE5700", "#EA1104"],
["#FE02FF", "#DF21FF", "#8407CE", "#5F03BD", "#2F0049"].reverse(),
["#463F9E", "#F354A9", "#84F5D5", "#9B62E5", "#9D2EB0"],
["#221EB8", "#861CFF", "#EC14EE", "#1ECDE6", "#FB7443"].reverse(),
["#1B37AA", "#2572D9", "#A83A96", "#F0533C", "#DB3836"]
]
let basePalette = palettes[5];
let ctx = cnv.getContext("2d");
let unit = 0;
let u = val => unit * val;
let resize = () => {
let minVal = Math.min(innerWidth, innerHeight);
cnv.width = cnv.height = minVal * 0.95;
unit = cnv.height * 0.01;
}
window.addEventListener("resize", resize);
resize();
let color = new Color();
const palette = basePalette.map(pal => {
let res = {h:0, s:0, l:0};
color.set(pal).getHSL(res);
return res;
});
const layersAmount = palette.length;
const amountPerLayer = 30;
const center = new Vector2();
const ctxCenter = new Vector2(50, 50);
let points = Array.from({length: amountPerLayer}, (_, pIdx) => {
let v = new Vector2(
Math.cos(pIdx * Math.PI / (amountPerLayer * 0.5)),
Math.sin(pIdx * Math.PI / (amountPerLayer * 0.5))
);
return v;
});
let noisyPoints = Array.from({length: amountPerLayer}, () => {return new Vector2()});
let layers = Array.from({length: layersAmount}, (_, layerIdx) => {
return {
layerIdx: layerIdx,
radius: (50 / (layersAmount)) * (layerIdx + 1)
}
}).reverse();
let mediators = {
temp: new Vector2()
}
let clock = new Clock();
let t = 0;
draw();
function draw(){
requestAnimationFrame(draw);
let dt = clock.getDelta();
t += dt * 0.25;
ctx.clearRect(0, 0, cnv.width, cnv.height);
ctx.save();
{
ctx.translate(u(ctxCenter.x), u(ctxCenter.y));
let rrSize = 85;
ctx.beginPath();
ctx.roundRect(-u(rrSize * 0.5), -u(rrSize * 0.5), u(rrSize), u(rrSize), u(rrSize * 0.1));
ctx.strokeStyle = basePalette[1];
ctx.lineWidth = u(2);
let bckGrd = ctx.createLinearGradient(0, -u(rrSize * 0.5), 0, u(rrSize * 0.5));
basePalette.forEach((pal, palIdx) => {
bckGrd.addColorStop(palIdx / (basePalette.length - 1), pal);
})
ctx.fillStyle = bckGrd;
ctx.stroke();
ctx.fill();
layers.forEach((layer) => {
// pre-compute noise
let layerPlus = ((layer.layerIdx + 1) * 0.75) ** 2.7;
noisyPoints.forEach((np, pIdx) => {
np.copy(points[pIdx]);
let noise = simplex.noise3d(np.x * layerPlus, np.y * layerPlus, t * (layerPlus ** 0.27)) * 0.5 + 0.5;
np.setLength(u((1 + noise) * 0.5 * layer.radius));
})
let pal = palette[layer.layerIdx];
ctx.strokeStyle = `hsl(${360 * pal.h}, ${100 * pal.s}%, ${100 * (pal.l + 0.125)}%)`;
ctx.fillStyle = `hsl(${360 * pal.h}, ${100 * pal.s}%, ${100 * pal.l}%)`;
ctx.lineWidth = u(0.5);
let r = u(0.5);
ctx.beginPath();
let pMinusOne = noisyPoints[noisyPoints.length - 1];
let pZero = noisyPoints[0];
ctx.moveTo((pMinusOne.x + pZero.x) * 0.5, (pMinusOne.y + pZero.y) * 0.5);
noisyPoints.forEach((p, pIdx) => {
let pNext = noisyPoints[(pIdx + 1) % noisyPoints.length];
ctx.quadraticCurveTo(p.x, p.y, (p.x + pNext.x) * 0.5, (p.y + pNext.y) * 0.5);
})
ctx.fill();
ctx.stroke();
})
}
ctx.restore();
}
源码:
https://codepen.io/prisoner849/pen/oNrOErX
体验:
https://codepen.io/prisoner849/full/oNrOErX