मैं ओपनजीएल का उपयोग कर सी ++ के साथ मंडेलब्रॉटसेट प्रोग्रामिंग कर रहा हूं, लेकिन मैंने एक समस्या में भाग लिया है: मैं अपने शेडर में जो फ्लोट भेज रहा हूं और गणना कर रहा हूं वह केवल दशमलव स्थानों की एक निश्चित मात्रा में फिट हो सकता है। इसलिए अगर मैं बहुत दूर तक ज़ूम इन करता हूँ तो यह पिक्सेलेटेड हो जाता है।

मैंने कस्टम सरणी फ़ंक्शन बनाने के बारे में सोचा लेकिन मैं वास्तव में इसका पता नहीं लगा सकता। क्या सरणियों का उपयोग करने के अलावा कोई अन्य तरीका है? और यदि नहीं तो मैं सरणियों का उपयोग करके सामान की गणना कैसे कर सकता हूं जैसे कि वे एक ही संख्या हैं? (जैसे arr[1,2] x arr[0,2] को 1.2 x 0.2 की गणना के समान ही आउटपुट देना चाहिए)

in vec4 gl_FragCoord;
 
out vec4 frag_color;
uniform float zoom;
uniform float x;
uniform float y;
#define maximum_iterations 1000

int mandelbrot_iterations()
{
    float real = ((gl_FragCoord.x / 1440.0f - 0.5f) * zoom + x) * 4.0f;
    float imag = ((gl_FragCoord.y / 1440.0f - 0.5f) * zoom + y) * 4.0f;
 
    int iterations = 0;
    float const_real = real;
    float const_imag = imag;
 
    
    while (iterations < maximum_iterations)
    {
        float tmp_real = real;
        real = (real * real - imag * imag) + const_real;
        imag = (2.0f * tmp_real * imag) + const_imag;
         
        float dist = real * real + imag * imag;
 
        if (dist > 4.0f)
            break;
 
        ++iterations;
    }
    return iterations;
}

^मैंडेलब्रॉट फंक्शन इन माई फ़्रेग्मेंटशेडर

1
Space Kek 25 नवम्बर 2020, 21:14

3 जवाब

सबसे बढ़िया उत्तर

जैसा कि अन्य ने सुझाव दिया है कि float के बजाय double का उपयोग करें जो आपको बहुत अधिक संभव ज़ूम देगा। उस के शीर्ष पर भिन्नात्मक पलायन का उपयोग करें जो बहुत कम पुनरावृत्तियों के साथ बहुत अधिक विवरण की अनुमति देगा इसलिए बेहतर विवरण के साथ बड़ी गति मेरा देखें:

मेरे मेंडलब्रॉट के लिए फ्लोट कोड है और 32 और 64 बिट फ्लोट दोनों के लिए Win32 डेमो के लिंक हैं। हालांकि double संस्करण शेडर्स उत्तर देने के लिए उपयुक्त नहीं थे, इसलिए वे यहां हैं (फ्रैक्टल के लिए, दूसरा पास रिकॉलिंग शेडर्स महत्वपूर्ण नहीं हैं लेकिन आप उन्हें डेमो के रूप में निकाल सकते हैं):

// Fragment
#version 450 core
uniform dvec2 p0=vec2(0.0,0.0);     // mouse position <-1,+1>
uniform double zoom=1.000;          // zoom [-]
uniform int  n=100;                 // iterations [-]
uniform int  sh=7;                  // fixed point accuracy [bits]
uniform int  multipass=0;           // multi pass?
uniform int  inverted=0;            // inverted/reciprocal position?
in smooth vec2 p32;
out vec4 col;

const int n0=1;                     // forced iterations after escape to improve precision

vec3 spectral_color(float l)        // RGB <0,1> <- lambda l <400,700> [nm]
    {
    float t;  vec3 c=vec3(0.0,0.0,0.0);
         if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); c.r=    +(0.33*t)-(0.20*t*t); }
    else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); c.r=0.14         -(0.13*t*t); }
    else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); c.r=    +(1.98*t)-(     t*t); }
    else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); c.r=0.98+(0.06*t)-(0.40*t*t); }
    else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); c.r=0.65-(0.84*t)+(0.20*t*t); }
         if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); c.g=             +(0.80*t*t); }
    else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); c.g=0.8 +(0.76*t)-(0.80*t*t); }
    else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); c.g=0.84-(0.84*t)           ; }
         if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); c.b=    +(2.20*t)-(1.50*t*t); }
    else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); c.b=0.7 -(     t)+(0.30*t*t); }
    return c;
    }

