Next code are a complete example to draw 2 cubes using OpenGL and Linux Debian 9. Also, has a class to see pressed buton and positions if click the mouse. All sources and Makefile are included.
- To compile: copy all files with correct name in same directory and type make
- To execute: type ./start
All needed libraries are installed with next commands:
apt-get install libglew-dev
apt-get install freeglut3-dev
apt-get install libglm-dev
The example are created from multiple examples from Internet and changed to Object Oriented code to clarify.
I hope it's useful.
File: cube.hpp
#ifndef CUBE_H
#define CUBE_H
#include <stdio.h>
#include <stdlib.h>
#include <glm/gtc/type_ptr.hpp>
#include <GL/glew.h>
#include <GL/glut.h>
class Cube{
private:
GLuint vboCubeVertex, vboCubeColors, vboCubeFaces;
GLint attribute_coord3d, attribute_v_color;
public:
int init_resources(GLuint shaderProgram);
void draw(GLint uniform_mvp, glm::mat4 mvp);
void disable();
void deleteBuffers();
};
#endif
File: cube.cpp
#include "cube.hpp"
int Cube::init_resources(GLuint shaderProgram)
{
fprintf(stderr, "init_resources\n");
//**********************************************************
// VERTEX **************************************************
//**********************************************************
// Each vertex has the format x,y,z
GLfloat arrCubeVertex[] = {
// front
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// back
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
-1.0, 1.0, -1.0,
};
//**********************************************************
// COLORS OF EACH VERTEX ***********************************
//**********************************************************
GLfloat arrCubeColors[] = {
// front colors
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 1.0,
// back colors
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 1.0,
};
// Faces are squares, each square are maked wit two triangles.
// See document OpenGL_Charly.pdf.
GLushort arrCubeFaces[] = {
// front
0, 1, 2,
2, 3, 0,
// right
1, 5, 6,
6, 2, 1,
// back
7, 6, 5,
5, 4, 7,
// left
4, 0, 3,
3, 7, 4,
// bottom
4, 5, 1,
1, 0, 4,
// up
3, 2, 6,
6, 7, 3,
};
glGenBuffers(1, &vboCubeVertex);
glBindBuffer(GL_ARRAY_BUFFER, vboCubeVertex);
glBufferData(GL_ARRAY_BUFFER, sizeof(arrCubeVertex), arrCubeVertex, GL_STATIC_DRAW);
// Las mismas operaciones que hicimos con los vertices, las hacemos ahora con los colores.
glGenBuffers(1, &vboCubeColors);
glBindBuffer(GL_ARRAY_BUFFER, vboCubeColors);
glBufferData(GL_ARRAY_BUFFER, sizeof(arrCubeColors), arrCubeColors, GL_STATIC_DRAW);
// Ahora lo mismo pero con las caras.
glGenBuffers(1, &vboCubeFaces);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboCubeFaces);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(arrCubeFaces), arrCubeFaces, GL_STATIC_DRAW);
//**********************************************************
attribute_coord3d = glGetAttribLocation(shaderProgram, "coord3d");
if (attribute_coord3d == -1)
{
fprintf(stderr, "Could not bind attribute %s\n", "coord3d");
return 0;
}
attribute_v_color = glGetAttribLocation(shaderProgram, "v_color");
if (attribute_v_color == -1)
{
fprintf(stderr, "Could not bind attribute %s\n", "v_color");
return 0;
}
return 1;
}
void Cube::draw(GLint uniform_mvp, glm::mat4 mvp){
glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
// Cube vertices
glEnableVertexAttribArray(attribute_coord3d);
glBindBuffer(GL_ARRAY_BUFFER, vboCubeVertex);
// Attribute, elements per vertex (x,y,z), the type of each element, take our values as-is, no extra data between each position, offset of first element
glVertexAttribPointer(attribute_coord3d, 3, GL_FLOAT, GL_FALSE, 0, 0);
// Cube colors
glEnableVertexAttribArray(attribute_v_color);
glBindBuffer(GL_ARRAY_BUFFER, vboCubeColors);
// Attribute, elements per vertex (R,G,B), the type of each element, take our values as-is, no extra data between each position, offset of first element
glVertexAttribPointer(attribute_v_color, 3, GL_FLOAT, GL_FALSE, 0, 0);
/* Push each element in buffer_vertices to the vertex shader */
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboCubeFaces);
int size;
glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
}
void Cube::disable(){
glDisableVertexAttribArray(attribute_coord3d);
glDisableVertexAttribArray(attribute_v_color);
}
void Cube::deleteBuffers(){
glDeleteBuffers(1, &vboCubeVertex);
glDeleteBuffers(1, &vboCubeColors);
glDeleteBuffers(1, &vboCubeFaces);
}
File: mouse.hpp
#ifndef MOUSE_H
#define MOUSE_H
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
struct Point {
GLint x;
GLint y;
};
class Mouse{
private:
Point p1, p2;
public:
int show(int button, int state, int x, int y);
};
#endif
File: mouse.cpp
#include "mouse.hpp"
int Mouse::show(int button, int state, int x, int y)
{
if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
printf("%s\n", "LEFT DOWN");
}
else if(button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
printf("%s\n", "LEFT UP");
}
printf("mouse X: %d, Y: %d\n", x, y);
}
File: shader_utils.hpp
#ifndef _CREATE_SHADER_H
#define _CREATE_SHADER_H
#include <GL/glew.h>
char* file_read(const char* filename);
void print_log(GLuint object);
GLuint create_shader(const char* filename, GLenum type);
GLuint create_program(const char* vertexfile, const char *fragmentfile);
GLuint create_gs_program(const char* vertexfile, const char *geometryfile, const char *fragmentfile, GLint input, GLint output, GLint vertices);
GLint get_attrib(GLuint program, const char *name);
GLint get_uniform(GLuint program, const char *name);
#endif
File: shader_utils.cpp
#include <stdio.h>
#include <stdlib.h>
#include "shader_utils.hpp"
char* file_read(const char* filename)
{
FILE* in = fopen(filename, "rb");
if (in == NULL) return NULL;
int res_size = BUFSIZ;
char* res = (char*)malloc(res_size);
int nb_read_total = 0;
while (!feof(in) && !ferror(in)) {
if (nb_read_total + BUFSIZ > res_size) {
if (res_size > 10*1024*1024) break;
res_size = res_size * 2;
res = (char*)realloc(res, res_size);
}
char* p_res = res + nb_read_total;
nb_read_total += fread(p_res, 1, BUFSIZ, in);
}
fclose(in);
res = (char*)realloc(res, nb_read_total + 1);
res[nb_read_total] = '\0';
return res;
}
void print_log(GLuint object)
{
GLint log_length = 0;
if (glIsShader(object))
glGetShaderiv(object, GL_INFO_LOG_LENGTH, &log_length);
else if (glIsProgram(object))
glGetProgramiv(object, GL_INFO_LOG_LENGTH, &log_length);
else {
fprintf(stderr, "printlog: Not a shader or a program\n");
return;
}
char* log = (char*)malloc(log_length);
if (glIsShader(object))
glGetShaderInfoLog(object, log_length, NULL, log);
else if (glIsProgram(object))
glGetProgramInfoLog(object, log_length, NULL, log);
fprintf(stderr, "%s", log);
free(log);
}
GLuint create_shader(const char* filename, GLenum type)
{
const GLchar* source = file_read(filename);
if (source == NULL) {
fprintf(stderr, "Error opening %s: ", filename); perror("");
return 0;
}
GLuint res = glCreateShader(type);
const GLchar* sources[] = {
// Define GLSL version
#ifdef GL_ES_VERSION_2_0
"#version 100\n"
#else
"#version 120\n"
#endif
,
// GLES2 precision specifiers
#ifdef GL_ES_VERSION_2_0
// Define default float precision for fragment shaders:
(type == GL_FRAGMENT_SHADER) ?
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
"precision highp float; \n"
"#else \n"
"precision mediump float; \n"
"#endif \n"
: ""
// Note: OpenGL ES automatically defines this:
// #define GL_ES
#else
// Ignore GLES 2 precision specifiers:
"#define lowp \n"
"#define mediump\n"
"#define highp \n"
#endif
,
source };
glShaderSource(res, 3, sources, NULL);
free((void*)source);
glCompileShader(res);
GLint compile_ok = GL_FALSE;
glGetShaderiv(res, GL_COMPILE_STATUS, &compile_ok);
if (compile_ok == GL_FALSE) {
fprintf(stderr, "%s:", filename);
print_log(res);
glDeleteShader(res);
return 0;
}
return res;
}
GLuint create_program(const char *vertexfile, const char *fragmentfile) {
GLuint program = glCreateProgram();
GLuint shader;
if(vertexfile) {
shader = create_shader(vertexfile, GL_VERTEX_SHADER);
if(!shader)
return 0;
glAttachShader(program, shader);
}
if(fragmentfile) {
shader = create_shader(fragmentfile, GL_FRAGMENT_SHADER);
if(!shader)
return 0;
glAttachShader(program, shader);
}
glLinkProgram(program);
GLint link_ok = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
if (!link_ok) {
fprintf(stderr, "glLinkProgram:");
print_log(program);
glDeleteProgram(program);
return 0;
}
return program;
}
#ifdef GL_GEOMETRY_SHADER
GLuint create_gs_program(const char *vertexfile, const char *geometryfile, const char *fragmentfile, GLint input, GLint output, GLint vertices) {
GLuint program = glCreateProgram();
GLuint shader;
if(vertexfile) {
shader = create_shader(vertexfile, GL_VERTEX_SHADER);
if(!shader)
return 0;
glAttachShader(program, shader);
}
if(geometryfile) {
shader = create_shader(geometryfile, GL_GEOMETRY_SHADER);
if(!shader)
return 0;
glAttachShader(program, shader);
glProgramParameteriEXT(program, GL_GEOMETRY_INPUT_TYPE_EXT, input);
glProgramParameteriEXT(program, GL_GEOMETRY_OUTPUT_TYPE_EXT, output);
glProgramParameteriEXT(program, GL_GEOMETRY_VERTICES_OUT_EXT, vertices);
}
if(fragmentfile) {
shader = create_shader(fragmentfile, GL_FRAGMENT_SHADER);
if(!shader)
return 0;
glAttachShader(program, shader);
}
glLinkProgram(program);
GLint link_ok = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
if (!link_ok) {
fprintf(stderr, "glLinkProgram:");
print_log(program);
glDeleteProgram(program);
return 0;
}
return program;
}
#else
GLuint create_gs_program(const char *vertexfile, const char *geometryfile, const char *fragmentfile, GLint input, GLint output, GLint vertices) {
fprintf(stderr, "Missing support for geometry shaders.\n");
return 0;
}
#endif
GLint get_attrib(GLuint program, const char *name) {
GLint attribute = glGetAttribLocation(program, name);
if(attribute == -1)
fprintf(stderr, "Could not bind attribute %s\n", name);
return attribute;
}
GLint get_uniform(GLuint program, const char *name) {
GLint uniform = glGetUniformLocation(program, name);
if(uniform == -1)
fprintf(stderr, "Could not bind uniform %s\n", name);
return uniform;
}
File: start.cpp
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shader_utils.hpp"
#include "mouse.hpp"
#include "cube.hpp"
int scrWidth = 600;
int scrHeight = 600;
GLuint shaderProgram;
GLint uniform_mvp;
Cube cube1;
Cube cube2;
glm::mat4 mvpCube1;
glm::mat4 mvpCube2;
Mouse theMouse;
void onIdle()
{
glm::vec3 rotationCube1(0, 1, 0); // Rotate cube 1 over Y axis
float angleCube1 = glutGet(GLUT_ELAPSED_TIME) / 1000.0 * 5; // 5° per second
glm::vec3 translationCube1 (0.0, 0.0, -2.0); // Translate cube 1 over Z axis
glm::vec3 rotationCube2(1, 0, 0); // Rotate cube 2 over X axis
float angleCube2 = glutGet(GLUT_ELAPSED_TIME) / 1000.0 * 3; // 3° per second
glm::vec3 translationCube2 (1.0, 0.0, 0.0); // Translate cube 2 over X axis
// Frustum parameters
float angleVision = 45.0f; // Also called Fovy
float aspect = 1.0f * scrWidth / scrHeight;
float zNear = 0.1f;
float zFar = 10.f;
// Camera parameters
glm::vec3 cameraEye(0.0, 2.0, -10.0);
glm::vec3 cameraCenter(0.0, 0.0, -5.0);
glm::vec3 cameraUp(0.0, 1.0, 0.0);
// Method lookAt has 3 parameters: eye, center, up
glm::mat4 view = glm::lookAt(cameraEye, cameraCenter, cameraUp);
// Create the projection matrix
glm::mat4 projection = glm::perspective(angleVision, aspect, zNear, zFar);
glm::mat4 modelCube1 = glm::mat4(); // Carga model con la identity matrix
modelCube1 = glm::translate(modelCube1, translationCube1); // El modelo queda trasladado.
modelCube1 = glm::rotate(modelCube1, angleCube1, rotationCube1); // El modelo ahora queda trasladado y rotado.
mvpCube1 = projection * view * modelCube1;
glm::mat4 modelCube2 = glm::mat4(); // Carga model con la identity matrix
modelCube2 = glm::translate(modelCube2, translationCube2); // El modelo queda trasladado.
modelCube2 = glm::rotate(modelCube2, angleCube2, rotationCube2); // El modelo ahora queda trasladado y rotado.
mvpCube2 = projection * view * modelCube2;
glUseProgram(shaderProgram);
glutPostRedisplay();
}
void onDisplay()
{
glClearColor(0.0, 0.0, 0.0, 1.0); // Set color (0, 0, 0 = Black)
// R,G,B,A
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(shaderProgram); // Program with loaded shaders
cube1.draw(uniform_mvp, mvpCube1);
cube1.disable();
cube2.draw(uniform_mvp, mvpCube2);
cube2.disable();
glutSwapBuffers();
}
void onReshape(int width, int height)
{
fprintf(stderr, "onReshape\n");
scrWidth = width;
scrHeight = height;
glViewport(0, 0, scrWidth, scrHeight);
}
void free_resources()
{
fprintf(stderr, "free_resources\n");
glDeleteProgram(shaderProgram);
cube1.deleteBuffers();
cube2.deleteBuffers();
}
void myMouseFunc(int button, int state, int x, int y)
{
theMouse.show(button, state, x, y);
}
int init(){
uniform_mvp = glGetUniformLocation(shaderProgram, "mvp");
if (uniform_mvp == -1)
{
fprintf(stderr, "Could not bind uniform %s\n", "mvp");
return 0;
}
return 1;
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA|GLUT_ALPHA|GLUT_DOUBLE|GLUT_DEPTH);
glutInitWindowSize(scrWidth, scrHeight);
glutCreateWindow("Two Cubes");
GLenum glew_status = glewInit();
if (glew_status != GLEW_OK)
{
fprintf(stderr, "Error: %s\n", glewGetErrorString(glew_status));
return 1;
}
if (!GLEW_VERSION_2_0)
{
fprintf(stderr, "Error: your graphic card does not support OpenGL 2.0\n");
return 1;
}
GLint link_ok = GL_FALSE;
GLuint vs, fs;
if ((vs = create_shader("cube_vertex.glsl", GL_VERTEX_SHADER)) == 0) return 0;
if ((fs = create_shader("cube_fragment.glsl", GL_FRAGMENT_SHADER)) == 0) return 0;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vs);
glAttachShader(shaderProgram, fs);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &link_ok);
if (!link_ok)
{
fprintf(stderr, "glLinkProgram:");
print_log(shaderProgram);
return 0;
}
cube1 = Cube();
cube2 = Cube();
if (init() && cube1.init_resources(shaderProgram) && cube2.init_resources(shaderProgram))
{
glutMouseFunc(myMouseFunc);
glutDisplayFunc(onDisplay); // Set the display function for the current window.
glutReshapeFunc(onReshape); // Set the reshape function for the current window.
glutIdleFunc(onIdle); // Set the global idle callback (perform background proccessing)
// Enable server side capabilities
glEnable(GL_BLEND); // GL_BLEND: If enabled, blend the computed fragment color values with the
// values in the color buffers. See glBlendFunc.
glEnable(GL_DEPTH_TEST); // GL_DEPTH_TEST: If enabled, do depth comparisons and update the depth buffer.
// Note that even if the depth buffer exists and the depth mask is non-zero,
// the depth buffer is not updated if the depth test is disabled. See glDepthFunc
// and glDepthRange.
//glDepthFunc(GL_LESS);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glutMainLoop();
}
free_resources();
return 0;
}
File: cube_fragment.glsl
varying vec3 f_color;
void main(void) {
gl_FragColor = vec4(f_color.x, f_color.y, f_color.z, 1.0);
}
File: cube_vertex.glsl
attribute vec3 v_color;
uniform mat4 mvp;
varying vec3 f_color;
void main(void)
{
gl_Position = mvp * vec4(coord3d, 1.0);
f_color = v_color;
}
File: Makefile
LDLIBS=-lglut -lGLEW -lGL -lm
all: start
clean:
rm -f *.o start
start: mouse.o shader_utils.o cube.o
.PHONY: all clean
The result are two rotating cubes: