javascript - Javascript Julia Fractal缓慢且不详细

标签 javascript performance fractals math.js

我正在尝试使用math.js在JavaScript的画布中生成Julia分形

不幸的是,每次在画布上绘制分形时,它都相当缓慢且不够细致。

谁能告诉我该脚本执行速度太慢的特定原因,还是浏览器要求很高? (注意:鼠标移动部分已禁用,仍然有点慢)

我曾尝试升高和降低“ bail_num”,但高于1的所有内容都会导致浏览器崩溃,低于0.2的所有内容会使浏览器崩溃。

// Get the canvas and context
var canvas = document.getElementById("myCanvas"); 
var context = canvas.getContext("2d");

// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;

// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);

// Pan and zoom parameters
var offsetx = -imagew/2;
var offsety = -imageh/2;
var panx = -2000;
var pany = -1000;
var zoom = 12000;

// c complexnumber
var c = math.complex(-0.310, 0.353);

// Palette array of 256 colors
var palette = [];

// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 1;


// Initialize the game
function init() {

//onmousemove listener
canvas.addEventListener('mousemove', onmousemove);

    // Generate image
    generateImage();

    // Enter main loop
    main(0);
}

// Main loop
function main(tframe) {
    // Request animation frames
    window.requestAnimationFrame(main);

    // Draw the generate image
    context.putImageData(imagedata, 0, 0);
}

// Generate the fractal image
function generateImage() {
    // Iterate over the pixels
    for (var y=0; y<imageh; y++) {
        for (var x=0; x<imagew; x++) {
            iterate(x, y, maxiterations);
        }
    }
}

// Calculate the color of a specific pixel
function iterate(x, y, maxiterations) {
    // Convert the screen coordinate to a fractal coordinate
    var x0 = (x + offsetx + panx) / zoom;
    var y0 = (y + offsety + pany) / zoom;
    var cn = math.complex(x0, y0);

    // Iterate
    var iterations = 0;
    while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
        cn = math.add( math.sqrt(cn) , c);   
        iterations++;
    }

    // Get color based on the number of iterations
    var color;
    if (iterations == maxiterations) {
        color = { r:0, g:0, b:0}; // Black
    } else {
        var index = Math.floor((iterations / (maxiterations)) * 255);
        color = index;
    }

    // Apply the color
    var pixelindex = (y * imagew + x) * 4;
    imagedata.data[pixelindex] = color;
    imagedata.data[pixelindex+1] = color;
    imagedata.data[pixelindex+2] = color;
    imagedata.data[pixelindex+3] = 255;
}


function onmousemove(e){
var pos = getMousePos(canvas, e);
//c = math.complex(-0.3+pos.x/imagew, 0.413-pos.y/imageh);

//console.log( 'Mouse position: ' + pos.x/imagew + ',' + pos.y/imageh );

// Generate a new image
    generateImage();

}

function getMousePos(canvas, e) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
        y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
    };
}
init();

最佳答案

这段代码中执行最多的部分是:

while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
    cn = math.add( math.sqrt(cn) , c);   
    iterations++;
}


对于给定的画布大小和使用的偏移量,以上while主体执行了19,575,194次。因此,有一些明显的方法可以提高性能:


以某种方式减少了必须执行循环的点数
以某种方式减少了每点执行这些语句的次数
以某种方式改进这些语句,以便它们更快地执行


第一个想法很简单:减小画布尺寸。但这可能不是您想要执行的操作。

第二个想法可以通过减少bail_num的值来实现,因为这样会更快地违反while条件(假定复数的范数始终为正实数)。但是,这只会导致更多的黑度,并提供与缩小分形中心相同的视觉效果。尝试使用0.225:仅存在一个“遥远的恒星”。当bail_num减少太多时,您会再也找不到分形了,因为一切都变黑了。因此,要进行补偿,您可能需要更改偏移量和缩放系数,以在分形的中心(仍然存在,BTW!)获得更近的视野。但是朝向分形的中心,点需要更多的迭代才能到达bail_num以下,因此最终没有获得任何收益:使用此方法,您将回到平方一。这不是真正的解决方案。

解决第二个想法的另一种方法是减少最大化。但是,这将相应降低分辨率。显然,您将可以使用的颜色更少,因为该数字直接对应于最多可以进行的迭代次数。

第三个想法意味着您将以某种方式优化带有复数的计算。事实证明可以带来很多收益:

使用有效的计算

while条件下计算出的范数可以用作计算相同数字平方根的中间值,这在下一条语句中是必需的。这是从复数中求平方根的公式,如果您已经有了它的范数:

           __________________
root.re = √ ½(cn.re + norm)
root.im = ½cn.im/root.re


re和im属性表示各个复数的实部和虚部。您可以在this answer on math.stackexchange中找到这些公式的背景。

就像在您的代码中一样,平方根是单独计算的,而没有利用先前的规范计算,这肯定会带来好处。

同样,在while条件下,您实际上不需要与bail_num进行比较的规范(涉及平方根)。您可以省略平方根运算,然后与bail_num的平方进行比较,这归结为同一件事。显然,您只需在代码开始时计算一次bail_num的平方。这样,您可以在发现条件为真时延迟平方根运算。计算范数平方的公式如下:

square_norm = cn.re² + cn.im²


math对象的方法调用有一些开销,因为该库在其多个方法中允许使用不同类型的参数。因此,如果您不依赖math.js直接对计算进行编码,则将有助于提高性能。无论如何,上面的改进已经开始这样做了。在我的尝试中,这也导致了性能的显着提高。

预定义颜色

尽管与昂贵的while循环无关,但您可以通过在代码开始时计算所有可能的颜色(按迭代次数)并将其存储在以迭代次数为键的数组中,从而获得更多收益。这样,您就可以在实际计算中进行查找。

可以做一些其他类似的事情来节省计算量:例如,您可以避免沿X轴移动时将屏幕y坐标转换为世界坐标,因为它始终是相同的值。

这是在我的PC上将原始完成时间减少了10倍的代码:

增加了初始化:

// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
    // Note that I have stored colours in the opposite direction to 
    // allow for a more efficient "countdown" loop later
    colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;


用此功能替换功能generateImageiterate

// Generate the fractal image
function generateImage() {
    // Iterate over the pixels
    var pixelindex = 0,
        step = 1/zoom,
        worldX, worldY,
        sq, rootX, rootY, x0, y0;
    for (var y=0; y<imageh; y++) {
        worldY = (y + offsety + pany)/zoom;
        worldX = (offsetx + panx)/zoom;
        for (var x=0; x<imagew; x++) {
            x0 = worldX;
            y0 = worldY;
            // For this point: iterate to determine color index
            for (var iterations = maxiterations; iterations && (sq = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
                // root of complex number
                rootX = Math.sqrt((x0 + Math.sqrt(sq))/2);
                rootY = y0/(2*rootX);
                x0 = rootX + cx;
                y0 = rootY + cy;
            }
            // Apply the color
            imagedata.data[pixelindex++] = 
                imagedata.data[pixelindex++] = 
                imagedata.data[pixelindex++] = colors[iterations];
            imagedata.data[pixelindex++] = 255;
            worldX += step;
        }
    }
}


使用上面的代码,您不再需要包含math.js

这是处理鼠标事件的较小代码段:



// Get the canvas and context
var canvas = document.getElementById("myCanvas"); 
var context = canvas.getContext("2d");

// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;

// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);

// Pan and zoom parameters
var offsetx = -512
var offsety = -430;
var panx = -2000;
var pany = -1000;
var zoom = 12000;

// Palette array of 256 colors
var palette = [];

// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 0.8; //0.225; //1.15;//0.25;

// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
    colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;

// Initialize the game
function init() {

    // onmousemove listener
    canvas.addEventListener('mousemove', onmousemove);

    // Generate image
    generateImage();

    // Enter main loop
    main(0);
}

// Main loop
function main(tframe) {
    // Request animation frames
    window.requestAnimationFrame(main);

    // Draw the generate image
    context.putImageData(imagedata, 0, 0);
}

// Generate the fractal image
function generateImage() {
    // Iterate over the pixels
    console.log('generate', cx, cy);
    var pixelindex = 0,
        step = 1/zoom,
        worldX, worldY,
        sq_norm, rootX, rootY, x0, y0;
    for (var y=0; y<imageh; y++) {
        worldY = (y + offsety + pany)/zoom;
        worldX = (offsetx + panx)/zoom;
        for (var x=0; x<imagew; x++) {
            x0 = worldX;
            y0 = worldY;
            // For this point: iterate to determine color index
            for (var iterations = maxiterations; iterations && (sq_norm = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
                // root of complex number
                rootX = Math.sqrt((x0 + Math.sqrt(sq_norm))/2);
                rootY = y0/(2*rootX);
                x0 = rootX + cx;
                y0 = rootY + cy;
            }
            // Apply the color
            imagedata.data[pixelindex++] = 
                imagedata.data[pixelindex++] = 
                imagedata.data[pixelindex++] = colors[iterations];
            imagedata.data[pixelindex++] = 255;
            worldX += step;
        }
    }
    console.log(pixelindex);
}


function onmousemove(e){
    var pos = getMousePos(canvas, e);
    cx = -0.31+pos.x/imagew/150;
    cy = 0.35-pos.y/imageh/30;
    

    generateImage();
}

function getMousePos(canvas, e) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
        y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
    };
}
init();

<canvas id="myCanvas" width="512" height="200"></canvas>

关于javascript - Javascript Julia Fractal缓慢且不详细,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36742814/

相关文章:

php - 分形解释

java - 分形和 "java.lang.OutOfMemoryError: Java heap space"

java - 平滑地制作分形树的动画

javascript - 如何防止历史记录在计算器中消失

Javascript 书签在 Firefox 41 中停止工作

javascript - 如何使 CSS Accordion 在从一个部分转到另一个部分时自动关闭打开的内容

javascript - VueJS + Vuex + Vuetify 抽屉导航

c# - 强类型数据集会提高性能吗?

c - 如何提高这个 Haskell 程序的性能?

oracle sql如何知道哪部分查询耗时较长