Java-Gaming.org    
Featured games (91)
games approved by the League of Dukes
Games in Showcase (576)
games submitted by our members
Games in WIP (498)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  LWJGL Tutorial Series - Introduction to Shaders  (Read 3155 times)
0 Members and 1 Guest are viewing this topic.
Offline SHC
« Posted 2013-09-27 13:20:59 »

LWJGL Tutorial Series - Introduction to Shaders


Welcome to the twelfth part of the LWJGL Tutorial Series. In this tutorial, we are going to learn basics about shaders. We learn things like what shaders actually are and how can they be effectively used. Also at the end of this tutorial, we will write shaders that changes the color of every vertex into red. This tutorial follows from the previous tutorial 'A Rotating Cube' and we just make additions to it's source. If you've found any mistakes in this tutorial, please notify them to me with comments.

What are Shaders?


Shaders are nothing but some small programs that runs directly on the GPU. These are designed to replace fixed function rendering and gain control on what is actually rendered. The two main types of shaders are
Vertex Shader
and
Fragment Shader
There is also another type of shader called as
Geometry Shader
but we'll learn about it in future. Here's their operations in detail.

  • Vertex Shader


    • Vertex Transformation
    • Normal Transformation, Normalization and rescaling
    • Lighting
    • Texture Coordinate Generation and Transformation

  • Fragment Shader


    • Texture access and Application
    • Fog

These are the mostly used applications. I've listed all of these but we are only going to learn how to make the basic shaders in this tutorial. Other advanced operations will be explained in future tutorials when we need them. Now that we've understood what shaders actually are, let's learn how to write them.

OpenGL Shading Language


OpenGL Shading Language or GLSL for short is a high level programming language developed to use with shaders. All the shaders we are going to write will be in this language. It's similar to C language but it is easily understandable by us. I'm gonna explain it's syntax first before actually loading and compile them in our tutorial. Let's first start with a basic program.

1  
2  
3  
4  
void main()
{
    // Your program code here
}

Yes, every shader should contain a void main method from where the execution begin. You can also load comments the same way we use in Java. Now let's see a listing of some data types. The basic data type is
float
and not
int
since GPU's are specially designed to work with precision and they are faster with floats. Now let's see a program which defines a float variable.

1  
2  
3  
4  
void main()
{
    float variable = 1.0;
}

It's very similar to java but there is one thing, there is no 'f' suffix as in java. Now let's try to define a constant. For that we use the
const
keyword which is equal to
final
in java.

1  
2  
3  
// I know this is not accurate but it is the value
// present in our mathematics textbook
const float pi = 3.141714;

Now comes the vector types. Here's an example of them.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
// A 2D Vector is made with two float values
vec2 2dVec = vec2(1.0, 0.0);

// A 3D Vector can be made with three float values
vec3 3dVec = vec3(1.0, 0.0, 1.0);

// A 3D Vector can also be made with a 2D Vector and a float value
vec3 3dVec2 = vec3(2dVec, 1.0);

// A 4D Vector can be made from four floats
vec4 4dVec = vec4(1.0, 0.0, 1.0, 0.0);

// A 4D Vector can also be made with a 3D Vector and a float value
vec4 4dVec2 = vec4(3dVec, 0.0);

Note that the vecs on the right hand side of the statements are constructors and not functions. Don't get confused since there is no
new
keyword in GLSL as well as in C. There are other data types such as matrix types, samplers etc., but I'm not going to show them in this basic tutorial. Upto now, this is so similar to Java code. From now on, the things will change slightly since now I'm going to introduce variable modifiers. There are some modifiers for the variables which I'll explain in detail. The major modifiers are
varying
,
uniform
and
attribute
and here is what they mean.

uniform  
It means that the value of that variable doesn't change during rendering. However, it can be changed before rendering starts.
varying  
It means that the variable is shared between both vertex and fragment shaders. It allows communication between shaders.
attribute
It means that the variable is set by Java Code and is read in the vertex shader only. These are also read-only and are only available in vertex shaders.

There are also some built-in variables in GLSL. I'm not going to list everything here but just note that if any variable starts with
gl_
then it is a built in variable. There are also built-in functions which are recognisable. However, I'll point that out when we came across them. Enough of explanation about GLSL and let's now learn how to load the shaders and apply them.

Loading Shaders


Now we write code to load the code from source files. I'm listing the code of our
FileUtil.java


1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
package com.shc.tutorials.lwjgl.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * A Utility class for dealing with files.
 *
 * @author Sri Harsha Chilakapati
 */

public class FileUtil
{
    /**
     * @return The entire source of a file as a single string
     */

    public static String readFromFile(String name)
    {
        StringBuilder source = new StringBuilder();
        try
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                                                                ShaderProgram.class
                                                                .getClassLoader()
                                                                .getResourceAsStream(name)));
           
            String line;
            while ((line = reader.readLine()) != null)
            {
                source.append(line).append("\n");
            }
           
            reader.close();
        }
        catch (Exception e)
        {
            System.err.println("Error loading source code: " + name);
            e.printStackTrace();
            Game.end();
        }
       
        return source.toString();
    }
}

This is used to load the source code of the file into a string. Now let's learn how to create a shader. At first, we need to make a program for our shaders. We then attach our shaders to the program and link the program. Then, we bind it before drawing and later unbind it.

ShaderProgram class


This is relatively easy so I'm now listing the key methods of
ShaderProgram.java
and you can always find the full version of at the source code at the end.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
public class ShaderProgram
{
    // ProgramID
   int programID;
   
    // Vertex Shader ID
   int vertexShaderID;
    // Fragment Shader ID
   int fragmentShaderID;
   
    /**
     * Create a new ShaderProgram.
     */

    public ShaderProgram()
    {
        programID = glCreateProgram();
    }

This shows the constructor of the ShaderProgram class. In this, we generate a program id by calling
glCreateProgram()
which is part of LWJGL's
org.lwjgl.opengl.GL20
class. Now, let's see how to attach a vertex shader to this program.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
public void attachVertexShader(String name)
{
    // Load the source
   String vertexShaderSource = FileUtil.readFromFile(name);
       
    // Create the shader and set the source
   vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderID, vertexShaderSource);
       
    // Compile the shader
   glCompileShader(vertexShaderID);
   
    // Check for errors
   if (glGetShaderi(vertexShaderID, GL_COMPILE_STATUS) == GL_FALSE)
    {
        System.err.println("Unable to create vertex shader:");
        dispose();
        Game.end();
    }
       
    // Attach the shader
   glAttachShader(programID, vertexShaderID);
}

This loads the full source of our file through our utility class and then we call
glCreateShader(GL_VERTEX_SHADER)
which creates an ID for the vertex shader. Then by calling
glShaderSource()
function, we send the source code to the shader. Next we are compiling the shader with
glCompileShader()
function. Now we need to check whether the compilation succeeds or not and we use the next 4 lines of code for that. And finally, we attach the shader to the program with
glAttachShader()
function. And you could write
attachFragmentShader()
method in the same way. So I'm not including it here.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
public void link()
{
    // Link this program
   glLinkProgram(programID);

    // Check for linking errors
   if (glGetProgrami(programID, GL_LINK_STATUS) == GL_FALSE)
    {
        System.err.println("Unable to link shader program:");
        dispose();
        Game.end();
    }
}

Now the next phase is linking the program for actual use. In this part, we'll try to link the program with a call to
glLinkProgram()
function and then we check for the linking errors by querying with
glGetProgrami(programID, GL_LINK_STATUS)
and if any errors are found, we'll simply end the game. Next are the functions
bind()
and
unbind()
methods. Since there is nothing much to explain, I'm just listing them here.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
/**
 * Bind this program to use.
 */

public void bind()
{
    glUseProgram(programID);
}

/**
 * Unbind the shader program.
 */

public static void unbind()
{
    glUseProgram(0);
}

There is one last thing in this class, that is the
dispose()
method. Here's what it does.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
public void dispose()
{
    // Unbind the program
   unbind();

    // Detach the shaders
   glDetachShader(programID, vertexShaderID);
    glDetachShader(programID, fragmentShaderID);

    // Delete the shaders
   glDeleteShader(vertexShaderID);
    glDeleteShader(fragmentShaderID);

    // Delete the program
   glDeleteProgram(programID);
}

We first unbind any shader programs being used. Then we detach the shaders from the program and delete them individually with
glDetachShader()
and
glDeleteShader()
functions. Then we finally delete the program itself with the
glDeleteProgram()
function. That's the end of the ShaderProgram class and now we write the actual shader programs in GLSL.

An Ambient Shader


An ambient shader is a shader that changes the color of every vertex into a single color. We are going to make the cube we made in previous tutorial into red. So let me list the shaders and then explain them individually. Now let me show the source of our vertex shader
shader.vert


1  
2  
3  
4  
void main()
{
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

What does this do? This transforms the position of the vertex by multiplying with the ModelView Projection matrix. All the transformations in OpenGL are done through matrices and we'll learn it thoroughly in future tutorials. Now let's see the source of the our fragment shader
shader.frag


1  
2  
3  
4  
void main()
{
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

There is nothing much to explain here. We create a 4 dimensional vector to represent our color data with components red, green, blue and alpha values. We create a red color vector with this code. Here we've seen four built-in variables. Let me explain them in detail.

gl_Position                 
4D vector representing the final processed vertex position. Only available in vertex shader.
gl_Vertex                   
4D vector representing the vertex position.
gl_ModelViewProjectionMatrix
4x4 Matrix representing the model-view-projection matrix.
gl_FragColor                
4D vector representing the final color which is written in the frame buffer. Only available in fragment shader.

Now, let's see how to construct the ShaderProgram for our tutorial. In the
init()
method,

1  
2  
3  
4  
5  
6  
7  
// Create Shader Program
shaderProgram = new ShaderProgram();
shaderProgram.attachVertexShader("com/shc/tutorials/lwjgl/tutorial12/shader.vert");
shaderProgram.attachFragmentShader("com/shc/tutorials/lwjgl/tutorial12/shader.frag");

// Link the program
shaderProgram.link();

We are creating a new ShaderProgram object and attaching the shaders and linking them together. Now comes the
render()
method.

1  
2  
3  
4  
5  
6  
7  
// Use shader
shaderProgram.bind();

// Code to render the CUBE VBO

// Disable shader
ShaderProgram.unbind();

And don't forget to dispose the ShaderProgram in the game's
dispose()
method. And if we run it, it'll look like


See, our cube is now completely red! That's the end of this tutorial and in the next tutorial, we are going to learn how to create a 3D Camera class. If you've found any mistakes in this tutorial, please notify them to me with comments so that I can make changes.

Source Code


Tutorial12.java
ShaderProgram.java
FileUtil.java

Offline xsvenson
« Reply #1 - Posted 2013-09-27 13:27:56 »

Quote
...
It's very identical to java but there is one thing, there is no 'f' suffix as in java.
...

"very identical" is a fallacy. There is no "more" or "less" identical.

I think You are looking for "very similar".

“The First Rule of Program Optimization: Don't do it. The Second Rule of Program Optimization (for experts only!): Don't do it yet.” - Michael A. Jackson
Offline SHC
« Reply #2 - Posted 2013-09-27 13:29:22 »

@xsvenson

That's the right word! Thanks!

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline davedes
« Reply #3 - Posted 2013-09-27 15:09:18 »

Just a note; when you move to the fully programmable pipeline there is no "default shader", so rendering with
glUseShader(0)
may actually raise an error.

I'm surprised that you're starting to cover shaders, but you still haven't mentioned the (modern) programmable pipeline. Do you have plans to do so? Or is this series just a flash back to the past? Tongue

Offline SHC
« Reply #4 - Posted 2013-09-27 15:16:50 »

I thought to move to programmable pipeline but my drivers only support OpenGL 2.1 in compatibility mode. GLView says my mac has 3.1 and on windows, it is 3.3 when on core profile. I just doesn't understand how to switch between those profiles in LWJGL. But for sure, I'll move on to it slowly.

Offline SHC
« Reply #5 - Posted 2013-09-27 15:32:42 »

Quote
there is no "default shader", so rendering with
glUseShader(0)
may actually raise an error.

Please also say the alternative.

Online RobinB

JGO Knight


Medals: 37
Projects: 1
Exp: 3 years


Spacegame in progress


« Reply #6 - Posted 2013-09-27 16:02:49 »

Please no depricated shaders.
There is no reason to use the old version, the new(er) stuff is at least as easy to understand.
But unlearning everything with the gl_ prefix will make it harder as you get more used (dependent) on it.
Offline SHC
« Reply #7 - Posted 2013-09-27 16:35:18 »

@RobinB

This tutorial is just for an introduction. I'm gonna switch to OpenGL 3.2 within the next four tutorials and none in between touch shaders again.

Offline quew8

JGO Coder


Medals: 23



« Reply #8 - Posted 2013-09-27 18:03:29 »

Quote
I just doesn't understand how to switch between those profiles in LWJGL.

http://www.lwjgl.org/wiki/index.php?title=Version_selection See here.
Offline SHC
« Reply #9 - Posted 2013-09-27 18:09:29 »

I don't know how I forgot to check with that wiki. Thanks for the link. Could you also suggest the topics to touch in order from here? I'm thinking about a 3D camera, then some 3D models, then VAO's and then making a room, etc.,

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline quew8

JGO Coder


Medals: 23



« Reply #10 - Posted 2013-09-27 18:31:36 »

I think that's a good line up more or less. In terms of OpenGL features you haven't mentioned, there are a few I feel it would be a good idea to cover:

Framebuffers (FBOs)

Alternate Buffer Usage (Stencil and accumulation buffer. If you do this for god sake make sure you request a stencil and or accumulation buffer when you create the Display)

Definitely some more advanced shader usage. The newer GLSLs maybe but I just feel there was a lot you didn't talk about. I do appreciate that this is an intro but later on...

However really, from this point on I think the most important requirement, other than general programming technique, is just a really solid understanding of matrix transformations and all that palaver.
Offline SHC
« Reply #11 - Posted 2013-09-27 18:42:35 »

I'll explain some matrix math in the next tutorial 'Creating 3D Camera'. I'll then use that matrix math to create a Camera class. Can you point to some tutorials? I've found open.gl site's but I just wanted to read multiple tutorials on the topic before writing mine.

Offline quew8

JGO Coder


Medals: 23



« Reply #12 - Posted 2013-09-27 18:58:11 »

You mean tutorials on matrix transformations?

I've probably already linked you this one (because I link it to everyone) but just in case: http://www.wildbunny.co.uk/blog/vector-maths-a-primer-for-games-programmers/ Then of course there's this http://arcsynthesis.org/gltut/ I haven't actually read the tutorial on transformations (I already knew that stuff when I found this tutorial) but if the rest of the tutorial is any guide, it'll be great. Also @princec ported the code of this tutorial to LWJGL if your interested (but I can never find the link to that). Also there is this http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/ and this http://www.opengl-tutorial.org/miscellaneous/math-cheatsheet/ (the same website) which I think are good for people who haven't done much maths at all (I don't know your background so please don't be offended if your doing a maths degree or whatever) That's all for now. I'll post up any more if I think of them.
Offline davedes
« Reply #13 - Posted 2013-09-27 20:03:26 »

Quote
Please also say the alternative.
In the programmable pipeline, you shouldn't expect zero to refer to a valid shader program. Instead, you should always have a shader bound before rendering.

So there is no need to ever bind shader program zero.

"If program is zero, then the current rendering state refers to an invalid program object and the results of shader execution are undefined. However, this is not an error."
http://www.opengl.org/sdk/docs/man/xhtml/glUseProgram.xml

Quote
I thought to move to programmable pipeline but my drivers only support OpenGL 2.1 in compatibility mode.
My Mac's drivers only support GL 2.1 but I still tend to use almost all aspects of the programmable pipeline. This means getting away from deprecated calls like:
  • glRotate, glLoadMatrix, glOrtho, glVertex
  • built-in attributes like gl_Color, gl_MultiTexCoord0, etc.
  • Fixed-function features like texture combiners, lighting, fog, text, display lists, etc.

    GL 2.0 is actually a good version to learn since OpenGL ES is based on it (and thus mobile + WebGL).

    Because I target 2.0+ (which you probably should if you want to reach a wider audience), there are some things I use that are not truly part of the "modern" 3.0+ pipeline:
    • GLSL has evolved with new features, but I am still using GLSL #version 110 (the default).
    • My custom attributes need to be specified with glBindAttribLocation -- while not deprecated, the layout qualifier is preferred in the modern pipeline.
    • I am not taking advantage of modern features like texture arrays, geometry shaders, or instanced drawing.

      You can see that all of my tutorials are compatible with GL 2.0, but focus on the programmable pipeline:
      https://github.com/mattdesl/lwjgl-basics/wiki

      Offline SHC
      « Reply #14 - Posted 2013-09-28 05:01:00 »

      Thanks for your replies. I'm gonna learn OpenGL 3.0 with GLSL 1.50

      Offline Phibedy

      Senior Member


      Medals: 8



      « Reply #15 - Posted 2013-09-28 09:58:17 »

      Nice tut anyways, keep up good work Smiley
      Offline Spasi
      « Reply #16 - Posted 2013-09-28 16:33:59 »

      I'd like to see a more efficient way of loading shader source code from a file. The way it is now, the following things happen:

      The file contents are copied to the BufferedReader's buffer (1) and also expanded to 2 bytes per character, then to the StringBuffer used by .readLine (2), then to the line String (3), then copied to the StringBuilder's buffer (4), then copied to the final String containing the whole source (5), then the CharSequence version of glShaderSource is used and LWJGL copies the String to a ByteBuffer (6) to pass it down to OpenGL.

      6 copies. This can be done much more efficiently, even if the source is a stream and you don't know the source byte size. Or even without a copy using a MappedByteBuffer.
      Offline Danny02
      « Reply #17 - Posted 2013-09-28 16:51:09 »

      @spasi
      yes this seems wastfull, but I guess it doesn't matter. A Shader file is most of the times only a couple of lines and I guess this loading and copying takes only a fraction compared to the time the driver needs to compile the shader.

      Also, I like to parse the shader source to add some framework features. Like an inlcude command for example
      Offline SHC
      « Reply #18 - Posted 2013-09-28 17:20:54 »

      @Spasi

      I don't think there is any better way for loading from files and if you need, just write the shaders in a string directly in java code.

      Offline davedes
      « Reply #19 - Posted 2013-09-28 18:31:25 »

      Here is a faster method:
      https://github.com/mattdesl/lwjgl-basics/blob/master/test/mdesl/test/Util.java#L75

      You can probably get faster than that with Java 7. Or something more specific to LWJGL, by just passing a ByteBuffer without the String copying.

      But in reality it probably won't make a dent in your game's load time.

      Offline Riven
      « League of Dukes »

      JGO Overlord


      Medals: 605
      Projects: 4
      Exp: 16 years


      Hand over your head.


      « Reply #20 - Posted 2014-04-15 21:42:02 »

      Unlocked upon request Pointing

      Hi, appreciate more people! Σ ♥ = ¾
      Learn how to award medals... and work your way up the social rankings
      Pages: [1]
        ignore  |  Print  
       
       

       

      Add your game by posting it in the WIP section,
      or publish it in Showcase.

      The first screenshot will be displayed as a thumbnail.

      xsi3rr4x (16 views)
      2014-04-15 18:08:23

      BurntPizza (14 views)
      2014-04-15 03:46:01

      UprightPath (27 views)
      2014-04-14 17:39:50

      UprightPath (12 views)
      2014-04-14 17:35:47

      Porlus (29 views)
      2014-04-14 15:48:38

      tom_mai78101 (51 views)
      2014-04-10 04:04:31

      BurntPizza (110 views)
      2014-04-08 23:06:04

      tom_mai78101 (211 views)
      2014-04-05 13:34:39

      trollwarrior1 (179 views)
      2014-04-04 12:06:45

      CJLetsGame (185 views)
      2014-04-01 02:16:10
      List of Learning Resources
      by Longarmx
      2014-04-08 03:14:44

      Good Examples
      by matheus23
      2014-04-05 13:51:37

      Good Examples
      by Grunnt
      2014-04-03 15:48:46

      Good Examples
      by Grunnt
      2014-04-03 15:48:37

      Good Examples
      by matheus23
      2014-04-01 18:40:51

      Good Examples
      by matheus23
      2014-04-01 18:40:34

      Anonymous/Local/Inner class gotchas
      by Roquen
      2014-03-11 15:22:30

      Anonymous/Local/Inner class gotchas
      by Roquen
      2014-03-11 15:05:20
      java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
      Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!