Neslihan Bağcı Aydınoğlu

Kim?

Hacettepe İstatistik Bölümü mezunu olduğu halde, hayatını kendini mutlu eden bilgisayar programcılığına ve sevgili eşi Deniz'e adamış, su kaplumbağalarının bizzat annesi ayrıca Anathema ve Opeth severgillerden olan bu kişinin sayfasına hoşgeldiniz.

Bağlantılar

  • oyunyapimi.org
  • aydinoglu.blogspot.com
  • CizBakalim.com
  • SHADER PROGRAMLAMA DİLLERİ

    Dizinin bu dökümanında bir shader programlama diline giriş yapacağız. Başlangıç olarak elimizde bulunan shader programlarını OpenGL uygulamalarımıza nasıl bağlayacağımız üzerinde duracağız.Bu noktada şunu belirtmek gerekir ki bu dökümanı okuyan kişilerin OpenGL uygulamaları konusunda en azından temel bilgi sahibi olup, uygulama geliştiren kişiler olduklarını varsayıyorum.

    Bu açıklamalardan sonra gelelim konumuza.Shader programlama ile yapılan işlemin GPU’yu programlamak olduğundan bir önceki dökümanımızda birçok yerde belirttik. Peki bu iş nasıl yapılıyor? GPU’nun programlanması için temel olarak kendi standartları olan bir assembly dili kullanılıyor. Fakat yüksek seviyeli diller de mevcut. GPU lar üzerinde programlama yaparken kullanılabileceğiniz seçenekler:

    • OpenGL - OpenGL Shading Language (GLSL)
    • DirectX – High Level Shader Language (HLSL)
    • Nvidia - C for Graphics (Cg)
    olarak sıralanabilir.

    Yüksek seviyeli shader dillerinden GLSL'i örnek olarak incelemek konuyu daha iyi anlamamıza yardımcı olacaktır.Fakat bu incelemeye geçmeden önce shader programlama yapısını şematik olarak aşağıdaki şekilde özetleyebiliriz.

    GLSL

    Giriş olarak yazmış olduğunuz veya örnek olarak bulduğunuz bir shader programının (pixel ve/veya vertex) OpenGL uygulaması ile kullanılabilmesi için neler yapmamız gerektiği üzerinde duralım. Shader programlama esnasında OpenGL’in ARB (extensions) eklentileri kullanılacaktır.Bu eklentilerin kolaylıkla kullanılmasını sağlamak için size GLEW (The OpenGL Extension Wrangler Library) kütüphanesinden yararlanmanızı tavsiye edebilirim.

    GLEW kütüphanesi ile kullanmak istediğiniz eklentilerin sistemde bulunup bulunmadığı kontrolünü yapıp var ise uygulamanızda kullanabilirsiniz. OpenGL çizim alanı tanımınızı yaptıktan sonra glewInit() fonksiyonu ile glew tanımlama işlemini yapabilirsiniz. Fonksiyon geriye GLEW_OK değeri döndürür ise tanınmlama başarılı olmuştur ve eklentileri kullanbilirsiniz anlamına gelir. Aşağıdaki örnek uygulama bu tanımlamayı yapmaktadır.

    #include <GL/glew.h>
    ....
    GLenum sonuc = glewInit();
    if (GLEW_OK != sonuc) {
        printf("GLEW tanımlaması esnasında hata olustu: %s\n",
    glewGetErrorString(err));
    }
    else {
        printf("GLEW versiyon: %s\n", glewGetString(GLEW_VERSION));
    }
    ....

    Shaderların yüklenmesi ve kullanılması için gerekli olan OpenGL eklentileri şunlardır:
    • GL_ARB_vertex_shader
    • GL_ARB_fragment_shader
    • GL_ARB_shader_object
    Bu eklentileri kullanmadan önce sistemde tanımlı olup olmadıklarını glew kütüphanesinin glewGetExtension() fonksiyonu ile test edebiliriz.

    if (GL_TRUE != glewGetExtension((const GLubyte*) "GL_ARB_vertex_shader")){
        printf("GL_ARB_vertex_shader extension is not available!"; }
    }

    Testimizin ardından bir OpenGL Shader Objesini glCreateShader ile yaratıp, bu objenin tipinin vertex veya fragment olarak belirtmeliyiz.
    GLuint vertexShader, fragmentShader;

    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    Gerekli tanımları yaptıktan sonra şimdi de sıra shader kodunun belleğe yüklenip sonrasında da derlenmesine geldi. Kodun belleğe alınmasına ilişkin birçok farklı yöntem kullanabiliriz. Aşağıdaki örnek fonksyion ile dosyanın okunup belleğe alınmasını sağlayalım.

    char *ReadFile(const char *path){
         FILE *shaderFile;
         char *str;

         if (!(shaderFile = fopen(path, "r"))){
             printf("%s, dosyası bulunamadı.\n", path);
             return NULL;
         }

         fseek(shaderFile, 0, SEEK_END);
         long length = ftell(shaderFile);
         fseek(shaderFile, 0, SEEK_SET);
         if (!(str = (char*)malloc(length * sizeof(char)))){
             printf("Bellekten bolge alma hatası olustu.\n");
             return NULL;
         }

         long r = fread(str, sizeof(char), length, shaderFile);

         //Shader dosyasının sonuna NULL eklememiz gerekiyor.
         str[r - 1] = '\0';
         fclose(shaderFile);

         return str;
    }

    Aşağıdaki örnekte vertexShaderSource ve fragmentShaderSource GLSL kodu içeren dosyaların bellekteki yerini göstermektedir.

    const char *vertexShaderSource = ReadFile("shader.vert");
    const char *fragmentShaderSource = ReadFile("shader.frag");

    int vLength = strlen(vertexShaderSource);
    int fLength = strlen(fragmentShaderSource);

    glShaderSourceARB(vertexShader, 1, &vertexShaderSource;, &vLength;);
    glShaderSourceARB(fragmentShader, 1, &fragmentShaderSource;, &fLength;);

    //Shader kodlarını derleme işlemi
    glCompileShaderARB(vertexShader);
    glCompileShaderARB(fragmentShader);

    Yükleyip derlediğimiz shader programında bir hata oluşup oluşmadığının kontrolünü yapmak istersek bunu için glGetObjectParameteriv() fonksiyonunu kullanabiliriz.

    glGetObjectParameterivARB(vertexShader, GL_COMPILE_STATUS, &compiled;);
    if (!compiled) {
        printf("Vertex Shader derleme hatasi.\n");
    }

    glGetObjectParameterivARB(fragmentShader, GL_COMPILE_STATUS,&compiled;);
    if (!compiled) {
        printf("Fragment shader derleme hatası.\n");
    }

    Kontrollerimizi yaptık.Herşey yolunda.Biraz daha sabrederseniz sonuca ulaşmak üzereyiz.Bu adımların sonrasında glCreataProgram() ile kendimize bir program objesi (Program Object) yaratıp derlediğimiz shader kodlarını bu objeye eklememiz gerekiyor. Program objesi shader objelerini glAttachShader() fonksiyonu ile ilişkilendirdiğimiz bir objedir. glCreataProgram() fonksiyonu düzgün olarak çalıştığında geriye 0 olmayan bir değer geriye döndürür.

    GLint program = 0;
    program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    Bu işlemin ardından da bir kontrol etsek iyi olur.

    GLint linked;
    glGetObjectParameterivARB(program, GL_LINK_STATUS, &linked;);
    if (!linked){
        printf("Shader baglama hatası.\n");
    }

    Tüm kontroller ve yükleme işlemleri burada sona eriyor.Bu adımların ardından artık glUseProgram() ile o anki çizim işlemleri için tanımlanan shaderların kullanımı aktif hale getirilebilir.

    glUseProgram(program);
    Bundan sonraki kısımda artık yapılan çizimler shader programlarının etkisinde kalacaktır. Son bir not uygulamanızı kapatmadan önce yaptığınız işlemlere aşağıdaki silme işlemlerini de eklemeyi unutmayın.

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteProgram(program);

    Bu satıra kadar gelebilen sabırlı okuyucuları da eğer atlama yapmadılar ise canı gönülden tebrik ederim.İyi shaderlar. :)

    Neslihan Aydınoğlu :: Eylül 2007