[Java] OpenGL içinde Shader Kullanımı GLSL

merakettim

Homo Sapiens Sapiens
Özel üye
Merhaba arkadaşlar;
Uzun süredir karşılaştığım bir sorunu sizinle paylaşmak istiyorum. Bildiğiniz üzere (bildiğinizi var sayıyorum) Android gibi taşınabilir işletim sistemlerinde OpenGL desteği kısmen yok. Bunun yerine OpenGL ES kullanılıyor. Bunun da bi çok farkı var. Neredeyse alıştığımız eski metotların çoğunu kullanamıyoruz. Yada bazı fonksiyonlar artık gerekli görülmediğinden güncel sürümlere eklememişler. Örneğin; artık glBegin ve glEnd fonksiyonlarının arasına glVertex3f fonksiyonu ile kordinat girerek köşe nokta tanımlaması yapılamıyor çünkü bu fonksiyonlar yok. Peki biz bu metotların yerine ne kullanacağız. Elbette daha estetik olan glDrawElements kullanacağız. Bu konuda C dili İle OpenGL Programlama başlığı altında biraz bilgi vermiştik. Avantajlarından ve dezavantajlarından bahsettik. Grafik kartı ramini nasıl kullandığımızdan bahsettik. Bu konuyu Java yapmamın sebebi Android cihazlara da ufak değişikliklerle entegre edilebilmesini amaçlamak. Zira türkçe tek bir kaynak dahi bulamadım. Umarım insanların işine yarar.

Bu yazıyı bilimsel veya kesin doğru bi kaynak olarak göstermeyin. Ben deneyimlerimi anlatacam. Yanlış bi durumda konunun altında doğrusunu ekleyebilirsiniz.

Denemelerde;
*glBegin glEnd glVertex3f glColor3f vb.. fonksiyonlarının kullanılmadığını, bunlar yerine glDrawArrays ve glDrawElements kullanıldığını gördük.
*glGenLists glNewList glCallList gibi ön bellek metodlarının kullanılmadığını gördük.
*Ayrıca glDrawElements gibi fonksiyonlar kullanırken renk bilgisi gönderdiğimiz glColorPointer kullanılmadığını zaten kullanılsa da pek kullanışlı olmadığını farkettik.
*Android'de sürekli Buffer'lar kullanak köşe nokta dizinleri oluşturmak zorunda kaldığımızdan dolayı, animasyon matrislerini hep (yani ben yanlış yapıyormuşum :) ) işlemciye yaptırıp, tekrar Buffer'lara yazıyorduk. Ne kadar fazla bi iş. Ve sürekli yapılması lazım. Halbuki önceden veri dizisini işler glVertex3f'e yazardık. Çok kolaydı.
*Display listeleri kullanırken de statik noktalar oluşturulabileceğini ve sonradan müdahale edemeyeceğimizi anlatmıştık yine aynı konuda.

"Bu adamlar OpenGL'yi bitirmişler batmış gitmiş vah vah" derken Shader sistemini incelemek durumunda kaldık ve "adamlar mantıklı yapmışlar abi" dedik.


Anladığım kadarıyla Shader sistemi grafik işlemcisini programlamak anlamına geliyo. Biz vertex (köşe nokta) dizilerimizi grafik işlemcisine gönderiyoruz ve animasyon için gerekli işlemleri glsl programcıklarına yaptırıyoruz. Yani çok ağır işler (örneğin gölge veya animasyon matris dönüşümleri) grafik kartı işlemcisinde kolayca yapılıyo. Bu da ana işlemcimizi yormadan hızlıca işimizi halletmemizi sağlıyo. Şimdi bu anlattıklarımı nasıl yapacağımıza bakalım.

Örnek kodu paylaşmadan önce shader'lar c diline çok benzerler ve zaten bu dilden devşirilmişlerdir. İçlerinde döngüler ve koşullu dallanmalar yapabilirsiniz. burada iki adet shader paylaşacam.
  1. VertexShader.glsl bu program delen dizinin içindeki köşe noktalarına dilediğimiz gibi erişmemizi ve oynama yapabilmemizi sağlıyo. Tıpkı glVertex3f'e parametre girmeden önce oynadığımız gibi.
  2. FragmentShader.glsl bu program ise parametresini VertexShader'dan alıyo. Bu tamemen dokuyla alakalı. Renkler ve kaplamalar burada işleniyo. glColor3f alternatifi olmuş buda.
GeneltestOgl.java
Java:
package com.shaderornegi;

import com.sun.opengl.util.Animator;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;




public class GeneltestOgl implements GLEventListener {