void main()
    {
    int i,j,N;
    dvec2 pp,p;
    double x,y,q,xx,yy,mu,cx,cy;
    p=dvec2(p32);

    pp=(p/zoom)-p0;         // y (-1.0, 1.0)
    pp.x-=0.5;              // x (-1.5, 0.5)
    if (inverted!=0)
        {
        cx=pp.x/((pp.x*pp.x)+(pp.y*pp.y));  // inverted
        cy=pp.y/((pp.x*pp.x)+(pp.y*pp.y));
        }
    else{
        cx=pp.x;                // normal
        cy=pp.y;
        }
    for (x=0.0,y=0.0,xx=0.0,yy=0.0,i=0;(i<n-n0)&&(xx+yy<4.0);i++)
        {
        q=xx-yy+cx;
        y=(2.0*x*y)+cy;
        x=q;
        xx=x*x;
        yy=y*y;     
        }
    for (j=0;j<n0;j++,i++)  // 2 more iterations to diminish fraction escape error
        {
        q=xx-yy+cx;
        y=(2.0*x*y)+cy;
        x=q;
        xx=x*x;
        yy=y*y;
        }
    mu=double(i)-double(log2(log(float(sqrt(xx+yy)))));
    mu*=double(1<<sh); i=int(mu);
    N=n<<sh;
    if (i>N) i=N;
    if (i<0) i=0;

    if (multipass!=0)
        {
        // i
        float r,g,b;
        r= i     &255; r/=255.0;
        g=(i>> 8)&255; g/=255.0;
        b=(i>>16)&255; b/=255.0;
        col=vec4(r,g,b,255);
        }
    else{
        // RGB
        float q=float(i)/float(N);
        q=pow(q,0.2);
        col=vec4(spectral_color(400.0+(300.0*q)),1.0);
        }
    }

और:

// Vertex
#version 450 core
layout(location=0) in vec2 pos;         // glVertex2f <-1,+1>
out smooth vec2 p32;                    // texture end point <0,1>
void main()
    {
    p32=pos;
    gl_Position=vec4(pos,0.0,1.0);
    }

यह zoom = 1e+14 तक जा सकता है जहां पिक्सेलेशन होने लगते हैं:

pixelation

GPU पर मनमाने ढंग से सटीक फ्लोट का उपयोग करना बहुत धीमा और समस्याग्रस्त होगा (जैसा कि पहले ही सुझाव दिया गया है)। हालांकि फ्लोट या डबल की सटीकता बढ़ाने के लिए सरल कामकाज हैं।

उदाहरण के लिए आप अपना मान अधिक doubles के योग के रूप में रख सकते हैं...

की बजाय

double x;

आप उपयोग कर सकते हैं:

double x0,x1,x2,....,xn;

जहां x = x0+x1+x2+...+xn जहां x0 छोटे मान रखता है, x1 बड़ा, ... xn सबसे बड़ा। आपको केवल बुनियादी +,-,* संचालन की आवश्यकता है, इसलिए

  1. x += y

    x0+=y0; x1+=y1; ... xn+=yn;
    
  2. x -= y

    x0-=y0; x1-=y1; ... xn-=yn;
    
  3. x *= y

    x0*=(y0+y1+...yn);
    x1*=(y0+y1+...yn);
    ...
    xn*=(y0+y1+...yn);
    

और प्रत्येक ऑपरेशन के बाद आप प्रत्येक चर की श्रेणियों को सामान्य करते हैं:

   if (fabs(x0)>1e-10){ x1+=x0; x0=0; }
   if (fabs(x1)>1e-9) { x2+=x1; x1=0; }
   ...
   if (fabs(x(n-1))>1e+9){ xn+=x(n-1); x(n-1)=0; }

आपको श्रेणियों को चुनने की आवश्यकता है ताकि आप उन संख्याओं पर चर बर्बाद न करें जिनका उपयोग नहीं किया जाएगा ...

इस सटीकता के साथ अभी भी सीमित है लेकिन सटीकता का नुकसान बहुत छोटा है ...

हालाँकि अभी भी एक सीमा है (जिसे आसानी से पार नहीं किया जा सकता है)। अभी आप फ्रैगमेंट कोऑर्डिनेट, zoom और पैन x,y से स्थिति की गणना कर रहे हैं, जो फ्लोट तक ही सीमित रहेगा क्योंकि हमारे पास अभी भी 64 बिट डबल इंटरपोलेटर नहीं हैं। यदि आप इस बाधा को तोड़ना चाहते हैं, तो आपको सीपीयू की तरफ या वर्टेक्स पर ज़ूम की गई स्थिति की गणना उसी तरह से करनी होगी जैसे कि बाकी गणना (अधिक चर का योग लेकिन इस बार फ्लोट) और परिणाम को टुकड़े में बदलना होगा

2
Spektre 26 नवम्बर 2020, 12:22

सबसे पहले, यदि आप float के बजाय double का उपयोग करते हैं, तो आपको दोगुनी सटीकता मिलती है। लेकिन यह GPU कोड है, इसलिए double धीमा हो सकता है। (एक CPU पर, double और float लगभग समान गति के होते हैं)

