问题描述
目前,我正在玩一个脚本,该脚本生成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进行安装,那将是理想的选择。
解决方法
分形生成算法总是会随着缩放的深入而减慢速度,因为您越深(或在获得纾困之前),每个像素的迭代次数就越多。
这在解释语言中永远不会很快。当然,您可以对其进行调整以稍微提高速度,但是对于所有缩放级别,它永远不会为“实时”(例如,每图像
如果您想继续使用Python,则必须同意自己的看法,这永远不会很快。
但是。但是,您可以 将每个象限的生成分成单独的进程,每个进程将在各自的CPU /内核上运行。这样可以使您提高N / cores的速度。
可以通过检测图像中的对称性来执行一些优化,并且仅计算一半的像素,因为另一侧是它的镜像(如通过缩小的Mandelbrot set的水平轴) 。您可能可以参考古老的Fractint Program的来源以获取示例。
...
此外:我使用nVidia Mandelbrot set库在C语言中编写了其中一个(绘制CUDA),该库将计算结果分散到视频卡上的1200位数的“ CPU”(使用中档2018年笔记本电脑)。尽管对于相当大的图像或深度“放大”的分形来说,它工作得相当快,但它仍然变慢。涉及太多的数字运算。
,(这个问题最终使我安装了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()
这完全没有优化。
另外,正如金斯利(Kingsley)所写,缩放(此处未显示)
即使在GPU中,速度也在放慢速度(但是:未优化)。