Самые короткие и простые способы генерации различных фракталов или других изображений
Я интересуюсь компьютерной графикой и хотел бы провести своего рода конкурс.
Я хотел бы узнать о новых (для себя) способах генерации различных фракталов или других изображений, полученных по достаточно простой формуле.
То есть критерием соревнования является, - использование простой базовой формул
для получения интересных картинок.
Например есть такая реализация цикла по всем пикселям на картинке:
let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height
let formula = (x, y, cx, cy, m) => {
return [x/w+cx/w, y/h+cy/h, 0]
}
canvas.onmousemove = e => {
var img = c.getImageData(0, 0, w, h)
for(var x = 0; x
Необходимо реализовать функцию formula для получения "интересного" изображения, дополнительными аргументами выступают координаты мыши
Язык - любой, но желательно js, из-за возможности онлайн визуализации.
PS: рекурсивные методы мне менее интересны, особенно если рекурсия не хвостовая
так какпортировать это на glsl будет сложно если не невозможно.
P.P.S. для привлечения более широкой аудитории мной был выбран cpu и код цикла п
картинке я написал для него, однако если Вам угодно, мне больше импонирует webgl, по этому ниже сниппет, где цикл по всем пикселям делает видеокарта, когда я рисую один треугольник, закрывающий весь экран, а функция formula - это фрагментный шейдер :)
let gl = canvas.getContext('webgl');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 3, -1, -1, 3, -1]), gl.STATIC_DRAW);
let pid = gl.createProgram();
shader('vertex', gl.VERTEX_SHADER);
shader('fragment', gl.FRAGMENT_SHADER);
gl.linkProgram(pid);
gl.useProgram(pid);
let coords = gl.getAttribLocation(pid, "coords");
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coords);
let mouse = gl.getUniformLocation(pid, 'mouse');
let resolution = gl.getUniformLocation(pid, 'resolution');
gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);
let changeCenter = e => {
e = e.touches ? e.touches[0] : e;
gl.uniform2f(mouse, e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop);
draw();
}
window.addEventListener('mousemove', changeCenter);
window.addEventListener('touchmove', changeCenter);
draw();
function draw() {
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
function shader(src, type) {
let sid = gl.createShader(type);
gl.shaderSource(sid, document.querySelector(`script[type="glsl/${src}"]`).textContent);
gl.compileShader(sid);
var message = gl.getShaderInfoLog(sid);
gl.attachShader(pid, sid);
if (message.length > 0) {
console.log(src.split('\n').map(function (str, i) {
return ("" + (1 + i)).padStart(4, "0") + ": " + str
}).join('\n'));
throw message;
}
}
Ответы
Ответ 1
Пример с бассейном Ньютона.
Области подкрашиваются в соответствии с близостью к корням уравнения z3-1 = 0
class Complex {
constructor({algebraic,trigonometric} = {}) {
if (algebraic) {
this.initAlgebraic(algebraic);
} else if (trigonometric) {
this.initTrigonometric(trigonometric)
} else {
throw new Error('Invalid arguments');
}
}
static fromReal(r) {
return Complex.fromAlgebraic(r, 0);
}
static fromAlgebraic(r, i) {
return new Complex({algebraic: {r,i}});
}
static fromTrigonometric(modulus, arg) {
return new Complex({trigonometric: {modulus,arg}});
}
initAlgebraic({r,i}) {
this.r = r;
this.i = i;
this.modulus = Math.sqrt(r * r + i * i);
this.arg = Math.atan2(i, r)
}
initTrigonometric({modulus,arg}) {
this.modulus = modulus,
this.arg = arg;
this.r = this.modulus * Math.cos(arg);
this.i = this.modulus * Math.sin(arg);
}
pow(exp) {
return Complex.fromTrigonometric(Math.pow(this.modulus, exp), exp * this.arg);
}
add(c) {
return Complex.fromAlgebraic(this.r + c.r, this.i + c.i);
}
conjugate(){
return Complex.fromAlgebraic(this.r, -this.i);
}
sub(c){
return Complex.fromAlgebraic(this.r - c.r, this.i - c.i);
}
mul(c){
return Complex.fromAlgebraic(this.r*c.r-this.i*c.i, this.r*c.i+this.i*c.r);
}
div(c){
var cConjugate = c.conjugate();
var mulToConjugate = this.mul(cConjugate);
var divider = c.mul(cConjugate).r;
return Complex.fromAlgebraic(mulToConjugate.r/divider, mulToConjugate.i/divider);
}
}
let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height
function formula(x,y, maxIter, max, min){
var z = Complex.fromAlgebraic(x,y);
var d = z;
for(var i=0;imin;i++){
var z1 = z.sub(z.pow(3).add(Complex.fromReal(-1)).div(z.pow(2).mul(Complex.fromReal(3))));
//var z1 = z.sub(z.pow(5).add(Complex.fromReal(-1)).div(z.pow(4).mul(Complex.fromReal(5))));
var z2 = z1.sub(z);
d = Complex.fromAlgebraic(Math.abs(z2.r),Math.abs(z2.i));
z = z1;
}
return [i, z];
}
var input = document.getElementById('coef');
input.addEventListener('change',function(){
draw(this.value);
});
var roots = [
Complex.fromReal(1),
Complex.fromAlgebraic(-1, Math.sqrt(3)/2),
Complex.fromAlgebraic(-1, -Math.sqrt(3)/2),
]
var maxDistance = input.max;
function draw(distance){
var xc = w/2;
var yc = h/2;
var coef = 0.0015 + distance * ((0.015-0.0015)/maxDistance);
var img = c.getImageData(0, 0, w, h);
var aaa = 0;
for(var y = -h/2; y
Для урaвнения z5-1 = 0
class Complex {
constructor({algebraic,trigonometric} = {}) {
if (algebraic) {
this.initAlgebraic(algebraic);
} else if (trigonometric) {
this.initTrigonometric(trigonometric)
} else {
throw new Error('Invalid arguments');
}
}
static fromReal(r) {
return Complex.fromAlgebraic(r, 0);
}
static fromAlgebraic(r, i) {
return new Complex({algebraic: {r,i}});
}
static fromTrigonometric(modulus, arg) {
return new Complex({trigonometric: {modulus,arg}});
}
initAlgebraic({r,i}) {
this.r = r;
this.i = i;
this.modulus = Math.sqrt(r * r + i * i);
this.arg = Math.atan2(i, r)
}
initTrigonometric({modulus,arg}) {
this.modulus = modulus,
this.arg = arg;
this.r = this.modulus * Math.cos(arg);
this.i = this.modulus * Math.sin(arg);
}
pow(exp) {
return Complex.fromTrigonometric(Math.pow(this.modulus, exp), exp * this.arg);
}
add(c) {
return Complex.fromAlgebraic(this.r + c.r, this.i + c.i);
}
conjugate(){
return Complex.fromAlgebraic(this.r, -this.i);
}
sub(c){
return Complex.fromAlgebraic(this.r - c.r, this.i - c.i);
}
mul(c){
return Complex.fromAlgebraic(this.r*c.r-this.i*c.i, this.r*c.i+this.i*c.r);
}
div(c){
var cConjugate = c.conjugate();
var mulToConjugate = this.mul(cConjugate);
var divider = c.mul(cConjugate).r;
return Complex.fromAlgebraic(mulToConjugate.r/divider, mulToConjugate.i/divider);
}
}
let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height
function formula(x,y, maxIter, max, min){
var z = Complex.fromAlgebraic(x,y);
var d = z;
for(var i=0;imin;i++){
//var z1 = z.sub(z.pow(3).add(Complex.fromReal(-1)).div(z.pow(2).mul(Complex.fromReal(3))));
var z1 = z.sub(z.pow(5).add(Complex.fromReal(-1)).div(z.pow(4).mul(Complex.fromReal(5))));
var z2 = z1.sub(z);
d = Complex.fromAlgebraic(Math.abs(z2.r),Math.abs(z2.i));
z = z1;
}
return [i, z];
}
var input = document.getElementById('coef');
input.addEventListener('change',function(){
draw(this.value);
});
var maxDistance = input.max;
function draw(distance){
var xc = w/2;
var yc = h/2;
var coef = 0.0015 + distance * ((0.015-0.0015)/maxDistance);
var img = c.getImageData(0, 0, w, h);
var aaa = 0;
for(var y = -h/2; y
Ответ 2
Предлагаю Вашему вниманию Суперформулу
Суперформула является обобщением суперэллипса и впервые была выведена Йоханом Гиелисо
в 2003 году. Гиелис предположил использовать формулу для описания сложных форм и кривых, которые встречаются в природе.
В полярной системе координат, с радиусом, и углом, суперформула выглядит так:
Выбирая различные значения параметров , получаются различные формы.
let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height
// функция возвращает расстояние в полярной системе координат для угла phi, идущего первым аргументом
function superformula(phi, m, n1, a, b, n2, n3) {
with (Math) {
m = m*phi/4;
a = pow(abs(cos(m))/a, n2);
b = pow(abs(sin(m))/b, n3);
return pow(a + b, -1/n1);
}
}
let formula = (x, y, cx, cy) => {
x = (2*x-w)/w*(3-cy/h);
y = (2*y-h)/w*(3-cy/h);
let a = Math.atan2(y, x) - cx/w*Math.PI;
let d = superformula(a, t.m, t.n1, t.a, t.b, t.n2, t.n3);
let l = Math.sqrt(x*x + y*y);
let c = Math.min(d-l)*10.;
return [c, c, c]
}
let types = {
asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1},
butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1},
circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1},
clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1},
cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1},
cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1},
diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1},
drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1},
gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1},
heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18},
heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1},
malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1},
pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1},
rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1},
roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1},
square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1},
star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1},
triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1}
};
let t = Object.values(types)[0];
let draw = e => {
var img = c.getImageData(0, 0, w, h)
for(var x = 0; x {
draw(e);
}
canvas.onclick = e => {
let vals = Object.values(types);
let i = Math.floor(Math.random()*vals.length);
t = vals[i];
document.querySelector('span').textContent = 'click to change: ' + Object.keys(types)[i]
draw(e)
}
click to change: asterisk
Ответ 3
Самое простое что известно мне, помимо фракталов в комплексной плоскости - это фрактал kali, назван в честь опубликовавшего эту формулу на fractalforums
формула в glsl для него вообще простая:
vec2 q = vec2(x, y);
for(var i=0; i<10; i++)
q = abs(q)/dot(q,q) - vec2(cx, cy);
на js чуть посложнее из-за отсутствия операций над векторами, но все равно очен
простая:
let formula = (x, y, cx, cy, m) => {
x = (2*x-w)/w;
y = (2*y-h)/w;
for (var i=0; i<10; i++) {
x = Math.abs(x)
y = Math.abs(y)
m = x*x + y*y
x = x/m - cx/w
y = y/m - cy/h
}
return [x, y, Math.sqrt(x*x+y*y)/2.]
}
Вот результат для cpu версии, мышка меняет переменные в формуле для получения другого изображения:
let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height
let formula = (x, y, cx, cy, m) => {
x = (2*x-w)/w;
y = (2*y-h)/w;
for (var i=0; i<10; i++) {
x = Math.abs(x)
y = Math.abs(y)
m = x*x + y*y
x = x/m - cx/w
y = y/m - cy/h
}
return [x, y, Math.sqrt(x*x+y*y)/2.]
}
canvas.onmousemove = e => {
var img = c.getImageData(0, 0, w, h)
for(var x = 0; x
GPU вариация и другая палитра
let gl = canvas.getContext('webgl');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 3, -1, -1, 3, -1]), gl.STATIC_DRAW);
let pid = gl.createProgram();
shader('vertex', gl.VERTEX_SHADER);
shader('fragment', gl.FRAGMENT_SHADER);
gl.linkProgram(pid);
gl.useProgram(pid);
let coords = gl.getAttribLocation(pid, "coords");
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coords);
let mouse = gl.getUniformLocation(pid, 'mouse');
let resolution = gl.getUniformLocation(pid, 'resolution');
gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);
let changeCenter = e => {
e = e.touches ? e.touches[0] : e;
gl.uniform2f(mouse, e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop);
draw();
}
window.addEventListener('mousemove', changeCenter);
window.addEventListener('touchmove', changeCenter);
draw();
function draw() {
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
function shader(src, type) {
let sid = gl.createShader(type);
gl.shaderSource(sid, document.querySelector(`script[type="glsl/${src}"]`).textContent);
gl.compileShader(sid);
var message = gl.getShaderInfoLog(sid);
gl.attachShader(pid, sid);
if (message.length > 0) {
console.log(src.split('\n').map(function (str, i) {
return ("" + (1 + i)).padStart(4, "0") + ": " + str
}).join('\n'));
throw message;
}
}
Комментариев нет:
Отправить комментарий