PDA

View Full Version : VBO with generic attributes and shader does not render



Teamworkguy2
09-06-2012, 06:40 PM
Using Windows 7 (x64) with Nvidia GTS 450
Open GL 4.2.0 (lwjgl 2.8.4)
Shaders are compiled with #version 130

I'm *trying* to use buffer objects in GL and the result is a black screen (immediate mode works fine).
I've tried checking glGetError()'s output, but did not get any errors.
In the below code, I can render the VBO vertices using immediate mode and it displays just fine using the same vertex, normal, and color data as the VBO.
The vertex and fragment shaders seem to be working(?) since the test triangles render correctly (correct me if immediate mode triangles do not use the same vertex and fragment shaders as VBOs).

Here's some of the relevant code, I made a test project to work on the code that is having the problem.
So hopefully it highlights the problem and does not waste anyone's time.

Here's the main method that creates the Open GL context and loads the shaders (I did not include the actual code for those functions) and runs a loop to render a VBO and a test triangle.


public static void main(String[] args) {
initGL();
GLShader shader = null;
try {
File vertexShaderFile = new File("shaders/shader.vert");
File fragmentShaderFile = new File("shaders/shader.frag");

// The next 3 lines just create the 3 Open GL integers for the program, vert, and frag shaders and store them in an object that has fields for each integer
shader = ShaderLoader.loadShaders(vertexShaderFile, fragmentShaderFile);
int programId = ShaderLoader.createProgram(shader.getVertexShader( ), shader.getFragmentShader())
shader.setProgram(programId);

// Generic Vertex Attributes to use when drawing the VBO
GL20.glBindAttribLocation(shader.getProgram(), 0, "position_in");
GL20.glBindAttribLocation(shader.getProgram(), 1, "normal_in");
GL20.glBindAttribLocation(shader.getProgram(), 2, "color_in");

ShaderLoader.linkAndUseShader(shader.getProgram()) ;
} catch (IOException e) {
System.err.println("Error loading shader files");
e.printStackTrace();
}

ByteBuffer data = GLVBO.getDataTestBuffer();
ByteBuffer element = GLVBO.getIndexTestBuffer();
// Hard coded 32 bytes for Vertexf[x,y,z]Normalf[x,y,z]Colorb[r,g,b,a]Paddingf[0] as well as exactly 3 elements
GLVBO vbo = new GLVBO(data, element, GL15.GL_STATIC_DRAW, GL11.GL_TRIANGLES, 32, 3);

while(!Display.isCloseRequested()) {
gameLoop(vbo, data);
}

destroy();
}


private static void gameLoop(GLVBO vbo, ByteBuffer vertexNormalColor) {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

vbo.render();

GL11.glBegin(GL11.GL_TRIANGLES);
for( ; vertexNormalColor.position() < vertexNormalColor.limit(); ) {
// Sorry, a lot of variables, I decided to displayed the actual vertex data of the VBO to ensure that the data is correct
float vx = vertexNormalColor.getFloat();
float vy = vertexNormalColor.getFloat();
float vz = vertexNormalColor.getFloat();
float nx = vertexNormalColor.getFloat();
float ny = vertexNormalColor.getFloat();
float nz = vertexNormalColor.getFloat();
byte r = vertexNormalColor.get(); // Get's a single byte
byte g = vertexNormalColor.get();
byte b = vertexNormalColor.get();
byte a = vertexNormalColor.get();
int padding = vertexNormalColor.getInt(); // Padding because [stride=32] = [Vertex=3*4 + Normal=3*4 + Color=1*4 + Padding=1*4]
GL11.glColor4f(r, g, b, a);
GL11.glNormal3f(nx, ny, nz);
GL11.glVertex3f(vx+1, vy+1, vz+1); // Shift the test vertices so that I can see when I get the VBO working
}
GL11.glEnd();
vertexNormalColor.flip();

Display.update();
Display.sync(maxFps);
}


Here is the class that creates and renders the VBO (it basically acts like a VAO for my program)


