我有两个版本的mandelbrot展示台。一个在CPU上计算,另一个在GPU上计算。对于两个版本,我基本上使用了相同的代码,但是它们的行为有所不同。
这是CPU版本的代码的重要位:
首先,根据复数坐标计算一些值:
double Renderer::mandelbrot(std::complex<double> c, int power) {
std::complex<double> z = {0.0, 0.0};
//limit is the number of iterations
for (int i = 0; i < limit; ++i) {
z = std::pow(z, power) + c;
if (std::norm(z) > 4) {
for (int j = 0; j < 3; ++j) {
z = std::pow(z, power) + c;
}
//LOG2INV = 1/log(2) -> #define LOG2INV 1.44269504089
double mu = i + 1 - std::log(std::log(std::abs(z))) * LOG2INV;
// If this is enabled, it won't repeat colours, this was disabled in the image.
if (m_state.m_smoother) {
mu = mu * m_state.m_viewports.back().limitInv * m_colours.size();
}
return mu;
}
}
return -1;
}
第二,基于该值,为其着色:
glm::vec3 Renderer::colourScheme(double divergence) {
int clr1 = divergence;
float t2 = divergence - clr1;
float t1 = 1 - t2;
clr1 = clr1 % m_colours.size();
int clr2 = (clr1 + 1) % m_colours.size();
return m_colours[clr1] * t1 + m_colours[clr2] * t2;
}
[m_colours
是具有12种不同颜色的glm::vec3
数组。
至于GPU版本,这是完整的片段着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(push_constant) uniform PushConstants {
dvec2 offset;
dvec2 scale;
uint limit;
double limitInv;
bool smoothGradient;
} fc;
//This will give coordinates of the pixel on the screen scaled to 1.
layout (location = 0) in vec2 outUV;
layout(location = 0) out vec4 outColor;
//Same values are used for the CPU version
const vec3 colours[12] = vec3[] (
vec3(1.0, 0.0, 0.0),
vec3(0.5, 0.0, 0.0),
vec3(1.0, 1.0, 0.0),
vec3(0.5, 0.5, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.5, 0.0),
vec3(0.0, 1.0, 1.0),
vec3(0.0, 0.5, 0.5),
vec3(0.0, 0.0, 1.0),
vec3(0.0, 0.0, 0.5),
vec3(1.0, 0.0, 1.0),
vec3(0.5, 0.0, 0.5)
);
dvec2 square(dvec2 z) {
return dvec2(z.x * z.x - z.y * z.y, 2 * z.x * z.y);
}
double norm(dvec2 z) {
return z.x * z.x + z.y * z.y;
}
double module(dvec2 z) {
return sqrt(z.x * z.x + z.y * z.y);
}
double mandelbrot(dvec2 c) {
dvec2 z = dvec2(0.0);
for (int i = 0; i < fc.limit; ++i) {
z = square(z) + c;
if (norm(z) > 4) {
for (int j = 0; j < 3; ++j) {
z = square(z) + c;
}
double mu = i + 1.0 - log(log(module(z))) * 1.44269504089;
if (fc.smoothGradient) {
mu = mu * fc.limitInv * colours.length();
}
return mu;
}
}
return -1;
}
void main() {
// Determines zoom level, also deals with the aspect ratio.
dmat2 scale = dmat2(fc.scale.x, 0.0,
0.0, fc.scale.y);
// Center the image, scales it and then offsets it.
dvec2 c = (outUV.xy - dvec2(0.5)) * scale + fc.offset;
double mu = mandelbrot(c);
if (mu == -1) {
outColor = vec4(vec3(0.0), 1.0);
} else {
int clr1 = int(mu);
float t2 = float(mu - clr1);
float t1 = 1 - t2;
clr1 = clr1 % colours.length();
int clr2 = (clr1 + 1) % colours.length();
outColor = vec4(vec3(colours[clr1] * t1 + colours[clr2] * t2), 1.0);
}
}
您可以看到坐标变换得很好(或者在两种情况下至少是相同的方式),但是颜色的作用不同。我并不在乎它们的顺序,我只是希望GPU颜色具有平滑的渐变,与CPU版本相同。我猜测问题是某些值被限制在GPU上的某个位置,但我无法确定位置。
我尚未完全确认这一点,但我想我知道问题出在哪里。
CPU版本会遍历每个屏幕坐标,然后将其转换为复数坐标(一对双精度),以进行进一步的计算。这样就可以了。
另一方面,GPU绘制与屏幕重叠的单个三角形,并通过outUV变量将标准化后的空间坐标发送到片段着色器。然后将其插值并转换为复数坐标。但是,插值是在float变量上完成的,因为那里不支持double。我认为这种精度损失会在迭代过程中传播并最终导致问题。
我不确定是否要回到程序中进行检查,如果可以,我将编辑答案,但是如果有人遇到类似的问题,可以从此开始。