使用 LOVE2d 在 GLSL 中通过着色器设置 Mandelbrot 渲染一个圆,而不是分形
Mandelbrot Set through shaders in GLSL with LOVE2d renders a circle, not a fractal
我正在尝试使用 GLSL 渲染 Mandelbrot 集,但我得到的只是一个完整的圆...
我检查了很多数学,但我根本找不到错误,所以我想可能是语义问题。
有谁能看出问题所在吗?
还有,谁能给我一些关于组织、结构等方面的见解?我正在尝试学习正确的编码,但很难找到 material 样式。
观察:着色器可以应用于任何图像
思路很简单(可以略过):
- checkConvergence returns true if z has not diverged (i.e., abs(z) < 4
- sumSquare returns the complex multiplication (z.r, z.i)*(z.r, z.i) - (c.r, c.i), where K.r = Re(K) and K.i = Im(K)
- iterate is the actual algorithm: it keeps squaring z until it diverges or it reaches a maximum number of iterations (tol)
- clr is parameterized function which returns a RGB array which depends on n
- finally, effect is where the magic should happen, by doing 2 things:
- It takes the coordinates of the pixel (GLSL normalizes to the interval(0, 1) and normalizes it to the Mandelbrot set size (-2
- It calls clr over iterate over the coordinates.
GLSLShader = love.graphics.newShader[[
vec2 c = vec2(1.0, 0.0);
int tol = 100;
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
bool checkConvergence(vec2 z) {
return (z[0]*z[0] + z[1]*z[1] < 4);
}
vec2 sumSquare(vec2 z) {
return vec2(z[0]*z[0] - z[1]*z[1] - c[0], 2 * z[0] * z[1] - c[1]);
}
int iterate(vec2 z) {
int n = 0;
while (checkConvergence(z) && (n < tol)) {
vec2 z = sumSquare(z);
n = n + 1;
}
return n;
}
vec4 clr(int n){
if(n == tol){return vec4(0.0,0.0,0.0,1.0);}
int r, g, b;
r = int(255*n + 47*(1-n));
g = int(180*n + 136*(1-n));
b = int(38*n + 255*(1-n));
if (r > 255) {r = 255;}
else{
if(r<0){r = 0;}
}
if (g > 255) {g = 255;}
else{
if(g<0){g = 0;}
}
if (b > 255) {b = 255;}
else{
if(b<0){b = 0;}
}
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);
return clr(iterate(z));
}
]]
更新
我尝试了@WeatherVane 的一些建议:
- 添加 C 而不是减去它;
- 将z作为(0.0,0.0)开始,将迭代的起点作为C传递;
但是都无济于事,我还是搞定了一个圈子。我也试过使用更多的迭代
我试图在不做太多改动的情况下简化编码。
我给clr函数加了一个条件,returns如果n小于0或者大于1就变绿。奇怪的是圆圈是黑色的,屏幕的其余部分是白色的,但我看不出这种白色是从哪里来的(因为 0 < n < 1 时 clr 从来没有 returns 白色)。
vec2 c = vec2(0.0, 0.0);
int tol = 1000;
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
int iterate(vec2 z) {
int n = 0;
while ( (z[0]*z[0] + z[1]*z[1] < 4) && (n < tol) ) {
vec2 z = vec2( z[0]*z[0] - z[1]*z[1] + c[0], 2 * z[0] * z[1] + c[1] );
n = n + 1;
}
return n;
}
vec4 clr(int n){
n = n / tol;
if(n == 1){return black;}
if(n > 1 || n < 0){return green;}
int r, g, b;
r = int(255*n + 47*(1-n));
g = int(180*n + 136*(1-n));
b = int(38*n + 255*(1-n));
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);
return clr(iterate(z));
}
迭代的起点应该作为向量c传递,而z从{0.0.0.0}开始。
您可以在 https://en.wikipedia.org/wiki/Mandelbrot_set
中找到
a complex number c is part of the Mandelbrot set if, when starting
with z = 0 and applying the iteration z = z² + c repeatedly, the
absolute value of z remains bounded however large n gets.
您正在使用 vec4 作为颜色,而不是使用浮点数,在您的代码中您正在计算具有整数值的 RGB 分量。您应该转换为浮动并将每个组件规范化到 (0.0,1.0) 范围。我试图更正您的代码,但恐怕我真的不知道 lua 和 love2d,而且我无法使用 texture_coords,所以我使用了 screen_coords。我能做的最好的是:
function love.load()
GLSLShader = love.graphics.newShader[[
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
int max_iter = 1024;
vec4 clr(int n){
if(n == max_iter){return black;}
float m = float(n)/float(max_iter);
float r = float(mod(n,256))/32;
float g = float(128 - mod(n+64,127))/255;
float b = float(127 + mod(n,64))/255;
if (r > 1.0) {r = 1.0;}
else{
if(r<0){r = 0;}
}
if (g > 1.0) {g = 1.0;}
else{
if(g<0){g = 0;}
}
if (b > 1.0) {b = 1.0;}
else{
if(b<0){b = 0;}
}
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 c = vec2((screen_coords[0]-500)/200,(screen_coords[1]-300)/200);
vec2 z = vec2(0.0,0.0);
vec2 zn = vec2(0.0,0.0);
int n_iter = 0;
while ( (z[0]*z[0] + z[1]*z[1] < 4) && (n_iter < max_iter) ) {
zn[0] = z[0]*z[0] - z[1]*z[1] + c[0];
zn[1] = 2*z[0]*z[1] + c[1];
z[0] = zn[0];
z[1] = zn[1];
n_iter++;
}
return clr(n_iter);
}
]]
end
function love.draw()
love.graphics.setShader(GLSLShader)
love.graphics.rectangle('fill', 0,0,800,600)
love.graphics.setShader()
end
这给了我这个输出:
while ( ... ) {
vec2 z = ...
}
去掉 vec2,每次迭代都声明一个新变量。除此之外,请遵循@Bob__ 的一些建议(即迭代(vec2 c))。
我正在尝试使用 GLSL 渲染 Mandelbrot 集,但我得到的只是一个完整的圆... 我检查了很多数学,但我根本找不到错误,所以我想可能是语义问题。
有谁能看出问题所在吗?
还有,谁能给我一些关于组织、结构等方面的见解?我正在尝试学习正确的编码,但很难找到 material 样式。
观察:着色器可以应用于任何图像
思路很简单(可以略过):
- checkConvergence returns true if z has not diverged (i.e., abs(z) < 4
- sumSquare returns the complex multiplication (z.r, z.i)*(z.r, z.i) - (c.r, c.i), where K.r = Re(K) and K.i = Im(K)
- iterate is the actual algorithm: it keeps squaring z until it diverges or it reaches a maximum number of iterations (tol)
- clr is parameterized function which returns a RGB array which depends on n
- finally, effect is where the magic should happen, by doing 2 things:
- It takes the coordinates of the pixel (GLSL normalizes to the interval(0, 1) and normalizes it to the Mandelbrot set size (-2
- It calls clr over iterate over the coordinates.
GLSLShader = love.graphics.newShader[[
vec2 c = vec2(1.0, 0.0);
int tol = 100;
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
bool checkConvergence(vec2 z) {
return (z[0]*z[0] + z[1]*z[1] < 4);
}
vec2 sumSquare(vec2 z) {
return vec2(z[0]*z[0] - z[1]*z[1] - c[0], 2 * z[0] * z[1] - c[1]);
}
int iterate(vec2 z) {
int n = 0;
while (checkConvergence(z) && (n < tol)) {
vec2 z = sumSquare(z);
n = n + 1;
}
return n;
}
vec4 clr(int n){
if(n == tol){return vec4(0.0,0.0,0.0,1.0);}
int r, g, b;
r = int(255*n + 47*(1-n));
g = int(180*n + 136*(1-n));
b = int(38*n + 255*(1-n));
if (r > 255) {r = 255;}
else{
if(r<0){r = 0;}
}
if (g > 255) {g = 255;}
else{
if(g<0){g = 0;}
}
if (b > 255) {b = 255;}
else{
if(b<0){b = 0;}
}
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);
return clr(iterate(z));
}
]]
更新
我尝试了@WeatherVane 的一些建议:
- 添加 C 而不是减去它;
- 将z作为(0.0,0.0)开始,将迭代的起点作为C传递;
但是都无济于事,我还是搞定了一个圈子。我也试过使用更多的迭代
我试图在不做太多改动的情况下简化编码。
我给clr函数加了一个条件,returns如果n小于0或者大于1就变绿。奇怪的是圆圈是黑色的,屏幕的其余部分是白色的,但我看不出这种白色是从哪里来的(因为 0 < n < 1 时 clr 从来没有 returns 白色)。
vec2 c = vec2(0.0, 0.0);
int tol = 1000;
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
int iterate(vec2 z) {
int n = 0;
while ( (z[0]*z[0] + z[1]*z[1] < 4) && (n < tol) ) {
vec2 z = vec2( z[0]*z[0] - z[1]*z[1] + c[0], 2 * z[0] * z[1] + c[1] );
n = n + 1;
}
return n;
}
vec4 clr(int n){
n = n / tol;
if(n == 1){return black;}
if(n > 1 || n < 0){return green;}
int r, g, b;
r = int(255*n + 47*(1-n));
g = int(180*n + 136*(1-n));
b = int(38*n + 255*(1-n));
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);
return clr(iterate(z));
}
迭代的起点应该作为向量c传递,而z从{0.0.0.0}开始。
您可以在 https://en.wikipedia.org/wiki/Mandelbrot_set
中找到a complex number c is part of the Mandelbrot set if, when starting with z = 0 and applying the iteration z = z² + c repeatedly, the absolute value of z remains bounded however large n gets.
您正在使用 vec4 作为颜色,而不是使用浮点数,在您的代码中您正在计算具有整数值的 RGB 分量。您应该转换为浮动并将每个组件规范化到 (0.0,1.0) 范围。我试图更正您的代码,但恐怕我真的不知道 lua 和 love2d,而且我无法使用 texture_coords,所以我使用了 screen_coords。我能做的最好的是:
function love.load()
GLSLShader = love.graphics.newShader[[
vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
int max_iter = 1024;
vec4 clr(int n){
if(n == max_iter){return black;}
float m = float(n)/float(max_iter);
float r = float(mod(n,256))/32;
float g = float(128 - mod(n+64,127))/255;
float b = float(127 + mod(n,64))/255;
if (r > 1.0) {r = 1.0;}
else{
if(r<0){r = 0;}
}
if (g > 1.0) {g = 1.0;}
else{
if(g<0){g = 0;}
}
if (b > 1.0) {b = 1.0;}
else{
if(b<0){b = 0;}
}
return vec4(r, g, b, 1.0);
}
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
vec2 c = vec2((screen_coords[0]-500)/200,(screen_coords[1]-300)/200);
vec2 z = vec2(0.0,0.0);
vec2 zn = vec2(0.0,0.0);
int n_iter = 0;
while ( (z[0]*z[0] + z[1]*z[1] < 4) && (n_iter < max_iter) ) {
zn[0] = z[0]*z[0] - z[1]*z[1] + c[0];
zn[1] = 2*z[0]*z[1] + c[1];
z[0] = zn[0];
z[1] = zn[1];
n_iter++;
}
return clr(n_iter);
}
]]
end
function love.draw()
love.graphics.setShader(GLSLShader)
love.graphics.rectangle('fill', 0,0,800,600)
love.graphics.setShader()
end
这给了我这个输出:
while ( ... ) {
vec2 z = ...
}
去掉 vec2,每次迭代都声明一个新变量。除此之外,请遵循@Bob__ 的一些建议(即迭代(vec2 c))。