    public static void main(String[] args) {
        Frame frame = new Frame("Simple JOGL Application");
        GLCanvas canvas = new GLCanvas();

        canvas.addGLEventListener(new GeneltestOgl());
        frame.add(canvas);
        frame.setSize(640, 480);
        final Animator animator = new Animator(canvas);
        frame.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                // Run this on another thread than the AWT event queue to
                // make sure the call to Animator.stop() completes before
                // exiting
                new Thread(new Runnable() {

                    public void run() {
                        animator.stop();
                        System.exit(0);
                    }
                }).start();
            }
        });
        // Center frame
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        animator.start();
    }
   
  //buraya kadar olan kısmı anlatmaya gerek yok çünkü bunlar java içinde opengl kullanmak için gerekli işlemler

    public void init(GLAutoDrawable drawable) {
        // Use debug pipeline
        // drawable.setGL(new DebugGL(drawable.getGL()));

        GL gl = drawable.getGL();
        System.err.println("INIT GL IS: " + gl.getClass().getName());

        // Enable VSync
        gl.setSwapInterval(1);

        // Setup the drawing area and shading mode
       gl.glDisable(GL.GL_DITHER);

        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_FASTEST);


        gl.glShadeModel(GL.GL_SMOOTH);

        gl.glEnable(GL.GL_DEPTH_TEST);
       
//buraya kadarki kısım ise genel opengl işlemleri asıl konu şu aşağıdaki iki fonksiyonun içinde gerçekleşiyo
//biri sendDataToOpenGL (bu ismi biz verdik genel bi fonksiyon veya metot değil) ve
//CreateVertexProgram (bunu da yabancı bi kaynaktan aşırdım ama içine girecez merak etmeyin:) )
        sendDataToOpenGL(drawable);
        CreateVertexProgram(drawable);
       
       
    }
   
//kullanılan dizileri Bufferlara çevirmek gerektiğini söylemiştim. Aşağıdaki üç buffer oluşturma fonksiyonu var.
//Bu kullanım gayet rahat oluyo

       private static FloatBuffer makeFloatBuffer(float[] arr) {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer fb = bb.asFloatBuffer();
        fb.put(arr);
        fb.position(0);
        return fb;
    }
     
       private static ShortBuffer makeShortBuffer(short[] arr) {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 2);
        bb.order(ByteOrder.nativeOrder());
        ShortBuffer fb = bb.asShortBuffer();
        fb.put(arr);
        fb.position(0);
        return fb;
    }
     
        private static IntBuffer makeIntBuffer(int[] arr) {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 2);
        bb.order(ByteOrder.nativeOrder());
        IntBuffer fb = bb.asIntBuffer();
        fb.put(arr);
        fb.position(0);
        return fb;
    }


    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();

        gl.glEnable(gl.GL_SCISSOR_TEST);
        gl.glViewport(0, 0, (int) width , (int) height);
        gl.glScissor(0, 0, (int) width , (int) height);

        gl.glClearColor(0.5f, 0.5f, 0.9f, 0.0f); //set background to black
        gl.glClearDepth(1.0f);


        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);

        gl.glMatrixMode(gl.GL_PROJECTION); // Change PROJECTION matrix
        gl.glLoadIdentity();

        float aspect_ratio = (float) width / height;
        glu.gluPerspective( 67, aspect_ratio, 1, 1500);
   

        gl.glEnable(gl.GL_DEPTH_TEST);
        gl.glMatrixMode(gl.GL_MODELVIEW); // Change MODELVIEW matrix
        gl.glLoadIdentity();
    }

   float don=0;
    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();

         gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);

        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glLoadIdentity();



        glu.gluLookAt(
                0  , 0, -3,//kamera pozisyonu
                0  , 0, 0,//kamera bakis pozisyonu
                0, 1, 0);//yon

       
        gl.glRotatef(don++, 1, 1, 0);
        if(don>360)
            don=0;
     
  //burada birazdan yapacağımız tüm işlemler yürütülecek.
  //farkettiniz mi bilmiyorum ama basılacak köşe noktaları ile alakalı tek bir parametre dahi girilmemiş.
//çünkü sendDataToOpenGL fonksiyonu içinde herşeyi önbelleğe zaten gönderdik
gl.glDrawElements(GL.GL_TRIANGLES, 6, GL.GL_UNSIGNED_SHORT, 0);
       
       

        // Flush all drawing operations to the graphics card
        gl.glFlush();
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
       
    }
   

