OpenGL Bagian 2: Pengantar Shader

Berikutnya kita akan mencoba membuat gambar sederhana (tiga titik di layar) dengan menggunakan shader.  Penggunaan shader semakin penting di masa depan,  jadi lebih baik memulai langsung dengan shader walaupun lebih kompleks. Jangan kaget jika code yang diperlukan cukup panjang. OpenGL sifatnya dekat dengan hardware, powerful, cepat  tetapi sayangnya jadi kompleks.

Proses untuk menampilkan gambar di layar melibatkan berbagai tahapan yang ada di rendering pipeline. Misalnya untuk menentukan suatu objek berada di posisi mana dan warnya apa (untuk memberikan efek 3D warna dapat berubah tergantung posisi dan posisi cahaya). Sejalan dengan semakin majunya GPU, pipeline ini dapat diprogram. Mulai dari versi 3.1 (core profile), shader wajib digunakan.

Program shader pada dasarnya adalah code yang dijalankan langsung oleh GPU pada rendering pipeline.  Dengan shader,  programmer memiliki fleksibilitas lebih tinggi dalam memanfaatkan GPU. Minimal ada dua program shader yang harus digunakan, pertama vertex shader dan fragment shader. Vertex shader bisa sederhana, sekeder mengcopy data (seperti pada contoh di posting ini) atau yang lebih kompleks seperti mengatur posisi vertex.   Fragment shader  mengatur warna pada layar,  termasuk memberikan tekstur dan efek kedalaman.

Sekarang kita akan mencoba membuat program sederhana yang menggunakan shader. Sebagian besar code saya ambil dari:  http://schabby.de/opengl-shader-example/

Buat project baru, untuk menambahkan LWJGL gunakan cara yang sama dengan tutorial sebelumnya. Selain lwljgl.jar, tambahkan juga lwjgl-util.jar (via project properties –> java build path –> Libraries –> add External JAR). Kemudian buat class baru (ShaderProgram) yang berfungsi untuk meload dan mengcompile program shader:

import java.io.BufferedReader;
import java.io.FileReader;
 
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import static org.lwjgl.opengl.GL20.*;

// class ini digunakan untuk meload dan mengcompile shader 


public class ShaderProgram {
		// OpenGL handle yang menunjuk ke executable shader program
		private int programId;
	 
		public void init(String vertexShaderFilename, String fragmentShaderFilename)
		{
			// buat shader program. jika berhasil  OK, buat vertex dan fragment shaders
			programId = glCreateProgram();
	 
			// load and compile shaders (vertex dan fragment)
			int vertShader = loadAndCompileShader(vertexShaderFilename, GL_VERTEX_SHADER);
			int fragShader = loadAndCompileShader(fragmentShaderFilename, GL_FRAGMENT_SHADER);
	 
			// attach shaders yg sudah dikompilasi ke program
			glAttachShader(programId, vertShader);
			glAttachShader(programId, fragShader);
	 
			// link the program
			glLinkProgram(programId);
	 
			// validate linking
			if (glGetProgrami(programId, GL_LINK_STATUS) == GL11.GL_FALSE) 
			{
				throw new RuntimeException("could not link shader. Reason: " + glGetProgramInfoLog(programId, 1000));
			}
	 
			// perform general validation that the program is usable
			glValidateProgram(programId);
	 
			if (glGetProgrami(programId, GL_VALIDATE_STATUS) == GL11.GL_FALSE)
			{
				throw new RuntimeException("could not validate shader. Reason: " + glGetProgramInfoLog(programId, 1000));            
			}
		}
	 
	   /**
	    * With the exception of syntax, setting up vertex and fragment shaders
	    * is the same.
	    * @param the name and path to the vertex shader
	    */
		private int loadAndCompileShader(String filename, int shaderType)
		{
			//vertShader will be non zero if succefully created
			int handle = glCreateShader(shaderType);
	 
			if( handle == 0 )
			{
				throw new RuntimeException("could not created shader of type "+shaderType+" for file "+filename+". "+ glGetProgramInfoLog(programId, 1000));
			}
	 
			// load code from file into String
			String code = loadFile(filename);
	 
			// upload code to OpenGL and associate code with shader
			glShaderSource(handle, code);
	 
			// compile source code into binary
			glCompileShader(handle);
	 
			// acquire compilation status
			int shaderStatus = glGetShaderi(handle, GL20.GL_COMPILE_STATUS);
	 
			// check whether compilation was successful
			if( shaderStatus == GL11.GL_FALSE)
			{
				throw new IllegalStateException("compilation error for shader ["+filename+"]. Reason: " + glGetShaderInfoLog(handle, 1000));
			}
	 
			return handle;
		}
	 