public class GLVBO {
private int dataId, elementId, elementCount, elementSize, drawType;

/** GLVBO, create Open GL Buffer Object
* @param data - the buffer data
* @param element - list of indices to buffer data
* @param usage - the buffer's usage (e.g. GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY, GL_STATIC_DRAW,
* GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, or GL_DYNAMIC_COPY)
* @param drawType - such as GL_TRIANGLES, GL_LINES, etc...
* @param elementSize - the number of bytes in each element
* @param elementCount - the number of elements in the data and element buffers
*/
public GLVBO(ByteBuffer data, ByteBuffer element, int usage, int drawType, int elementSize, int elementCount) {
// Create the Vertex BO and Element BO
dataId = createBO(data, GL15.GL_ARRAY_BUFFER, usage);
elementId = createBO(element, GL15.GL_ELEMENT_ARRAY_BUFFER, usage);

this.drawType = drawType;
this.elementSize = elementSize;
this.elementCount = elementCount;
}


public void render() {
// Bind buffers
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.dataId);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.elementId);

// Bind attribute types
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, this.elementSize, 0); // Vertex (x,y,z)
GL20.glVertexAttribPointer(1, 3, GL11.GL_FLOAT, false, this.elementSize, 12); // Normal (x,y,z)
GL20.glVertexAttribPointer(2, 4, GL11.GL_BYTE, false, this.elementSize, 24); // Color (r,g,b,a)

// Enable attribute types
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);

// Draw attributes from GL_ARRAY_BUFFER using indices from GL_ELEMENT_ARRAY_BUFFER
GL11.glDrawElements(this.drawType, this.elementCount, GL11.GL_INT, 0);

// Disable attribute types
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);

// Unbind buffers
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}


public static int createBO(ByteBuffer vertexData, int targetType, int usage) {
int id = GL15.glGenBuffers();
GL15.glBindBuffer(targetType, id); // Bind the new buffer
GL15.glBufferData(targetType, vertexData, usage); // Upload the buffer data
GL15.glBindBuffer(targetType, 0); // Unbind the buffer once data uploading is done
return id;
}
}


Hopefully the code did not scare anyone away.
I am guessing that I'm doing something wrong with generic vertex attributes, but I'm not sure since I don't fully understand how attributes are supposed to work.
Also, I'd be happy to post the vertex and fragment shaders if anyone wants to see them, their very short.

Teamworkguy2
09-12-2012, 06:12 PM
Anyone have any ideas, I'm still trying to work on this problem.

Note:
I noticed a error in my post, this comment

// Hard coded 32 bytes for Vertexf[x,y,z]Normalf[x,y,z]Colorb[r,g,b,a]Paddingf[0] as well as exactly 3 elements
is supposed to mean this

// Hard coded 32 bytes for Vertexf[x,y,z]Normalf[x,y,z]Colorb[r,g,b,a]Paddingf[1] as well as exactly 3 elements

Teamworkguy2
09-16-2012, 06:13 PM
Well, I have made the move to shaders and have spent the last week or so trying to figure out how to use shaders and how to create working VBOs/VAOs.
I found a good example of how to program in Open GL 4.0 at http://openglbook.com/the-book/chapter-2-vertices-and-shapes/ (most of the following code is modified to fit Java from the examples on that website).

I am posting the following code in hopes that it benefits some lost soul trying to learn the newer (buffer object based) version of Open GL (>=3.0) in Light Weight Java Game Library (LWJGL).
The following code is not perfect or optimized by any stretch of the imagination, but it should at least get a triangle rendered to the screen using VAOs and Shaders without any dependancy libraries other than LWJGL.

Here's the main class (GLSLExample.java) with the initialization methods for LWJGL and the render loop:


import java.io.IOException;
import java.io.StringReader;
import java.nio.FloatBuffer;
import java.util.Arrays;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;


/** Renders a colored triangle using Vertex Buffer Objects
*/
public class GLSLExample {
final int maxFps = 60;
private int fps;
private long nextFps;
private long lastFrame;
private int modelViewMatrixUniformLocation;
private int projectionMatrixUniformLocation;
private VAO vao;


public GLSLExample() {
float[] modelViewMatrix = null;
float[] projectionMatrix = null;
float horzFOV = 90;
float vertFOV = 70;
float zNear = 1.9f;
float zFar = 11f;
float widthRatio = horzFOV/vertFOV;

initGL("Open GL/GLSL Test", 800, 600);

projectionMatrix = frustumMatrix(-widthRatio, widthRatio, -1, 1, zNear, zFar);
//projectionMatrix = orthoMatrix(-1, 1, -1, 1, zNear, zFar);

FloatBuffer projectionBuffer = BufferUtils.createFloatBuffer(16);
projectionBuffer.put(projectionMatrix);
projectionBuffer.flip();

modelViewMatrix = lookAtMatrix(0, 0, 0, 270, 0);

FloatBuffer modelViewBuffer = BufferUtils.createFloatBuffer(16);
modelViewBuffer.put(modelViewMatrix);
modelViewBuffer.flip();

Shader shader = shaderInit();
modelViewMatrixUniformLocation = GL20.glGetUniformLocation(shader.getProgram(), "modelViewMatrix");
projectionMatrixUniformLocation = GL20.glGetUniformLocation(shader.getProgram(), "projectionMatrix");
GL20.glUniformMatrix4(modelViewMatrixUniformLocati on, false, modelViewBuffer);
GL20.glUniformMatrix4(projectionMatrixUniformLocat ion, false, projectionBuffer);

System.out.println("ModelView: " + Arrays.toString(modelViewMatrix) );
System.out.println("Projection: " + Arrays.toString(projectionMatrix) );

this.vao = new VAO();
vao.createVAO();
gameLoop();
vao.destroyVAO();
destroy();
}


/** initGL, initialize Open GL display and create vbo
* @return two ints, the first is the vbo created and the second is the ibo created
*/
private void initGL(String title, int width, int height) {
try {
Display.setDisplayMode(new DisplayMode(width, height));
Display.setTitle(title);
Display.create();
} catch (LWJGLException e) {
e.printStackTrace();
}
}


private Shader shaderInit() {
Shader shader = null;
StringReader vertexShaderSource = new StringReader(
"#version 130\n" +
"in vec3 in_Position;\n" +
"in vec4 in_Color;\n" +
"out vec4 frag_Color;\n" +
"uniform mat4 modelViewMatrix;\n" +
"uniform mat4 projectionMatrix;\n" +
"void main(void) {\n" +
"gl_Position = (projectionMatrix * modelViewMatrix) * vec4(in_Position, 1.0);\n" +
"frag_Color = in_Color;\n" +
"}\n");
StringReader fragmentShaderSource = new StringReader(
"#version 130\n" +
"in vec4 frag_Color;\n" +
"out vec4 out_Color;\n" +
"void main(void) {\n" +
"out_Color = frag_Color;\n" +
"}\n");
try {
shader = Shader.createShader(vertexShaderSource, fragmentShaderSource);
Shader.linkUseShader(shader.getProgram());
} catch (IOException e) {
System.err.println("Error loading shader files");
e.printStackTrace();
}
return shader;
}


private void gameLoop() {
nextFps = getTime();
while(!Display.isCloseRequested()) {
updateDelta();
updateFps();
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

render();

Display.update();
Display.sync(maxFps);
}
}


private void render() {
GL30.glBindVertexArray(this.vao.getId());
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GL30.glBindVertexArray(0);
}


/** getDelta, calculate how many milliseconds have passed since last frame.
* @return milliseconds passed since last frame
*/
public long updateDelta() {
long time = getTime();
long delta = time - lastFrame;
lastFrame = time;
return delta;
}


/** getTime
* @return the current time in milliseconds
*/
public long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}


/** updateFps, calculate the FPS and set it in the title bar
*/
public void updateFps() {
if(getTime() > nextFps) {
Display.setTitle("Open GL/GLSL Test - FPS: " + fps);
fps = 0;
nextFps += 1000;
}
fps++;
}


private void destroy() {
Display.destroy();
}


/** orthoMatrix, generates a projection/orthogonal matrix from the input parameters
* @return an array of 16 floats in column major order for use as an Open GL projection matrix
*/
public static float[] orthoMatrix(float left, float right, float bottom, float top, float near, float far) {
float[] mat = new float[] {
2/(right-left), 0, 0, 0,
0, 2/(top-bottom), 0, 0,
0, 0, -2/(far-near), 0,
-(right+left)/(right-left), -(top+bottom)/(top-bottom), -(far+near)/(far-near), 1,
};
return mat;
}


/** frustumMatrix, generate a projection/frustrum matrix from the input parameters
* @param near - the distance to the near culling plan (should be greater than 0)
* @param far - the distance to the far culling plan (should be greater than 'near')
* @return an array of 16 floats in column major order for use as an Open GL projection matrix
*/
public static float[] frustumMatrix(float left, float right, float bottom, float top, float near, float far) {
float[] mat = new float[] {
(2*near)/(right-left), 0, 0, 0,
0, (2*near)/(top-bottom), 0, 0,
(right+left)/(right-left), (top+bottom)/(top-bottom), -(far+near)/(far-near), -1,
0, 0, -(2*far*near)/(far-near), 0,
};
return mat;
}


/** lookAtMatrix, generates a model-view matrix 'looking at' the specified direction
* @return
*/
public static float[] lookAtMatrix(float xCenter, float yCenter, float zCenter, float horzRotation, float vertRotation) {
float panRadius = 1.0f; // Causes the camera to rotate around the central point
float xLook = (float) Math.cos( (horzRotation)*Math.PI/180 );
float zLook = (float) Math.sin( (horzRotation)*Math.PI/180 );
float yLook = (float) Math.sin( (vertRotation)*Math.PI/180 );

// Because we are creating a 3-dimensional vector, the square of the cosine of the 3rd vector should equal
// the sum of the squares of the first 2 vectors, so we multiple the first 2 vectors by the cosine of the
// 3rd vector to ensure that the sum of the square of all 3 vector parts equal 1
float xzNormalize = (float) Math.cos( (vertRotation)*Math.PI/180 ); // The square of the x and z parts of the vector should equal the square of this cosine value
// If the camera angle is at +-90 than adjust it a very small amount back toward level to prevent the camera view from flipping over
if(xzNormalize < 0.001f) { xzNormalize = 0.001f; }
// Multiple the x and z part of the vector by the cosine value calculated to adjust/average them into the unit vector
float ratioXNormalize = xLook * xzNormalize;
float ratioZNormalize = zLook * xzNormalize;

float xEye = -(ratioXNormalize*panRadius)+xCenter;
float yEye = -(yLook*panRadius)+yCenter;
float zEye = -(ratioZNormalize*panRadius)+zCenter;
float xUp = 0;
float yUp = 1;
float zUp = 0;
float[] eye = new float[] {xEye, yEye, zEye};
float[] f = normalize(xCenter-xEye, yCenter-yEye, zCenter-zEye);
float[] u = normalize(xUp, yUp, zUp);
float[] s = normalize(cross(f, u));
u = cross(s, f);

float[] m = new float[16];
m[0+0] = s[0];
m[4+0] = s[1];
m[8+0] = s[2];
m[0+1] = u[0];
m[4+1] = u[1];
m[8+1] = u[2];
m[0+2] = -f[0];
m[4+2] = -f[1];
m[8+2] = -f[2];
m[12+0] = -dot(s, eye);
m[12+1] = -dot(u, eye);
m[12+2] = dot(f, eye);
m[15] = 1.0f;
return m;
}


public static float[] normalize(float x, float y, float z) {
float length = (float) Math.sqrt(x*x + y*y + z*z);
return new float[] {
x/length,
y/length,
z/length,
};
}


public static float[] normalize(float[] a) {
float length = (float) Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]);
return new float[] {
a[0]/length,
a[1]/length,
a[2]/length,
};
}


public static float[] cross(float[] a, float[] b) {
return new float[] {
a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0],
};
}


public static float dot(float[] a, float[] b) {
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
}


/** Main method for testing the example
*/
public static void main(String[] args) {
new GLSLExample();
}
}


Here is the Vertex Array Object class (VAO.java) that handles creating and deleting a simple triangle stored as an Open GL VAO.


import java.nio.ByteBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.glu.GLU;

public class VAO {
private int vaoId, vboId;


public int getId() {
return vaoId;
}


public void createVAO() {
float[] vertices = {
-0.8f, -0.8f, -2.0f,
0.0f, 0.8f, -2.0f,
0.8f, -0.8f, -2.0f,
};
byte[] colors = {
127, 0, 0, 127,
0, 127, 0, 127,
0, 0, 127, 127
};

ByteBuffer vertexData = BufferUtils.createByteBuffer(3 * 16);
for(int i = 0; i < 3; i++) {
vertexData.putFloat(vertices[3*i + 0]);
vertexData.putFloat(vertices[3*i + 1]);
vertexData.putFloat(vertices[3*i + 2]);
vertexData.put(colors, i*4, 4);
}
vertexData.rewind();

vaoId = GL30.glGenVertexArrays(); // Vertex/Attribute array to reference buffers
GL30.glBindVertexArray(vaoId);

vboId = GL15.glGenBuffers(); // Buffer to hold vertex/normal/color data
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexData, GL15.GL_STATIC_DRAW); // upload data to Video Card
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 16, 0); // Vertex attribute
GL20.glVertexAttribPointer(1, 4, GL11.GL_BYTE, true, 16, 12); // Color attribute
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);

GL30.glBindVertexArray(0);

int errorCheckValue = GL11.glGetError();
if(errorCheckValue != GL11.GL_NO_ERROR) {
System.out.println("ERROR: Could not create a VBO: " + GLU.gluErrorString(errorCheckValue) );
System.exit(0);
}
}


public void destroyVAO() {
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(0);

GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);

GL15.glDeleteBuffers(vboId);

GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);

int errorCheckValue = GL11.glGetError();
if(errorCheckValue != GL11.GL_NO_ERROR) {
System.out.println("ERROR: Could not destroy the VBO: " + GL11.glGetString(errorCheckValue) );
System.exit(0);
}
}

}


Here is the Shader class (Shader.java) for compiling and linking a shader's source (file, string, or reader) to an Open GL shader program.


import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

public class Shader {
public int shaderProgram;
public int vertexShader;
public int fragmentShader;


public Shader(int shaderProgram, int vertexShader, int fragmentShader) {
this.shaderProgram = shaderProgram;
this.vertexShader = vertexShader;
this.fragmentShader = fragmentShader;
}


public int getProgram() {
return shaderProgram;
}


public void setProgram(int program) {
this.shaderProgram = program;
}


public int getVertexShader() {
return vertexShader;
}


public void setVertexShader(int vertexShader) {
this.vertexShader = vertexShader;
}


public int getFragmentShader() {
return fragmentShader;
}


public void setFragmentShader(int fragmentShader) {
this.fragmentShader = fragmentShader;
}

/** delete, delete this shader program from the Open GL context
*/
public void delete() {
// When the user shuts down your program, you should deallocate all your GL resources.
// Unbind shader program
GL20.glUseProgram(0);
// Detach shaders
GL20.glDetachShader(shaderProgram, vertexShader);
GL20.glDetachShader(shaderProgram, fragmentShader);
// Delete the shaders
GL20.glDeleteShader(vertexShader);
GL20.glDeleteShader(fragmentShader);
// Delete the shader program
GL20.glDeleteProgram(shaderProgram);
}


/** loadShader, load the specified files as Open GL shaders
* @param vertexShaderFile - the vertex shader
* @param fragmentShaderFile - the fragment shader
* @throws IOException if there is an error reading the shader files
*/
public static Shader createShader(File vertexShaderFile, File fragmentShaderFile) throws IOException {
FileReader vertexShaderReader = new FileReader(vertexShaderFile);
FileReader fragmentShaderReader = new FileReader(fragmentShaderFile);
return createShader(vertexShaderReader, fragmentShaderReader);
}


/** loadShader, load the specified readers as Open GL shaders
* @param vertexShaderReader - the vertex shader
* @param fragmentShaderReader - the fragment shader
* @throws IOException if there is an error reading the shader readers
*/
public static Shader createShader(Reader vertexShaderReader, Reader fragmentShaderReader) throws IOException {
int shaderProgram = 0;
int vertexShader = 0;
int fragmentShader = 0;
int maxLength = 0;
String infoLog = null;
// Read the shaders into strings to send to the Video Card
String vertexSource = readReader(vertexShaderReader);
String fragmentSource = readReader(fragmentShaderReader);

// Create an empty vertex shader handle, send the vertex shader source code to Open GL and compile it
vertexShader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
GL20.glShaderSource(vertexShader, vertexSource);
GL20.glCompileShader(vertexShader);

// Check for errors uploading the source code or compiling the shader
int isCompiled_VS = GL20.glGetShader(vertexShader, GL20.GL_COMPILE_STATUS);
if(isCompiled_VS == GL11.GL_FALSE) {
maxLength = GL20.glGetShader(vertexShader, GL20.GL_INFO_LOG_LENGTH);
infoLog = GL20.glGetShaderInfoLog(vertexShader, maxLength);
throw new ExceptionInInitializerError("Vertex Shader Info Log: " + infoLog);
}

// Create an empty fragment shader handle, send the fragment shader source code to Open GL and compile it
fragmentShader = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
GL20.glShaderSource(fragmentShader, fragmentSource);
GL20.glCompileShader(fragmentShader);

// Check for errors uploading the source code or compiling the shader
int isCompiled_FS = GL20.glGetShader(fragmentShader, GL20.GL_COMPILE_STATUS);
if(isCompiled_FS == GL11.GL_FALSE) {
maxLength = GL20.glGetShader(fragmentShader, GL20.GL_INFO_LOG_LENGTH);
infoLog = GL20.glGetShaderInfoLog(fragmentShader, maxLength);
throw new ExceptionInInitializerError("Fragment Shader Info Log: " + infoLog);
}

// Create the shader program and attached the vertex and fragment shaders to it
shaderProgram = GL20.glCreateProgram();
GL20.glAttachShader(shaderProgram, vertexShader);
GL20.glAttachShader(shaderProgram, fragmentShader);

Shader shader = new Shader(shaderProgram, vertexShader, fragmentShader);
return shader;
}


/** linkShader, links and uses shader program
* @param shaderProgram - the Open GL integer of the shader program
*/
public static void linkUseShader(int shaderProgram) {
int maxLength = 0;
String shaderProgramInfoLog = null;
// Link our program
/* The vertex and fragment shaders are inspected, optimized and converted to binary code by the Video Card. */
GL20.glLinkProgram(shaderProgram);
GL20.glValidateProgram(shaderProgram);

/* Check and make sure that the program linked. If it fails, it would mean either there is a mismatch between the vertex */
/* and fragment shaders. Or the shaders may have surpassed the Video Card's abilities. Perhaps too many ALU operations or */
/* too many texel fetch instructions or too many interpolators or dynamic loops. */
int isLinked = GL20.glGetProgram(shaderProgram, GL20.GL_LINK_STATUS);
if(isLinked == GL11.GL_FALSE) {
// Noticed the glGetProgram and glGetProgramInfoLog calls, instead of glGetShader and glGetShaderInfoLog
maxLength = GL20.glGetProgram(shaderProgram, GL20.GL_INFO_LOG_LENGTH);
shaderProgramInfoLog = GL20.glGetProgramInfoLog(shaderProgram, maxLength);

// Handle the error in an appropriate way such as displaying a message or writing to a log file.
System.out.println("Shader Program Info Log: " + shaderProgramInfoLog);
throw new ExceptionInInitializerError("Shader Program Info Log: " + shaderProgramInfoLog);
}

// Load the shader into the rendering pipeline
// Remember to also call glUniform** to update shader uniforms
GL20.glUseProgram(shaderProgram);
}


/** readReader
* @param reader - the reader to read
* @return the contents of the reader
* @throws IOException if there is an error reading the reader
*/
private static String readReader(Reader reader) throws IOException {
BufferedReader buffer = new BufferedReader(reader);
StringBuilder contents = new StringBuilder();
String line = null;
while((line = buffer.readLine()) != null) {
contents.append(line + "\n");
}
buffer.close();
reader.close();
return contents.toString();
}

}