//gelelim konumuza

    void sendDataToOpenGL(GLAutoDrawable drawable){
        GL gl = drawable.getGL();
    int[] vertexBufferId = new int[1];
//verts dizimizde her bir noktanın 5 kordinattan oluştuğunu görüyorsunuz.
//aslında bu kordinat 2 boyutlu. yani dizinin ilk iki elemanı kordinat belirtiyor(x ve y), sonraki 3 elemanı ise renk bilgisi belirtiyor (r,g,b)
//5 adet noktamız var ve renkli ikitane üçgen çizmeye çalışacaz
        float [] verts={
        0.0f,   0.0f,
        0f,      1f,      0f,
        1.0f,   1.0f,
        0f,      0f,      0f,
        -1.0f,  1.0f,
        1f,      0f,      0f,
        -1.0f,  -1.0f,
        0f,      0f,      0f,
        1.0f,   -1.0f,
        1f,      0f,      1f
        };
       
         //köşe noktalar için Buffer Id oluşturuyoruz
        gl.glGenBuffers(1, vertexBufferId, 0);
        //bunu tahminimce ram'a bağlıyoruz
        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vertexBufferId[0]);
       //köşe noktaları FloatBuffer tipine dönüştürüyoruz çünkü direk kullanmamıza izin vermiyo
        FloatBuffer toBuffer=makeFloatBuffer(verts);
       //Buffer verilerini belleğe yazıyoruz
        gl.glBufferData(GL.GL_ARRAY_BUFFER,toBuffer.capacity()*Float.BYTES, toBuffer,GL.GL_STATIC_DRAW);
        //Bu anladığım kadarıyla ekran kartı için yazdığımız programın 0. değişkeni olarak aşağıda gireceğimiz diziyi geçirecek
       //parametre göndermek için bi metot gibi
      //aktif ediyo galiba
        gl.glEnableVertexAttribArray(0);
       //ram'a aldığımız dizinin hangi kısmının ne olarak kullanacağı.2 dizin uzunluğu 0. parametre
       //biz onu köşe nokta olarak kullanacağız
        gl.glVertexAttribPointer(0, 2, GL.GL_FLOAT,false, Float.BYTES*5, 0);
      //bu da sonraki parametre göndereceğımızi ifade ediyoruz. 0dan sonra 1 yani. 1. parametreyi aktif et.
        gl.glEnableVertexAttribArray(1);
      //1. parametreyle ne göndereceğimizi belirtiyoruz. 3 parametreyi biz renk olarak kullanacağız.
        gl.glVertexAttribPointer(1, 3, GL.GL_FLOAT,false, Float.BYTES*5, Float.BYTES*2);
       

       //indis olayını anlatmıştım zaten. 0,1,2 noktalar bi üçgen, 0,3,4 noktalar bi üçgen
        short indices[]={0,1,2,0,3,4};
        int[] indexBufferId = new int[1];
       //indis için ram'da buffer oluşturduk
        gl.glGenBuffers(1, indexBufferId, 0);
       //bağladık, vertex gibi buda
        gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexBufferId[0]);
       //short türündeki indisimizi ShortBuffer'a dönüştürdük
        ShortBuffer toShortBuffer=makeShortBuffer(indices);
       //indis datasını göndermiş olduk
        gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER,toShortBuffer.capacity()*Short.SIZE, toShortBuffer,GL.GL_STATIC_DRAW);

