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.
VertexShader.glsl
FragmentShader.glsl
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
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.
- 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.
- FragmentShader.glsl bu program ise parametresini VertexShader'dan alıyo. Bu tamemen dokuyla alakalı. Renkler ve kaplamalar burada işleniyo. glColor3f alternatifi olmuş buda.
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