如何使用Pygame快速渲染Julia Set?

问题描述

目前,我正在玩一个脚本,该脚本生成Julia集和Mandelbrot集,然后使用pygame渲染这些点。

本质上,屏幕被映射到一个较小的坐标系,在该坐标系中,屏幕由x轴上的-2.5,2.5和y轴上的-1,1界定。然后,将此映射范围内的每个像素传递给函数,以检查其复数等效项是否在给定集中。此函数返回计算迭代次数是否在集合中(或最大迭代次数)的迭代次数

然后,对于每个像素,我都知道根据此迭代得分为它着色的颜色,并逐个渲染每个像素。该过程的这一部分确实很费力,渲染大约需要30秒,但取决于设置的复杂性,可以花更多的时间。

这里是用于确定传递的复数和复数坐标是否在Julia集中的代码,在检查1920 * 1080像素时,这根本不需要很长时间来计算:

max_iter = 45


def julia(z,c):
    n = 0
    while abs(z) <= 2 and n < max_iter:
        z = z * z + c
        n += 1
    return n

这是我用于pygame渲染的代码,这绝对是问题所在:

size_ = 1920,1080
re_ = -2.5,2.5
im_ = -1,1
surf = pygame.Surface(size)
colour_gradient1 = [c,c1,c2,c3,...] # This is some list of colours generated by a gradient function

for x in range(0,size_[0]):
    for y in range(0,size_[1]):
        z = complex(re_[0] + (x / size_[0]) * (re_[1] - re_[0]),im_[0] + (y / size_[1]) * (im_[1] - im_[0]))
        m = julia(z,c)
        colour = colour_gradient1[m]
        pygame.draw.rect(surf,colour,(x,y,1,1))

我想我理解为什么这是性能密集型的,因为pygame和python并没有针对将内容渲染到这样的屏幕进行真正的优化。我目前正在尝试学习C ++,对于这样的东西我了解得更多。

我还尝试了缩放功能,可以用鼠标选择一个框,脚本将渲染该选定区域,但是实施此功能后问题就一直存在。随着放大分形变得越来越复杂,该脚本花了太长时间才能使用此功能

所以我的问题是,有没有更好的方法可以使用python和pygame实时渲染类似这样的内容?我愿意使用其他程序包,但如果可以通过pygame进行安装,那将是理想的选择。

下面是生成的集合的几张图片

This is an example of one of the fractals generated.

This is an example of one of the fractals generated.

This is an example of one of the fractals generated.

解决方法

分形生成算法总是会随着缩放的深入而减慢速度,因为您越深(或在获得纾困之前),每个像素的迭代次数就越多。

这在解释语言中永远不会很快。当然,您可以对其进行调整以稍微提高速度,但是对于所有缩放级别,它永远不会为“实时”(例如,每图像

如果您想继续使用Python,则必须同意自己的看法,这永远不会很快。

但是。但是,您可以 将每个象限的生成分成单独的进程,每个进程将在各自的CPU /内核上运行。这样可以使您提高N / cores的速度。

可以通过检测图像中的对称性来执行一些优化,并且仅计算一半的像素,因为另一侧是它的镜像(如通过缩小的Mandelbrot set的水平轴) 。您可能可以参考古老的Fractint Program的来源以获取示例。

...

此外:我使用nVidia Mandelbrot set库在C语言中编写了其中一个(绘制CUDA),该库将计算结果分散到视频卡上的1200位数的“ CPU”(使用中档2018年笔记本电脑)。尽管对于相当大的图像或深度“放大”的分形来说,它工作得相当快,但它仍然变慢。涉及太多的数字运算。

mandelbrot section

,

(这个问题最终使我安装了PyOpenGL。非常感谢!)

据我所知,分别遍历每个像素,
永远不会提供良好的性能(不是在C ++ / C / Assembly /中)。
向量化(在CPU中)将有所帮助。什么真正有帮助,
正在使用GPU的能力执行一项操作(/内核),
平行排列到整个多维元素数组。

专门:使用片段着色器来计算
每个像素的颜色。但这意味着使用图形API
例如OpenGL(/ Vulkan / Direct3D /),或GPGPU /计算API,例如
OpenCL(/ CUDA/)。

如果在图形管线中使用了生成的图像,
那么它可以保留在GPU上并直接从
显示 那里。如果需要使用生成的图像,例如在
GUI,保存到磁盘或类似的文件,需要从
GPU到CPU(可能是渲染到纹理,读取帧缓冲区,
使用屏幕外缓冲区或其他我不知道的选项。

import numpy as np

from OpenGL.GL import *
from OpenGL.GL import shaders
from OpenGL.GLUT import *

# Vertex shader: Pass through (no model-view-projection).
vsSrc = '''
#version 300 es

layout (location = 0) in vec4 posIn;

void main()
{
    gl_Position = posIn;
}
'''

# Fragment shader: Compute fractal color,per-pixel.
# en.wikipedia.org/wiki/Mandelbrot_set#Computer_drawings
fsSrc = '''
#version 300 es

precision mediump float;

out vec4 colorOut;

vec2 mapLinear(
  vec2 val,vec2 srcMin,vec2 srcMax,vec2 dstMin,vec2 dstMax
) {
    vec2 valNorm = (val - srcMin) / (srcMax - srcMin);
    return valNorm * (dstMax - dstMin) + dstMin;
}

void main()
{
  // Debugging: Return fixed color; see which pixels get it.
  //colorOut = vec4(0.0,0.5,0.0,1.0);
  //return;
  
  // Originally,origin is top-left. Convert to Cartesian.
  vec2 pixelMin = vec2(0.0f,720.0f);
  vec2 pixelMax = vec2(1280.0f,0.0f);
  
  vec2 mbMin = vec2(-2.5f,-1.0f);
  vec2 mbMax = vec2(1.0f,1.0f);
  vec2 mbExtent = mbMax - mbMin;
  vec2 mbCenter = mbMin + (mbExtent / 2.0f);
  
  vec2 fragMapped = mapLinear(
    gl_FragCoord.xy,pixelMin,pixelMax,mbMin,mbMax
  );
  
  float real = 0.0f;
  float imag = 0.0f;
  int iter = 0;
  const int maxIter = 500;
  while (
    ((real*real + imag*imag) < 4.0f) && 
    (iter < maxIter)
  ) {
    float realTemp = real*real - imag*imag + fragMapped.x;
    imag = 2.0f*real*imag + fragMapped.y;
    real = realTemp;
    ++iter;
  }
  
  // Using generated colors,instead of indexing a palette.
  // (Don't remember anymore where this came from,// or if it was a heuristic.)
  vec3 chosenColor;
  float iterNorm = float(iter) / float(maxIter);
  if (iterNorm > 0.5f) {
    float iterNormInverse = 1.0f - iterNorm;
    chosenColor = vec3(
      0.0f,iterNormInverse,iterNormInverse
    );
  }
  else {
    chosenColor = vec3(0.0f,iterNorm,iterNorm);
  }
  
  colorOut = vec4(chosenColor.xyz,1.0f);
}
'''

def compileFractalShader():
  vs = shaders.compileShader(vsSrc,GL_VERTEX_SHADER)
  fs = shaders.compileShader(fsSrc,GL_FRAGMENT_SHADER)
  return shaders.compileProgram(vs,fs)

# Geometry: Just 2 triangles,covering the display surface.
# (So that the fragment shader runs for all surface pixels.)
def drawTriangles():
  topLeftTriangle = (
    1.0,1.0,-1.0,0.0
  )
  bottomRightTriangle = (
    1.0,0.0
  )
  verts = np.array(
    topLeftTriangle + bottomRightTriangle,dtype=np.float32
  )
  
  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,verts)
  glEnableVertexAttribArray(0)
  
  glDrawArrays(GL_TRIANGLES,6)

def printShaderException(e):
  errorMsg,shaderSrc,shaderType = e.args
  print('Shader error message:')
  for line in errorMsg.split('\\n'): print(line)
  print('--')
  #print('Shader source:')
  #for line in shaderSrc[0].split(b'\n'): print(line)
  #print('--')
  print('Shader type:',shaderType)

WIDTH = 1280
HEIGHT = 720

glutInit()
glutInitWindowSize(WIDTH,HEIGHT)
glutCreateWindow('Fractals with fragment shaders.')

# Create shaders,after creating a window / opengl-context:
try: fractalShader = compileFractalShader()
except RuntimeError as e: 
  printShaderException(e)
  exit()

glViewport(0,WIDTH,HEIGHT)

glClearColor(0.5,1.0)

def display():
  glClear(GL_COLOR_BUFFER_BIT)
  with fractalShader: drawTriangles()
  glutSwapBuffers()
glutDisplayFunc(display)

glutMainLoop()

enter image description here

这完全没有优化。
另外,正如金斯利(Kingsley)所写,缩放(此处未显示)
即使在GPU中,速度也在放慢速度(但是:未优化)。