//dikkat edilmesi gereken şey şu ki, köşe nokta tanımlarken GL_ARRAY_BUFFER,
//köşe nokta indisi tanımlarken GL_ELEMENT_ARRAY_BUFFER kullanıyoruz
//eğer glDrawElements yerine glDrawArrays kullansaydık, indis kullanmamıza gerek kalmazdı.
    }
   

    //Yazdığımız ekran kartı programını nasıl tanıtacaz
    int VertexProgram;
    public void CreateVertexProgram(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        StringBuilder vertexCode = new StringBuilder();
        StringBuilder fragmentCode = new StringBuilder();


//aslında VertexShader.glsl dosyasının içindekileri biz string değişkenine alıyoruz. dosya içine değil de direk stringe de yazabilirsiniz
//ama çok karışık oluyo ben denedim tavsiye etmem
//hepsini vertexCode isimli stringe aldık
        try {
            URL vertexShaderURL =new File("shaders/VertexShader.glsl").toURI().toURL();
            BufferedReader in = new BufferedReader(new InputStreamReader(vertexShaderURL.openStream()));
            String str;
            while ((str = in.readLine()) != null) {
                vertexCode.append(str + "\r\n");
            }
        } catch (Exception e) {
            System.err.println("Loading of VertexShader failed: '" + e.toString() + "'");
        }
       

//aynı işi FragmentShader.glsl için de yaptık
//artık hepsi fragmentCode isimli stringde
        try {
            URL fragmentShaderURL =new File("shaders/FragmentShader.glsl").toURI().toURL();
            BufferedReader in = new BufferedReader(new InputStreamReader(fragmentShaderURL.openStream()));
            String str;
            while ((str = in.readLine()) != null) {
                fragmentCode.append(str + "\r\n");
            }
        } catch (Exception e) {
            System.err.println("Loading of FragmentShader failed: '" + e.toString() + "'");
        }

        System.err.println("Creating Hardware Program");
     

//gelelim grafik kartı işlemcisi programımızı derleyip ekran kartına göndermeye

//Bitane shader objesi oluşturduk ve bu objenin kimliğine vertexShader dedik
        int vertexShader = gl.glCreateShaderObjectARB(gl.GL_VERTEX_SHADER);
//sonra vertexShader'ın programının kaynak kodunu vertexCode stringinden girdik
        gl.glShaderSourceARB(vertexShader, 1, new String[]{vertexCode.toString()}, null);
//en son derliyoruz
        gl.glCompileShaderARB(vertexShader);


//aynı işleri FragmentShader için de yaptık
        int fragmentShader = gl.glCreateShaderObjectARB(gl.GL_FRAGMENT_SHADER);
        gl.glShaderSourceARB(fragmentShader, 1, new String[]{fragmentCode.toString()}, null);
        gl.glCompileShaderARB(fragmentShader);
       

//ekran kartı için bi program oluşturduk ve id sini VertexProgram'a attık.
        VertexProgram = gl.glCreateProgramObjectARB();
//programın içine daha önce derlediğimiz certexShader ve fragmentShader programcıklarını iliştirdik.
        gl.glAttachObjectARB(VertexProgram, vertexShader);
        gl.glAttachObjectARB(VertexProgram, fragmentShader);


   
        System.err.println("Validating Shader:");

//ve programımız kullanılmaya hazır. :)
        gl.glLinkProgram(VertexProgram);
        gl.glUseProgram(VertexProgram);

       
    }
   
}

VertexShader.glsl
C:
#version 130//versiyon numarası 130 olmalı eski versiyonlar in ve out desteklemiyo
//yeni versiyonları da bazı ekran kartları desteklemiyo. Ortasını bulun :)

//hani demiştik ya her nokta 5 diziden oluşuyo diye
//ilk 2 parametresi vec2 yani float[2] gibi bi dizi ve position (x ve y)
in vec2 position;
//diğer 3 parametre ise vec3 yani float [3] gibi bi dizi ve renk tutuyo (r,g,b)
in vec3 theColor;

//renk bilgisini burda işlemeyeceğız
//fakat FragmentShader'a parametreyi buradan yollamamız gerek
//theColor dan geleni colour'dan FragmentShader'a göndermek için lazım
out vec4 colour;
//bunu "acaba farklı parametreler gidiyo mu" diye denemek için yazdım
//gidiyomuş :D
out float test;


void main() {
//gl_Position ön tanımlı bir vec4 kordinar. Ekran kartındaki noktaların çıkış noktası.
//gl_ModelViewProjectionMatrix  ile gelen pozisyonu çapıyorz ki projeksiyon matrisi ölçekleme yapsın
//yoksa yamuk yumuk görünür, istersen dene :)
//vec4(position,0,1) şeklinde de yazabilirdim. Amacım dizi elemanlarına erişmeyi denemekti
    gl_Position =gl_ModelViewProjectionMatrix * vec4(position.x,position.y, 0, 1);
//aynı şekilde x'i 1 artırdım ki erişebildiğimi deneyebileyim
        gl_Position.x+=1;

//Fragment shader için parametrelerimizi göndermek için ikiside
        colour=vec4(theColor,0);
        test=0.5;



//bu programda pek bişey yapmadık. ufak tefek değişiklerle etki edip edemediğimizi test ettik
//artık gerisi size kalmış :)
}

FragmentShader.glsl
C:
#version 130

//VertexShader.glsl 'de out olarak yazdığımız değişken isimleri burada in olarak yazıyor
//yani bu iki değişkeni FragmentShader'a aktarmış olduk
in vec4 colour;
in float test;
void main()
{
//gl_Position gibi gl_FragColor ise yüzeyin her bir piksel rengini belirtiyo. Bu da ön tanımlı.
    gl_FragColor = colour;
//yeşilde değişiklik testi yapalım dedik ve oldu :)
gl_FragColor.g+=test;
}

Tüm proje Windows 10 üzerinde, Netbeans ile derlendi.
Gerekli gördüğümüz açıklamaları kodların üstlerine açıklama satırları olarak ekledik. Umarım işinizi görür. Saygılar sevgiler :)
 
Geri
Top