Skip to content

Instantly share code, notes, and snippets.

@WillWetzelCMS
Created October 6, 2018 22:48
Show Gist options
  • Save WillWetzelCMS/344f38894341072eabb0aba35697659d to your computer and use it in GitHub Desktop.
Save WillWetzelCMS/344f38894341072eabb0aba35697659d to your computer and use it in GitHub Desktop.
Some of the classes for my Software Rasteriser
/*
* Name: Will Wetzel - 130251255
* Project: OpenGLGraphics Coursework - Graphics for Games - Newcastle University
* Description: The following class utilitizes methods covered in the tutorials to use shaders to manipulate a 3D cube.
* This includes making the cube shrink and vanish, Blending a clean and a destroyed testure and increaseing
* the transparency of the cube so it fades away.
* This program also includes commands to: pause the animations or rotation, reset the animation, reload shaders
* and to split the cube into several smaller cubes.
* Main problem: Textures are loaded (png file), but they are not showing on the cube. When the cube splits, the
* details on the textures show. When two are faded together, the cube combines their colours.
*/
#include "Renderer.h"
#include "RenderObject.h"
#pragma comment(lib, "nclgl.lib")
#define NUM_SHADERS 5
Shader *graphicShader[NUM_SHADERS];
/*Delete array of shaders*/
void delete_shaders()
{
for (int i = 0; i < NUM_SHADERS; i++)
{
if (graphicShader[i] != NULL)
delete graphicShader[i];
graphicShader[i] = NULL;
}
}
/*Load all shaders for demo*/
void load_shaders()
{
for (int i = 0; i < NUM_SHADERS; i++) { //Check array is = NULL
graphicShader[i] = NULL;
}
//Add all shaders to the array for the program commands
graphicShader[0] = new Shader("BasicVert.glsl", "BasicFrag.glsl");
graphicShader[1] = new Shader("ShrinkVertex.glsl", "BasicFrag.glsl");
graphicShader[2] = new Shader("BasicVert.glsl", "TexfadeFragment.glsl");
graphicShader[3] = new Shader("BasicVert.glsl", "FadeFragment.glsl");
graphicShader[4] = new Shader("NomvpVertex.glsl", "BasicFrag.glsl", "SplitGeometry.glsl");
//For all the shaders, check if default shader is used. Tells the user if any shaders failed to load.
int failures = 0;
for (int i = 0; i < NUM_SHADERS; i++)
{
if (graphicShader[i] != NULL && graphicShader[i]->UsingDefaultShader())
{
failures++;
cout << "Shader " << i << " failed to load or compile." << endl;
}
}
if (failures == 0)
cout << "All shaders loaded and compiled successfully" << endl;
}
void main(void)
{
Window w = Window(800, 600);
Renderer r(w);
//Load the cube mesh and textures
Mesh *cubeMesh = Mesh::LoadMeshFile("cube.asciimesh");
GLuint cubeNormalTexture = r.LoadTexture("bricks.png");
GLuint cubeDestroyedTexture = r.LoadTexture("bricksDestroyed.png");
GLuint cubeHeightmap = r.LoadTexture("bricksHeightmap.png");
//Create cube render object
RenderObject cube(cubeMesh, graphicShader[0], cubeNormalTexture);
cube.SetTexture(1, cubeDestroyedTexture);
cube.SetTexture(2, cubeHeightmap);
//Load and compile the shaders
load_shaders();
cube.SetModelMatrix(Matrix4::Translation(Vector3(0.0, 0.0, -10.0)));
r.AddRenderObject(cube);
r.SetProjectionMatrix(Matrix4::Perspective(1, 100, 1.33f, 45.0f));
r.SetViewMatrix(Matrix4::BuildViewMatrix(Vector3(0, 0, 0), Vector3(0, 0, -10)));
cube.SetShader(graphicShader[0]);
//Print the list of commands
cout << "Commands: \n" << "r - Reset scene \n" << "p - Pause animation \n" << "shift + p - Pause rotation \n"
<< "0 - Reload and compile shaders \n" << "+ - Zoom in \n" << "- - Zoom out \n"
<< "s - Shrink the cube until it disappears \n" << "d - Fades form the normal texture to a destroyed texture \n"
<< "f - Fade the cube to transaparent \n" << "a - Split the cube into several smaller cubes" << endl;
bool rotate = true;
bool disableDepthDuringAnim = false;
while (w.UpdateWindow())
{
float msec = w.GetTimer()->GetTimedMS();
//Pause
if (Keyboard::KeyTriggered(KEY_P))
{
if (Keyboard::KeyDown(KEY_SHIFT))
rotate = !rotate;
else
r.animPause();
}
//Reload shaders
if (Keyboard::KeyTriggered(KEY_0))
{
delete_shaders();
load_shaders();
cube.SetShader(graphicShader[0]);
}
//Reset scene
if (Keyboard::KeyTriggered(KEY_R))
{
//Reset default values
r.SetLighting(1, Vector3(0.0f, 1000.0f, 0.0f), 0.0f, Vector3(0, 0, 0));
cubeMesh->type = GL_TRIANGLES;
glEnable(GL_DEPTH_TEST);
r.animStop();
cube.SetShader(graphicShader[0]);
}
//Zoom in
if (Keyboard::KeyTriggered(KEY_PLUS))
{
cube.SetModelMatrix(Matrix4::Translation(Vector3(0.0f, 0.0f, 0.5f)) * cube.GetModelMatrix());
}
//Zoom out
if (Keyboard::KeyTriggered(KEY_MINUS))
{
cube.SetModelMatrix(Matrix4::Translation(Vector3(0.0f, 0.0f, -0.5f)) * cube.GetModelMatrix());
}
//Shrink cube
if (Keyboard::KeyTriggered(KEY_S))
{
cube.SetShader(graphicShader[1]);
r.animStart();
}
//Fade to destroyed texture
if (Keyboard::KeyTriggered(KEY_D))
{
cube.SetShader(graphicShader[2]);
r.animStart();
}
//Fade cube to transparent
if (Keyboard::KeyTriggered(KEY_F))
{
disableDepthDuringAnim = true;
cube.SetShader(graphicShader[3]);
r.animStart();
}
//Split the cube into several smaller cubes
if (Keyboard::KeyTriggered(KEY_A))
{
cube.SetShader(graphicShader[4]);
r.animStart();
}
//Disable depth test part way through an animation, useful for fading to transparency
if (disableDepthDuringAnim && glIsEnabled(GL_DEPTH_TEST) && r.getAnimPosition() > 0.25)
{
glDisable(GL_DEPTH_TEST);
disableDepthDuringAnim = false;
}
//Rotate cube
if (rotate)
cube.SetModelMatrix(cube.GetModelMatrix() * Matrix4::Rotation(0.1f * msec, Vector3(0, 1, 1)));
r.UpdateScene(msec);
r.ClearBuffers();
r.RenderScene();
r.SwapBuffers();
}
}
#include "Mesh.h"
Mesh::Mesh(void)
{
// Most objects in OpenGL are represented as 'names' - an unsigned int
// index, really. They are always generated and destroyed by OpenGL
// functions. Most of these functions allow you to generate multiple
// names at once (the first parameter here is a count).
glGenVertexArrays(1, &arrayObject);
for (int i = 0; i < MAX_BUFFER; ++i)
{
bufferObject[i] = 0;
}
numVertices = 0;
type = GL_TRIANGLES;
// Later tutorial stuff
numIndices = 0;
vertices = NULL;
textureCoords = NULL;
normals = NULL;
tangents = NULL;
indices = NULL;
colours = NULL;
}
Mesh::~Mesh(void)
{
glDeleteVertexArrays(1, &arrayObject); // Delete our VAO
glDeleteBuffers(MAX_BUFFER, bufferObject); // Delete our VBOs
// Later tutorial stuff
delete[] vertices;
delete[] indices;
delete[] textureCoords;
delete[] tangents;
delete[] normals;
delete[] colours;
}
void Mesh::Draw()
{
/*
To render with a mesh in OpenGL, we need to bind all of the buffers
containing vertex data to the pipeline, and attach them to the
generic attributes referenced in our vertex shader.
That's quite a lot of code to set up, but fear not - all of the state
is cached within our Vertex Array Object, so once that's been set up
by the BufferData function, all we need to do is bind the VAO and it
all happens automagically
*/
glBindVertexArray(arrayObject);
// There are two draw functions in OpenGL, depending on whether we're
// using indices or not. Both start off taking a primitive type -
// triangles, quads, lines, points etc.
if (bufferObject[INDEX_BUFFER])
{
/*
If we have an index buffer, we tell OpenGL how to parse that
buffer data (is it bytes/ints/shorts), and how many data
elements there are. The last parameter should always be 0,
it's part of the old OpenGL spec.
*/
glDrawElements(type, numIndices, GL_UNSIGNED_INT, 0);
}
else
{
/*
If we don't have indices, we can just use this function.
Its extra parameters define which is the first vertex
to draw, and how many vertices past this point to draw.
*/
glDrawArrays(type, 0, numVertices); // Draw the triangle!
}
/*
We don't strictly have to do this, but 'undoing' whatever
we do to OpenGL in a function generally keeps the pipeline
from getting incorrect states, or otherwise not doing what
you want it to do.
*/
glBindVertexArray(0);
}
Mesh *Mesh::GenerateTriangle()
{
Mesh *m = new Mesh();
m->numVertices = 3;
m->vertices = new Vector3[m->numVertices];
m->vertices[0] = Vector3(0.0f, 0.5f, 0.0f);
m->vertices[1] = Vector3(0.5f, -0.5f, 0.0f);
m->vertices[2] = Vector3(-0.5f, -0.5f, 0.0f);
m->textureCoords = new Vector2[m->numVertices];
m->textureCoords[0] = Vector2(0.5f, 0.0f);
m->textureCoords[1] = Vector2(1.0f, 1.0f);
m->textureCoords[2] = Vector2(0.0f, 1.0f);
m->colours = new Vector4[m->numVertices];
m->colours[0] = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
m->colours[1] = Vector4(0.0f, 1.0f, 0.0f, 1.0f);
m->colours[2] = Vector4(0.0f, 0.0f, 1.0f, 1.0f);
m->BufferData();
m->GenerateNormals();
return m;
}
Mesh *Mesh::GenerateLine(const Vector3 &from, const Vector3 &to)
{
Mesh *m = new Mesh();
m->type = GL_LINES;
m->numVertices = 2;
m->vertices = new Vector3[m->numVertices];
m->vertices[0] = from;
m->vertices[1] = to;
m->textureCoords = new Vector2[m->numVertices];
m->textureCoords[0] = Vector2(1.0f, 1.0f);
m->textureCoords[1] = Vector2(0.0f, 1.0f);
m->colours = new Vector4[m->numVertices];
m->colours[0] = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
m->colours[1] = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
m->BufferData();
return m;
}
Mesh *Mesh::LoadMeshFile(const string &filename)
{
ifstream f(filename);
if (!f)
{
return NULL;
}
Mesh *m = new Mesh();
m->type = GL_TRIANGLES;
f >> m->numVertices;
int hasTex = 0;
int hasColour = 0;
f >> hasTex;
f >> hasColour;
m->vertices = new Vector3[m->numVertices];
if (hasTex)
{
m->textureCoords = new Vector2[m->numVertices];
m->colours = new Vector4[m->numVertices];
}
for (unsigned int i = 0; i < m->numVertices; ++i)
{
f >> m->vertices[i].x;
f >> m->vertices[i].y;
f >> m->vertices[i].z;
}
if (hasColour)
{
for (unsigned int i = 0; i < m->numVertices; ++i)
{
unsigned int r, g, b, a;
f >> r;
f >> g;
f >> b;
f >> a;
// OpenGL can use floats for colours directly - this will take up 4x as
// much space, but could avoid any byte / float conversions happening
// behind the scenes in our shader executions
m->colours[i] = Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
}
if (hasTex)
{
for (unsigned int i = 0; i < m->numVertices; ++i)
{
f >> m->textureCoords[i].x;
f >> m->textureCoords[i].y;
}
}
m->GenerateNormals();
m->BufferData();
return m;
}
void Mesh::BufferData()
{
/*
To more efficiently bind and unbind the states required to draw a mesh,
we can encapsulate them all inside a Vertex Array Object.
When a VAO is bound, all further changes to vertex buffers and vertex
attributes are cached inside the VAO, and will be reapplied whenever
that VAO is later bound again
*/
glBindVertexArray(arrayObject);
/*
To put some vertex data on the GPU, we must create a buffer object to store it.
To upload data to a vertex buffer, it must be bound, with a specific 'target',
which defines what the buffer is to be used for.
Then, once bound, data can be uploaded using the glBufferData function, which
takes a pointer to the data to be sent, and the size of that data, as well as
a usage parameter - this lets OpenGL know how the data will be accessed.
To turn a vertex buffer into a vertex attribute suitable for sending to a
vertex shader, the glVertexAttribPointer function must be called.
This takes in an attribute 'slot', how many elements each data entry has
(i.e 2 for a vector2 etc), and what datatype (usually float) it is.
This will bind the currently bound vertex buffer to that attribute slot.
To actually enable that attribute slot, the glEnableVertexAttribArray is called.
Note that we use the value VERTEX_BUFFER for the 'slot' parameter - this is just
an enum value, that equates to 0. It's common to use enums as indices into arrays
in this way, as it keeps everything consistent. It's also pretty sensible to always
bind the same data types to the same attribute slots, it makes life much easier!
These last two functions, along with the glBindBuffer call, are examples of
functionality that is cached in the actual VAO.
*/
// Buffer vertex data
glGenBuffers(1, &bufferObject[VERTEX_BUFFER]);
glBindBuffer(GL_ARRAY_BUFFER, bufferObject[VERTEX_BUFFER]);
glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vector3), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(VERTEX_BUFFER, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(VERTEX_BUFFER);
////Buffer texture data
if (textureCoords)
{
glGenBuffers(1, &bufferObject[TEXTURE_BUFFER]);
glBindBuffer(GL_ARRAY_BUFFER, bufferObject[TEXTURE_BUFFER]);
glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vector2), textureCoords, GL_STATIC_DRAW);
glVertexAttribPointer(TEXTURE_BUFFER, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(TEXTURE_BUFFER);
}
// buffer colour data
if (colours)
{
glGenBuffers(1, &bufferObject[COLOUR_BUFFER]);
glBindBuffer(GL_ARRAY_BUFFER, bufferObject[COLOUR_BUFFER]);
glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vector4), colours, GL_STATIC_DRAW);
glVertexAttribPointer(COLOUR_BUFFER, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(COLOUR_BUFFER);
}
// buffer index data
if (indices)
{
glGenBuffers(1, &bufferObject[INDEX_BUFFER]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObject[INDEX_BUFFER]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(GLuint), indices, GL_STATIC_DRAW);
}
if (normals)
{
glGenBuffers(1, &bufferObject[NORMAL_BUFFER]);
glBindBuffer(GL_ARRAY_BUFFER, bufferObject[NORMAL_BUFFER]);
glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vector3), normals, GL_STATIC_DRAW);
glVertexAttribPointer(NORMAL_BUFFER, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(NORMAL_BUFFER);
}
// Once we're done with the vertex buffer binding, we can unbind the VAO,
// ready to reapply later, such as in the Draw function above!
glBindVertexArray(0);
}
void Mesh::GenerateNormals()
{
if (!normals)
normals = new Vector3[numVertices];
for (unsigned int i = 0; i < numVertices; i += 3)
{
Vector3 &a = vertices[i];
Vector3 &b = vertices[i + 1];
Vector3 &c = vertices[i + 2];
Vector3 normal = Vector3::Cross(b - a, c - a);
normal.Normalise();
normals[i] = normal;
normals[i + 1] = normal;
normals[i + 2] = normal;
}
}
/******************************************************************************
Class:Mesh
Implements:
Author:Rich Davison <[email protected]>
Description:Wrapper around OpenGL primitives, geometry and related
OGL functions.
There's a couple of extra functions in here that you didn't get in the tutorial
series, to draw debug normals and tangents.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "../nclgl/OGLRenderer.h"
#include <vector>
#include <string>
#include <fstream>
using std::ifstream;
using std::string;
// A handy enumerator, to determine which member of the bufferObject array
// holds which data
enum MeshBuffer
{
VERTEX_BUFFER = 0,
COLOUR_BUFFER = 1,
TEXTURE_BUFFER,
NORMAL_BUFFER,
TANGENT_BUFFER,
INDEX_BUFFER,
MAX_BUFFER
};
class Mesh
{
public:
friend class MD5Mesh;
Mesh(void);
virtual ~Mesh(void);
virtual void Draw();
void GenerateNormals();
// Generates a single triangle, with RGB colours
static Mesh *GenerateTriangle();
static Mesh *GenerateLine(const Vector3 &from, const Vector3 &to);
static Mesh *LoadMeshFile(const string &filename);
GLuint type; // Primitive type for this mesh (GL_TRIANGLES...etc)
protected:
// Buffers all VBO data into graphics memory. Required before drawing!
void BufferData();
// VAO for this mesh
GLuint arrayObject;
// VBOs for this mesh
GLuint bufferObject[MAX_BUFFER];
// Number of vertices for this mesh
GLuint numVertices;
// Number of indices for this mesh
GLuint numIndices;
// Pointer to vertex position attribute data (badly named...?)
Vector3 *vertices;
// Pointer to vertex colour attribute data
Vector4 *colours;
// Pointer to vertex texture coordinate attribute data
Vector2 *textureCoords;
// Pointer to vertex normals attribute data
Vector3 *normals;
// Pointer to vertex tangents attribute data
Vector3 *tangents;
// Pointer to vertex indices attribute data
unsigned int *indices;
};
#include "Renderer.h"
Renderer::Renderer(Window &parent)
: OGLRenderer(parent)
, m_time(0.0f)
, m_animPosition(0.0f)
, m_animDelta(0.00025)
, m_runAnim(false)
{
glEnable(GL_DEPTH_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
Renderer::~Renderer(void)
{
m_renderObjects.clear();
}
void Renderer::RenderScene()
{
for (vector<RenderObject *>::iterator i = m_renderObjects.begin(); i != m_renderObjects.end();
++i)
{
Render(*(*i));
}
}
void Renderer::Render(const RenderObject &o)
{
modelMatrix = o.GetWorldTransform();
if (o.GetShader() && o.GetMesh())
{
GLuint program = o.GetShader()->GetShaderProgram();
glUseProgram(program);
UpdateShaderMatrices(program);
ApplyShaderLight(program);
Matrix3 rotation = Matrix3(viewMatrix);
Vector3 invCamPos = viewMatrix.GetPositionVector();
Vector3 camPos = rotation * -invCamPos;
glUniform3fv(glGetUniformLocation(program, "cameraPos"), 1, (float *)&camPos);
glUniform1f(glGetUniformLocation(program, "animPosition"), m_animPosition);
glUniform1i(glGetUniformLocation(program, "objectTextures[0]"), 0);
glUniform1i(glGetUniformLocation(program, "objectTextures[1]"), 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, o.GetTexture(0));
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, o.GetTexture(1));
o.Draw();
}
for (vector<RenderObject *>::const_iterator i = o.GetChildren().begin();
i != o.GetChildren().end(); ++i)
{
Render(*(*i));
}
}
void Renderer::UpdateScene(float msec)
{
if (m_runAnim)
{
if (m_animPosition < 1.0)
{
float animDelta = msec * m_animDelta;
m_animPosition += animDelta;
}
else
{
animStop();
}
}
m_time += msec;
for (vector<RenderObject *>::iterator i = m_renderObjects.begin(); i != m_renderObjects.end();
++i)
{
(*i)->Update(msec);
}
}
void Renderer::animStart()
{
m_animPosition = 0.0f;
m_runAnim = true;
}
void Renderer::animPause()
{
if (m_animPosition < m_animDelta || m_animPosition > 1.0 - m_animDelta)
return;
m_runAnim = !m_runAnim;
}
void Renderer::animStop()
{
m_runAnim = false;
}
GLuint Renderer::LoadTexture(string filename)
{
return SOIL_load_OGL_texture(filename.c_str(), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID,
SOIL_FLAG_MIPMAPS);
}
void Renderer::SetLighting(int i, const Vector3 &position, float radius, const Vector3 &colour)
{
m_light[i].position = position;
m_light[i].radius = radius;
m_light[i].colour = colour;
}
void Renderer::ApplyShaderLight(GLuint program)
{
glUniform3fv(glGetUniformLocation(program, "lightPos[0]"), 1, (float *)&(m_light[0].position));
glUniform1f(glGetUniformLocation(program, "lightRadius[0]"), m_light[0].radius);
glUniform3fv(glGetUniformLocation(program, "lightColour[0]"), 1, (float *)&(m_light[0].colour));
glUniform3fv(glGetUniformLocation(program, "lightPos[1]"), 1, (float *)&(m_light[1].position));
glUniform1f(glGetUniformLocation(program, "lightRadius[1]"), m_light[1].radius);
glUniform3fv(glGetUniformLocation(program, "lightColour[1]"), 1, (float *)&(m_light[1].colour));
}
#pragma once
#include "../nclgl/OGLRenderer.h"
#include "RenderObject.h"
#include <vector>
using std::vector;
struct Light
{
Vector3 position;
float radius;
Vector3 colour;
};
class Renderer : public OGLRenderer
{
public:
static const int NUM_TEXTURES = 5;
static const int NUM_LIGHTS = 2;
Renderer(Window &parent);
~Renderer(void);
virtual void RenderScene();
virtual void Render(const RenderObject &o);
virtual void UpdateScene(float msec);
void AddRenderObject(RenderObject &r)
{
m_renderObjects.push_back(&r);
}
void animStart();
void animPause();
void animStop();
float getAnimPosition()
{
return m_animPosition;
}
GLuint LoadTexture(string filename);
void SetLighting(int i, const Vector3 &position, float radius, const Vector3 &colour);
protected:
void ApplyShaderLight(GLuint program);
float m_time;
float m_animPosition;
float m_animDelta;
bool m_runAnim;
vector<RenderObject *> m_renderObjects;
Light m_light[NUM_LIGHTS];
};
#include "RenderObject.h"
RenderObject::RenderObject(void)
{
for (int i = 0; i < NUM_TEXTURES; i++)
textures[i] = NULL;
mesh = NULL;
shader = NULL;
parent = NULL;
}
RenderObject::RenderObject(Mesh *m, Shader *s, GLuint t)
{
for (int i = 0; i < NUM_TEXTURES; i++)
textures[i] = NULL;
mesh = m;
shader = s;
textures[0] = t;
parent = NULL;
}
RenderObject::~RenderObject(void)
{
if (mesh != NULL)
delete mesh;
if (shader != NULL)
delete shader;
if (parent != NULL)
delete parent;
}
void RenderObject::Update(float msec)
{
if (parent)
{
worldTransform = parent->modelMatrix * modelMatrix;
// worldTransform = modelMatrix * parent->modelMatrix;
}
else
{
worldTransform = modelMatrix;
}
for (vector<RenderObject *>::const_iterator i = children.begin(); i != children.end(); ++i)
{
(*i)->Update(msec);
}
}
void RenderObject::Draw() const
{
if (mesh)
{
mesh->Draw();
}
}
#pragma once
#include "../nclgl/Matrix4.h"
#include "Mesh.h"
#include "Shader.h"
#include "../nclgl/OGLRenderer.h"
class RenderObject
{
public:
static const int NUM_TEXTURES = 2;
RenderObject(void);
RenderObject(Mesh *m, Shader *s, GLuint t = 0);
~RenderObject(void);
Mesh *GetMesh() const
{
return mesh;
}
void SetMesh(Mesh *m)
{
mesh = m;
}
Shader *GetShader() const
{
return shader;
}
void SetShader(Shader *s)
{
shader = s;
}
GLuint GetTexture(int i) const
{
return textures[i];
}
void SetTexture(int i, GLuint tex)
{
textures[i] = tex;
}
void SetModelMatrix(Matrix4 mat)
{
modelMatrix = mat;
}
Matrix4 GetModelMatrix() const
{
return modelMatrix;
}
virtual void Update(float msec);
virtual void Draw() const;
void AddChild(RenderObject &child)
{
children.push_back(&child);
child.parent = this;
}
Matrix4 GetWorldTransform() const
{
return worldTransform;
}
const vector<RenderObject *> &GetChildren() const
{
return children;
}
protected:
Mesh *mesh;
Shader *shader;
GLuint textures[NUM_TEXTURES];
Matrix4 modelMatrix;
Matrix4 worldTransform;
RenderObject *parent;
vector<RenderObject *> children;
};
/*
* Name: Will Wetzel - 130251255
* Project: OpenGLGraphics Coursework - Graphics for Games - Newcastle University
* Description: Shader class provided to us, and added to in the tutorials.
*
*/
#include "Shader.h"
#include "Mesh.h"
/*
It's nice to have a robust program, that can handle things going wrong in them!
To enhance the robustness of your coursework, I've added some functionality not
really discussed in the tutorial notes. If one of your shader programs fails to
compile for some reason, it will revert to using a default vertex and fragment
program, that transforms the cube correctly, but makes it bright pink, to make
it obvious that something's gone wrong! You can test in your code whether a
shader has failed in such a way using the UsingDefaultShader function.
If you really want, you can disable this by commenting out the define below.
*/
#define BACKUP_SHADER
#ifdef BACKUP_SHADER
const string defaultVertex =
"#version 150 core\n"
"uniform mat4 modelMatrix;"
"uniform mat4 viewMatrix; "
"uniform mat4 projMatrix; "
"in vec3 position;"
"in vec2 texCoord;"
"in vec4 colour; "
"out Vertex{ "
" vec2 texCoord; "
" vec4 colour; "
"} OUT; "
"void main(void) {"
" gl_Position = (projMatrix * viewMatrix * modelMatrix) * vec4(position, 1.0);"
" OUT.texCoord = texCoord;"
" OUT.colour = colour;"
"}";
const string defaultFragment = "#version 150 core\n"
"in Vertex{"
" vec2 texCoord;"
" vec4 colour;"
"} IN;"
"out vec4 fragColor;"
"void main(void) {"
" fragColor = vec4(1,0,1,1);"
"}";
#endif
/*
OpenGL shader programs can have many different combinations of
vertex/fragment/geometry/tcs/tes, so I've made a combined constructor that can
handle this. Just use "" as the parameter for any shader stage you aren't using,
as the code checks for non-empty strings. As you must always have a vertex and
fragment shader stage, these come first, while the others are defaulted to "".
Unlike most other OpenGL objects, you can't create multiple programs at once,
you just receive a program name from the glCreateProgram function - I'm not
sure why they did this!
I've done a trick both in here and in the Mesh class involving how enums are
created - by default a new enum will be 1 larger than the previous entry. This
means that if you start your first enum entry with a value of 0, the last entry
will be a value suitable for using as an array size to keep all of the previous
entries. I use this to create a compile-time array large enough to store all of
the possible shader objects we might need, using an easy-to-read variable name.
*/
Shader::Shader(string vFile, string fFile, string gFile, string tcsFile, string tesFile)
{
linkSuccess = true;
loadSuccess = true;
usingBackupShader = false;
program = glCreateProgram();
objects[SHADER_VERTEX] = GenerateShader(vFile, GL_VERTEX_SHADER);
objects[SHADER_FRAGMENT] = GenerateShader(fFile, GL_FRAGMENT_SHADER);
objects[SHADER_GEOMETRY] = 0;
objects[SHADER_TCS] = 0;
objects[SHADER_TES] = 0;
if (!gFile.empty())
{
objects[SHADER_GEOMETRY] = GenerateShader(gFile, GL_GEOMETRY_SHADER);
}
if (!tcsFile.empty())
{
objects[SHADER_TCS] = GenerateShader(tcsFile, GL_TESS_CONTROL_SHADER);
}
if (!tesFile.empty())
{
objects[SHADER_TES] = GenerateShader(tesFile, GL_TESS_EVALUATION_SHADER);
}
for (int i = 0; i < SHADER_MAX; ++i)
{
if (objects[i])
{
glAttachShader(program, objects[i]);
}
}
SetDefaultAttributes();
linkSuccess = LinkProgram();
#ifdef BACKUP_SHADER
if (!linkSuccess || !loadSuccess)
{
LoadDefaultShader();
}
#endif
}
/*
It's good practice to destruct all of your OpenGL objects. For your
projects it probably doesn't matter (all objects will be destroyed when
the parent OpenGL context is destroyed), but its conceivable that you
could have some system that deletes and constructs shaders at runtime
(such as a button to reload all your shaders - this can be a time saver!)
*/
Shader::~Shader(void)
{
if (program)
{
for (int i = 0; i < SHADER_MAX; ++i)
{
if (objects[i])
{
glDetachShader(program, objects[i]);
glDeleteShader(objects[i]);
}
}
glDeleteProgram(program);
}
}
/*
Basic text file loading. Should be pretty efficient as the text
buffer will only be made once, and never resized.
*/
bool Shader::LoadShaderFile(string from, string &into)
{
ifstream file;
file.open(from);
if (!file)
{
cout << "File does not exist!" << endl;
return false;
}
file.seekg(0, std::ios::end);
into.resize(1 + (unsigned int)file.tellg());
file.seekg(0, std::ios::beg);
file.read(&into[0], into.size());
into[into.size() - 1] = '\n';
file.close();
cout << "Loaded shader text!" << endl << endl;
return true;
}
/*
The shader files you create are just text - they must be compiled into
machine code compatible with your graphics card, just as with your C++ code.
All shader objects are of a specific 'type' (vertex/fragment/geometry etc),
so when you create a shader object using glCreateShader, you must set this.
One you have loaded a shader file, you can tell OpenGL about it using the
glShaderSource function - you can send multiple text files at once using this
function, so you could create your own #include system by parsing your text
files. Once you've sent the text to OpenGL, you can tell it to turn it into
a shader object using glCompileShader. If your driver fails to compile the
code, you can query information on why its failed using the 'info log', which
will return a string telling you about what warnings and errors it found.
*/
GLuint Shader::GenerateShader(string from, GLenum type)
{
cout << "Compiling Shader " << from << endl;
string load;
if (!LoadShaderFile(from, load))
{
cout << "Compiling failed! File not found!" << endl;
loadSuccess = false;
return 0;
}
GLuint shader = glCreateShader(type);
const char *chars = load.c_str();
glShaderSource(shader, 1, &chars, NULL);
glCompileShader(shader);
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
cout << "Compiling failed! Error log as follows:" << endl;
char error[2048];
glGetInfoLogARB(shader, sizeof(error), NULL, error);
cout << error << endl;
loadSuccess = false;
return false;
}
cout << "Compiling success!" << endl << endl;
return shader;
}
/*
In order to create a finished working shader program, you must 'link' all of
your shader objects together - this makes sure the outputs of one stage connect
to the inputs of the next stage. In case this linking stage fails, it is possible
to get an 'info log' from your OpenGL driver, which should (hopefully) inform you
of why.
*/
bool Shader::LinkProgram()
{
if (!loadSuccess)
{
cout << "Can't link program, one or more shader objects have failed!" << endl;
return false;
}
glLinkProgram(program);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
cout << "Linking failed! Error log as follows:" << endl;
char error[2048];
glGetProgramInfoLog(program, sizeof(error), NULL, error);
cout << error << endl;
return false;
}
return true;
}
/*
When a vertex buffer is bound to the pipeline, it is attached to a
specific generic 'attribute', numbered between 0 and whatever the max
your driver implementation can do (generally this is at least 15).
Your shader needs to know how to interpret these generic attributes, and it
can do this in one of two ways - either 'layout qualifiers' in the vertex
shader itself, or by using the glBindAttribLocation function. For basic vertex
data that is used across lots of shaders, I prefer the latter method, as it acts
as a generic interface that any shader will work with as long as it uses the same
variable names, without having to really care about how the data is sent to it.
The glBindAttribLocation takes 3 parameters, a shader program, a generic attribute
index, and a string variable name, which will be an 'in' value in the vertex shader.
As you can see, I've matched up these generic attribute indices to the mesh class vertex
buffers, so the connecting of vertex buffers to any shader loaded via my class happens
'automagically'. It's perfectly safe to attempt to bind a non-existant input variable,
so it's generally a good idea to just attempt to bind all of your commonly used vertex
attributes
*/
void Shader::SetDefaultAttributes()
{
glBindAttribLocation(program, VERTEX_BUFFER, "position");
glBindAttribLocation(program, COLOUR_BUFFER, "colour");
glBindAttribLocation(program, NORMAL_BUFFER, "normal");
glBindAttribLocation(program, TANGENT_BUFFER, "tangent");
glBindAttribLocation(program, TEXTURE_BUFFER, "texCoord");
}
/*
If your shader all goes wrong, this function will be called, to create the
emergency 'default' shader. It goes through all of the stages outlined in
the tutorial notes, but instead of reading from a file, it uses some const
strings I defined at the top
*/
void Shader::LoadDefaultShader()
{
for (int i = 0; i < SHADER_MAX; ++i)
{
if (objects[i])
{
glDetachShader(program, objects[i]);
glDeleteShader(objects[i]);
objects[i] = 0;
}
}
loadSuccess = true;
linkSuccess = true;
usingBackupShader = true;
const char *vertchars = defaultVertex.c_str();
const char *fragchars = defaultFragment.c_str();
objects[SHADER_VERTEX] = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(objects[SHADER_VERTEX], 1, &vertchars, NULL);
glCompileShader(objects[SHADER_VERTEX]);
objects[SHADER_FRAGMENT] = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(objects[SHADER_FRAGMENT], 1, &fragchars, NULL);
glCompileShader(objects[SHADER_FRAGMENT]);
glAttachShader(program, objects[SHADER_VERTEX]);
glAttachShader(program, objects[SHADER_FRAGMENT]);
SetDefaultAttributes();
linkSuccess = LinkProgram();
cout << "Your shader program has failed! Reverting to default shader..." << endl;
}
/******************************************************************************
Class:Shader
Implements:
Author:Rich Davison <[email protected]>
Description:VERY simple class to encapsulate GLSL shader loading, linking,
and binding. Useful additions to this class would be overloaded functions to
replace the glUniformxx functions in external code, and possibly a map to store
uniform names and their resulting bindings.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include <string>
#include <iostream>
#include <fstream>
#include "GL/glew.h"
enum ShaderStage
{
SHADER_VERTEX = 0,
SHADER_FRAGMENT,
SHADER_GEOMETRY,
SHADER_TCS,
SHADER_TES,
SHADER_MAX
};
using namespace std;
class Shader
{
public:
Shader(string vertex, string fragment, string geometry = "", string tcs = "", string tes = "");
~Shader(void);
GLuint GetShaderProgram() const
{
return program;
}
bool ShaderLinked() const
{
return linkSuccess;
}
bool LinkProgram();
bool UsingDefaultShader() const
{
return usingBackupShader;
}
protected:
bool LoadShaderFile(string from, string &into);
GLuint GenerateShader(string from, GLenum type);
void SetDefaultAttributes();
void LoadDefaultShader();
GLuint objects[SHADER_MAX];
GLuint program;
bool loadSuccess;
bool linkSuccess;
bool usingBackupShader;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment