我正在尝试使用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;
用此功能替换功能
generateImage
和iterate
// 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/