आप जिस चीज़ की तलाश कर रहे हैं उसे मनमाने ढंग से सटीक अंकगणित कहा जाता है। यह बहुत धीमा है। आप पुस्तकालय ढूंढ सकते हैं जो ऐसा करते हैं, जैसे जीएमपी। लेकिन वे सीपीयू के लिए हैं। जाहिर है, GPU के लिए भी कुछ हैं - मैं उनसे परिचित नहीं हूं। याद रखें, यह धीमा है!

आप GPU के लिए अपना स्वयं का मनमाना सटीक अंकगणित कर सकते हैं, हाँ। आपको प्रति ऐरे आइटम में 1 अंक स्टोर करने की आवश्यकता नहीं है; आपको जितना संभव हो सके स्टोर करना चाहिए (आमतौर पर 32 बिट)।

आप कैसे जोड़ते हैं? आप बस प्रत्येक तत्व को एक साथ जोड़ते हैं, और अगले तत्व में ले जाते हैं यदि यह अतिप्रवाह होता है। घटाव के लिए भी, आप अगले तत्व से उधार लेते हैं यदि यह कम हो जाता है।

आप कैसे गुणा करते हैं? जैसे ग्रेड स्कूल में - आप प्रत्येक तत्व को एक दूसरे तत्व से गुणा करते हैं, फिर आप उन्हें नीचे एक साथ जोड़ते हैं।

आप कैसे विभाजित करते हैं? सौभाग्य से, मैंडलब्रॉट सेट गणना में, कोई विभाजन नहीं है। तो परेशान मत होइए।


मैंडलब्रॉट सेट के लिए विशेष रूप से, एक तेज़ अनुमानित एल्गोरिथम है जिसे "परटर्बेशन थ्योरी एल्गोरिथम" के रूप में जाना जाता है।

परटर्बेशन थ्योरी एल्गोरिथम आस-पास के बिंदुओं के एक समूह के लिए मैंडेलब्रॉट सेट की लगभग गणना करने के लिए एक दृष्टिकोण है। सबसे पहले, आप संदर्भ बिंदु नामक एक बिंदु चुनते हैं, और आप धीमी मनमानी सटीक गणित के साथ सामान्य एल्गोरिदम का उपयोग करके इसकी गणना करते हैं। संदर्भ बिंदु एक काला पिक्सेल होना चाहिए, या कम से कम इसे स्क्रीन पर किसी भी अन्य पिक्सेल की तुलना में अधिक पुनरावृत्तियों के लिए जाना चाहिए।

सूत्र यहां है - एक बार जब आपके पास एक संदर्भ बिंदु हो, तो प्रत्येक पिक्सेल के लिए, आपको केवल आवश्यकता होगी उस पिक्सेल और संदर्भ बिंदु के बीच अंतर की गणना करने के लिए - जो एक छोटी संख्या है, इसलिए आप सटीकता नहीं खोते हैं! Z <- Z^2 + C के बजाय आप Zref+Zdiff <- (Zref+Zdiff)^2 + (Cref+Cdiff) की गणना करते हैं - यदि आप इस समीकरण को गणितीय रूप से सरल करते हैं, तो आप पाएंगे कि बहुत सारे Zref और Cref रद्द हो गए हैं, और आप सटीकता खोए बिना Zdiff की गणना कर सकते हैं। (इसे सामान्य तरीके से गणना न करें और फिर Zref घटाएं, क्योंकि आपको इस तरह कोई अतिरिक्त सटीकता नहीं मिलेगी)

2
Rabbid76 9 पद 2020, 23:52

आप हमेशा double के साथ जा सकते हैं, लेकिन चूंकि यह shader है और इसे GPU पर निष्पादित किया जाएगा, यह एक प्रदर्शन दंड के साथ आएगा। एक तरकीब जिसका आप उपयोग कर सकते हैं, वह है इतने कम मूल्यों पर नहीं जाना, जहां सटीकता एक समस्या बन जाती है। इसके बजाय जब आप ज़ूम इन करते हैं तो आप किसी बिंदु पर अपने परिणामों का बैक अप ले सकते हैं, मूल रूप से मानों को स्थिर सीमा में रखते हुए जहां परिशुद्धता कोई समस्या नहीं है। आईआरसी, केरल स्पेस प्रोग्राम डेवलपर्स के पास वास्तव में इस तकनीक के बारे में एक ब्लॉग पोस्ट है, ताकि आप इसे देख सकें।

KSP से कोई नहीं मिल रहा है, इसलिए यहां पर कुछ इसी तरह का लिंक दिया गया है सेबस्टियन लैग द्वारा यूट्यूब। प्रासंगिक भाग लगभग 10 मिनट का है।

0
Michał Kaczorowski 25 नवम्बर 2020, 21:23