		/**
		 * Load a text file and return it as a String.
		 */
		private String loadFile(String filename)
		{
			StringBuilder vertexCode = new StringBuilder();
			String line = null ;
			try
			{
			    BufferedReader reader = new BufferedReader(new FileReader(filename));
			    while( (line = reader.readLine()) !=null )
			    {
			    	vertexCode.append(line);
			    	vertexCode.append('\n');
			    }
			    reader.close();
			}
			catch(Exception e)
			{
				throw new IllegalArgumentException("unable to load shader from file ["+filename+"]", e);
			}
	 
			return vertexCode.toString();
		}
	 
	 
		public int getProgramId() {
			return programId;
		}    
}

Selanjutnya kita akan buat file berisi code untuk vertex shader. Buat fie teks, beri nama simple.vertex dengan code sebagai berikut. Perhatikan bahwa code tidak menggunakan Bahasa Java. Bahasa yang digunakan disebut OpenGL Shading Language (GLSL) berbasis C. Code ini akan diload, dicompile dan dilink oleh class ShaderProgram diatas.

/* Very simple vertex shader that applies the model view
 * and projection matrix to the given vertex and overrides
 * the color with a constant for all vertices. 
 */
#version 400 core
 
layout(location = 0) in vec4 vPosition;

void main()
{
	gl_Position = vPosition;
}

Berikutnya adalah code untuk fragment shader, buat file teks, beri nama simple.fragment dengan isi sebagai berikut:

/* Very simple fragment shader. It basically passes the
 * (interpolated) vertex color on to the individual pixels.
 */ 

#version 400

out vec4 fColor;

void main() {
    fColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Sekarang kita buat program utama. Program ini terdiri atas bagian inisialisasi, loading-compiling-linking program shader menggunakan class ShaderProgram yang ditentukan sebelumnya. Bagian yang mungkin agak sulit dimengerti adalah penggunaan Vertex Array Object (VAO) dan Vertex Buffer Object (VBO). Saat ini GPU sudah memiliki memori sendiri yang terpisah dengan memori yang digunakan CPU. VAO dan VBO adalah mekanisme untuk memanfaatkan memori tersebut. Data disimpan di VBO sedangkan VAO mengelola VBO. VAO berisi informasi dimana data disimpan dan bagaimana data disimpan (bentuk layout). Detilnya dapat dilihat pada komentar di code.

Jalankan class ini maka akan muncul layar biru dengan tiga titik merah.


import static org.lwjgl.opengl.GL11.GL_NO_ERROR;
import static org.lwjgl.opengl.GL11.glGetError;
 
import java.nio.FloatBuffer;
 
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.*;
import org.lwjgl.util.glu.GLU;


public class ShaderDemo {
	/**
	 * General initialization stuff for OpenGL
	 */
	private int vaoId = 0;
	private int vboId = 0;
	
	public void initGl() throws LWJGLException
	{
		// width and height of window and view port
		int width = 640;
		int height = 480;
		
		// menggunakan versi 4 dan tidak 
		// menggunakan deprecated function
		PixelFormat pixelFormat = new PixelFormat();
		ContextAttribs contextAtrributes = new ContextAttribs(4, 0);
		contextAtrributes.withForwardCompatible(true);
		contextAtrributes.withProfileCore(true);
		
		// set up window and display
		try {
			Display.setDisplayMode(new DisplayMode(width, height));
			Display.setVSyncEnabled(true);
			Display.setTitle("Shader Demo");
			//baru setelah create fungsi opengl dapat digunakan
			Display.create(pixelFormat, contextAtrributes);
		} 
		catch (LWJGLException e) {
			System.out.println("error");
			e.printStackTrace();
			System.exit(-1);
		}
		// initialize basic OpenGL stuff
		GL11.glViewport(0, 0, width, height);
		GL11.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
	}
	
	public void run()
	{
		// loading, compiling,  linking vertex shader dan fragment shader
		ShaderProgram shader = new ShaderProgram();
		shader.init("src/simple.vertex", "src/simple.fragment");		
		
		//mempersiapkan VAO dan VBO
		//outpuntya: voaId sudah siap 
		constructVertexArrayObject();
	
		//loop 
		while( Display.isCloseRequested() == false )
		{
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
 
			// tell OpenGL to use the shader
			GL20.glUseProgram( shader.getProgramId() );
			
			// bind vertex, bukan untuk alokasi tapi untuk mengaktifkan 
			GL30.glBindVertexArray(vaoId);
			GL20.glEnableVertexAttribArray(0);

 
			// draw VAO
			// mulai dari 0, sebanyak 3 point
			GL11.glDrawArrays(GL11.GL_POINTS, 0, 3);
 
			// check for errors
			if( glGetError() != GL_NO_ERROR )
			{
				throw new RuntimeException("OpenGL error: "+GLU.gluErrorString(glGetError()));
			}
 
			// swap buffers and sync frame rate to 60 fps
			Display.update();
			Display.sync(60);
		}
 
		Display.destroy();
	}
 
	/**
	 * Create Vertex Array Object necessary to pass data to the shader
	 */
	private void constructVertexArrayObject()
	{
		
		// create vertex data 
		// koordinat 3 titik, masing2 direprsentasikan
		// dengan tiga nilai: x,y,z
		float[] vertices  = new float[] {
		    	0f,		0f,		0f,
		    	0.5f,	0f, 	0f,
		    	0f,		0.5f,	0f
		};
		
		// pindahkan array ke buffer
		//kalau di contoh C++ kebanyakan menggunakan arary 
		//lwjgl menggunakan buffer agar lebih cepat
		FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
		verticesBuffer.put(vertices);
		verticesBuffer.flip();
 
		
		// Buat Vertex Array Object (VAO) dan pilih (bind)
		// Satu VAO dapat memiliki 16 atribut (VBO) 
		//GLGen untuk alokasi nama
		//GLBind untuk alokasi memori
				
		vaoId = GL30.glGenVertexArrays();
		GL30.glBindVertexArray(vaoId);
		
		
		// Buat vertex buffer object dan pilih (VBO) 
		// VBO berisi vector, pada program ini akan diisi oleh lokasi vertex
		// VBO dikelola oleh vertex array object (VAO)
		vboId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		
		
		//transfer vertexdata ke buffer object yang dipilih
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
		
		//parameter 1: Letakkan VBO pada attributes list di index 0
		//  ini terkait dengan vertex shader 
		// 	0 --> layout(location = 0) di simple.vertex
				
		//parameter 2: ukuran tiap vertex, yaitu 3 (x,y,z)
		//paramter 4 : false --> koordinat tidak dibatasi -1, 1
		//paramter 5 : 0     --> data contiguous (tidak loncat2)
		//paramter 6 : 0     --> data mulai dari posisi 0
		GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
		
		//ukuran point, default 1 pixel yang tidak terlihat
		//jadi diperbesar jadi 5 pixel
		GL11.glPointSize(5);
		
		
		//Deselect (bind to 0) the VBO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		// Deselect (bind to 0) the VAO
		GL30.glBindVertexArray(0);
		
	}
 
	/**
	 * main method to run the example
	 */
	public static void main(String[] args) throws LWJGLException
	{
		ShaderDemo demo = new ShaderDemo();
		demo.initGl();
		demo.run();
	}
}


Apa itu OpenGL dan OpenGL-ES?

OpenGL adalah kumpulan standard API (Application Programming Interface) yang menghubungkan software dengan hardware grafis untuk menampilkan gambar 2D dan 3D. Intinya OpenGL itu adalah kumpulan library untuk mengakses hardware (GL= graphical library).  OpenGL mendefinisikan berbagai instruksi untuk menggambar objek, image (umumnya 3D) dan melakukan berbagai operasi terhadap objek-objek tersebut. OpenGL tidak mengandung source code, hanya spesifikasi saja. Pembuat GPU (graphical processing unit) seperti NVIDIA, Intel, Samsung dll yang akan membuat implementasi.  Dengan cara ini walaupun GPU diproduksi oleh berbagai produsen dengan berbagai berbagai variasi tipe dan implementasi, semuanya dapat diperintah dengan spesifikasi yang sama. Kebayang kan repotnya kalau setiap produsen punya spesifikasi yang berbeda: game ini hanya dapat dijalankan di NVIDIA saja, game yang itu hanya di Intel saja dst.

OpenGL dirancang independen terhadap sistem operasi, hardware, maupun bahasa pemrograman yang digunakan. Bahkan jika GPU tidak tersedia, openGL dapat dijalankan diatas software yang mengemulasi hardware,  tentu dengan kinerja yang  lebih rendah.

OpenGL dikembangkan mulai dari tahun 90-an  dan saat ini telah menjadi standard industri. OpenGL ada hampir disemua platform: Windows, Linux, Mac, smartphone, game console, avionic dan berbagai embedded system. Dari sisi software, OpenGL digunakan untuk berbagai macam hal mulai dari game, visualisasi, simulasi, CAD (Computer-Aided Design) sampai editing video dan image.

Standard yang ada di OpenGL dikelola oleh konsorsium yang berisi berbagai pihak yang berkepentingan dengan computer grafis.  Konsorsium itu disebut Khronos yang anggotanya  antara lain: AMD, Intel, NVIDIA, Apple, ARM,  Nokia, Qualcomm, Samsung, Sony, Epic Games.   Khronos juga mengelola standard lain seperti OpenCL, OpenVG  dan WebGL.

OpenGL adalah low level API, jadi saat kita menggambar suatu objek kita harus mengirimkan terlebih dulu objek, texture,  shaders dan lainnya. Ini membuat programming dengan OpenGL bisa jadi hal yang rumit, tapi disisi lain jadi lebih powerfull dan fleksibel.  Bagi pemula hal ini bisa memusingkan karena terdapat banyak variasi teknik yang dapat dilakukan untuk mencapai hasil yang sama.  Umumnya pengembang game tidak menggunakan openGL secara langsung, tetapi melalui game engine seperti Unity.

OpenGL terus berkembang dan semakin lama semakin ‘gemuk’ dan kompleks sehingga pada di versi 3, fungsi-fungsi yang dianggap sudah tidak dibutuhkan lagi (deprecated) dibuang. Tapi karena penolakan beberapa vendor seperti NVIDIA, maka di versi 3.2 diperkenalkan dua profile: pertama core profile yang seperti versi 3 membuang bagian yang deprecated dan versi kedua adalah compatibility profile yang tetap compatible sampai dengan OpenGL versi 1.  Bagi pemula, dianjurkan untuk belajar core profile  yang walaupun lebih rumit daripada  versi openGL sebelumnya, tetapi lebih aman untuk masa depan saat spesififikasi  yang deprecated benar-benar dibuang.

Jika kita menggunakana Java, ada dua library yang dapat digunakan LWLJGL  dan JOGL.  Kinerjanya relatif sama, jadi tinggal masalah selera saja.  Saya pribadi lebih memilih LWLJGL.

OpenGL ES

OpenGL ES adalah versi OpenGL untuk embedded system  dan mobile device khususnya untuk iPhone dan Android. Untuk ‘merampingkan’ OpenGL ES,  API OpenGL yang jarang digunakan atau terlalu kompleks dibuang.

OpenGL ES 2.0 memiliki kemiripan dengan OpenGL 3.0, tetapi ada beberapa fitur yang belum disupport seperti: glMultiDrawElements, glDrawRangeElements, antialiased lines polygon smooth, polygon antialiasing, multiple polygon modes, depth textures, rectangle textures, dan array textures. OpenGL ES 2.0 juga hanya dapat menggunakan vertex dan fragment shader.

Seperti halnya OpenGL 3, OpenGL ES 2.0 adalah versi yang tidak kompatible dengan versi sebelumnya (OpenGL ES 1).  Bagi pemula, dianjurkan langsung mempelajari versi OpenGL ES 2.0 ini.  Tetapi jika tujuannya hanya untuk membuat game,  sebaiknya menggunakan library seperti LibGDX yang menyederhanakan penggunaan OpenGL ES.

sumber: OpenGL superbible (Wright dkk) & OpenGL programming guide (Shreiner dkk)

Artikel selanjutnya: OpenGL dan Java

Update: Sebaiknya jangan belajar langsung ke OpenGL kecuali benar-benar diperlukan, seperti membuat game engine sendiri, atau membuat custom aplikasi grafis yang membutuhkan kinerja tinggi. Untuk awalnya, bisa menggunakan framework seperti LibGDX yang mempermudah pembuatan game atau simulasi tapi kalau perlu bisa mengakses API OpenGL langsung. Tutorialnya tersedia di blog ini (untuk Android, tapi bisa dijalankan di desktop juga).