Created
May 20, 2018 22:30
-
-
Save gamepopper/1931ca297f3decdee90e785f12762192 to your computer and use it in GitHub Desktop.
SFML 2.5.0's OpenGL Example, written using the modern OpenGL programmable pipeline instead of the legacy OpenGL fixed pipeline.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////////////////// | |
// Headers | |
//////////////////////////////////////////////////////////// | |
/// GLEW is needed to provide OpenGL extensions. | |
#include <GL/glew.h> | |
#include <SFML/Graphics.hpp> | |
#include <SFML/OpenGL.hpp> | |
/// GLM is needed to provide 3D math properties, particularly matrices for 3D transformations. | |
#define GLM_ENABLE_EXPERIMENTAL | |
#include "glm/glm.hpp" | |
#include "glm/gtx/transform.hpp" | |
#include <iostream> | |
#ifndef GL_SRGB8_ALPHA8 | |
#define GL_SRGB8_ALPHA8 0x8C43 | |
#endif | |
///Basic vertex shader that transforms the vertex position based on a projection view matrix and passes the texture coordinate to the fragment shader. | |
const std::string defaultVertexShader = | |
"#version 330\n"\ | |
"attribute vec3 position;"\ | |
"attribute vec2 texCoord;" \ | |
"uniform mat4 pvm;" \ | |
"varying vec2 uv;" \ | |
"void main() {"\ | |
" gl_Position = pvm * vec4(position, 1.0);"\ | |
" uv = texCoord;"\ | |
"}"; | |
///Basic fragment shader that returns the colour of a pixel based on the input texture and its coordinate. | |
const std::string defaultFragShader = | |
"#version 330\n" \ | |
"uniform sampler2D texture;" \ | |
"varying vec2 uv;" \ | |
"void main() {" \ | |
" gl_FragColor = texture2D(texture, uv);" \ | |
"}"; | |
///Shader Types | |
enum class ShaderType { Vertex, Fragment, Geometry, Count }; | |
///Standard Uniforms in the shader. | |
enum class UniformType { TransformPVM, Count }; | |
///Vertex attributes for shaders and the input vertex array. | |
enum class VertexAttribute { Position, TexCoord, COUNT }; | |
///Shader Program | |
GLuint program = 0; | |
///List of shaders set up for a 3D scene. | |
GLuint shader[static_cast<unsigned int>(ShaderType::Count)]; | |
///List of uniforms that can be defined values for the shader. | |
GLint uniform[static_cast<unsigned int>(UniformType::Count)]; | |
///Vertex Array Object ID. | |
GLuint vao = 0; | |
///Vertex Buffer Object for Vertices. | |
GLuint vertexVBO = 0; | |
///Vertex Buffer Object for Indices. | |
GLuint indexVBO = 0; | |
///Depending on input, the amount of vertices or indices that are needed to be drawn for this object. | |
unsigned int drawCount; | |
///Checks for any errors specific to the shaders. It will output any errors within the shader if it's not valid. | |
void checkError(GLuint l_shader, GLuint l_flag, bool l_program, const std::string& l_errorMsg) | |
{ | |
GLint success = 0; | |
GLchar error[1024] = { 0 }; | |
if (l_program) { glGetProgramiv(l_shader, l_flag, &success); } | |
else { glGetShaderiv(l_shader, l_flag, &success); } | |
if (success) { return; } | |
if (l_program) { | |
glGetProgramInfoLog(l_shader, sizeof(error), nullptr, error); | |
} | |
else { | |
glGetShaderInfoLog(l_shader, sizeof(error), nullptr, error); | |
} | |
std::cout << l_errorMsg << "\n"; | |
} | |
///Creates and compiles a shader. | |
GLuint buildShader(const std::string& l_src, unsigned int l_type) | |
{ | |
GLuint shaderID = glCreateShader(l_type); | |
if (!shaderID) { | |
std::cout << "Bad shader type!"; | |
return 0; | |
} | |
const GLchar* sources[1]; | |
GLint lengths[1]; | |
sources[0] = l_src.c_str(); | |
lengths[0] = l_src.length(); | |
glShaderSource(shaderID, 1, sources, lengths); | |
glCompileShader(shaderID); | |
checkError(shaderID, GL_COMPILE_STATUS, false, "Shader compile error: "); | |
return shaderID; | |
} | |
///Function to load the shaders from string data. | |
void LoadFromMemory(const std::string& shaderData, ShaderType type) | |
{ | |
if (shaderData.empty()) | |
return; | |
if (shader[static_cast<unsigned int>(type)]) | |
{ | |
glDetachShader(program, shader[static_cast<unsigned int>(type)]); | |
glDeleteShader(shader[static_cast<unsigned int>(type)]); | |
} | |
switch (type) | |
{ | |
case ShaderType::Vertex: | |
shader[static_cast<unsigned int>(type)] = buildShader(shaderData, GL_VERTEX_SHADER); | |
break; | |
case ShaderType::Geometry: | |
shader[static_cast<unsigned int>(type)] = buildShader(shaderData, GL_GEOMETRY_SHADER); | |
break; | |
case ShaderType::Fragment: | |
shader[static_cast<unsigned int>(type)] = buildShader(shaderData, GL_FRAGMENT_SHADER); | |
break; | |
default: | |
break; | |
} | |
if (program == 0) | |
{ | |
program = glCreateProgram(); | |
} | |
glAttachShader(program, shader[static_cast<unsigned int>(type)]); | |
glBindAttribLocation(program, static_cast<GLuint>(VertexAttribute::Position), "position"); | |
glBindAttribLocation(program, static_cast<GLuint>(VertexAttribute::TexCoord), "texCoord"); | |
glLinkProgram(program); | |
checkError(program, GL_LINK_STATUS, true, "Shader link error:"); | |
glValidateProgram(program); | |
checkError(program, GL_VALIDATE_STATUS, true, "Invalid shader:"); | |
uniform[static_cast<unsigned int>(UniformType::TransformPVM)] = glGetUniformLocation(program, "pvm"); | |
} | |
//////////////////////////////////////////////////////////// | |
/// Entry point of application | |
/// | |
/// \return Application exit code | |
/// | |
//////////////////////////////////////////////////////////// | |
int main() | |
{ | |
bool exit = false; | |
bool sRgb = false; | |
while (!exit) | |
{ | |
// Request a 24-bits depth buffer when creating the window | |
sf::ContextSettings contextSettings; | |
contextSettings.depthBits = 24; | |
contextSettings.sRgbCapable = sRgb; | |
// Create the main window | |
sf::RenderWindow window(sf::VideoMode(800, 600), "SFML graphics with OpenGL", sf::Style::Default, contextSettings); | |
window.setVerticalSyncEnabled(true); | |
// Initialise GLEW for the extended functions. | |
glewExperimental = GL_TRUE; | |
if (glewInit() != GLEW_OK) | |
return EXIT_FAILURE; | |
// Create a sprite for the background | |
sf::Texture backgroundTexture; | |
backgroundTexture.setSrgb(sRgb); | |
if (!backgroundTexture.loadFromFile("resources/background.jpg")) | |
return EXIT_FAILURE; | |
sf::Sprite background(backgroundTexture); | |
// Create some text to draw on top of our OpenGL object | |
sf::Font font; | |
if (!font.loadFromFile("resources/sansation.ttf")) | |
return EXIT_FAILURE; | |
sf::Text text("SFML / OpenGL demo", font); | |
sf::Text sRgbInstructions("Press space to toggle sRGB conversion", font); | |
sf::Text mipmapInstructions("Press return to toggle mipmapping", font); | |
text.setFillColor(sf::Color(255, 255, 255, 170)); | |
sRgbInstructions.setFillColor(sf::Color(255, 255, 255, 170)); | |
mipmapInstructions.setFillColor(sf::Color(255, 255, 255, 170)); | |
text.setPosition(250.f, 450.f); | |
sRgbInstructions.setPosition(150.f, 500.f); | |
mipmapInstructions.setPosition(180.f, 550.f); | |
// Load a texture to apply to our 3D cube | |
sf::Texture texture; | |
if (!texture.loadFromFile("resources/texture.jpg")) | |
return EXIT_FAILURE; | |
// Attempt to generate a mipmap for our cube texture | |
// We don't check the return value here since | |
// mipmapping is purely optional in this example | |
texture.generateMipmap(); | |
// Make the window the active window for OpenGL calls | |
window.setActive(true); | |
// Load the shaders we need. | |
if (program == 0) | |
{ | |
LoadFromMemory(defaultVertexShader, ShaderType::Vertex); | |
LoadFromMemory(defaultFragShader, ShaderType::Fragment); | |
} | |
// Enable Z-buffer read and write and culling. | |
glEnable(GL_DEPTH_TEST); | |
glEnable(GL_CULL_FACE); | |
glCullFace(GL_BACK); | |
// Setup a perspective projection | |
GLfloat ratio = static_cast<float>(window.getSize().x) / window.getSize().y; | |
glm::mat4 projection = glm::frustum(-ratio, ratio, -1.f, 1.f, 1.f, 500.0f); | |
// Define a 3D cube (6 faces made of 2 triangles composed by 3 indices of a list of vertices) | |
static const GLfloat cube[] = | |
{ | |
// positions // texture coordinates | |
//front | |
-20, -20, -20, 0, 0, | |
20, -20, -20, 1, 0, | |
20, 20, -20, 1, 1, | |
-20, 20, -20, 0, 1, | |
//right | |
20, 20, -20, 1, 1, | |
20, 20, 20, 0, 1, | |
20, -20, 20, 0, 0, | |
20, -20, -20, 1, 0, | |
//back | |
-20, -20, 20, 0, 0, | |
20, -20, 20, 1, 0, | |
20, 20, 20, 1, 1, | |
-20, 20, 20, 0, 1, | |
//left | |
-20, -20, 20, 0, 0, | |
-20, -20, -20, 1, 0, | |
-20, 20, -20, 1, 1, | |
-20, 20, 20, 0, 1, | |
//upper | |
20, -20, 20, 0, 1, | |
-20, -20, 20, 1, 1, | |
-20, -20, -20, 1, 0, | |
20, -20, -20, 0, 0, | |
//bottom | |
-20, 20, -20, 0, 1, | |
20, 20, -20, 1, 1, | |
20, 20, 20, 1, 0, | |
-20, 20, 20, 0, 0, | |
}; | |
// Define indices for 3D cube. | |
static const unsigned int indices[] = | |
{ | |
2, 1, 0, 3, 2, 0, //front | |
4, 5, 6, 4, 6, 7, //right | |
8, 9, 10, 8, 10, 11, //back | |
14, 13, 12, 15, 14, 12, //left | |
16, 17, 18, 16, 18, 19, //upper | |
22, 21, 20, 23, 22, 20 //bottom | |
}; | |
// Stride is the number of bytes per array element. | |
auto stride = sizeof(GLfloat) * 5; | |
// Data offset for texture coordinate in bytes. | |
auto textureCoordOffset = sizeof(GLfloat) * 3; | |
// Amount of index elements in total. | |
drawCount = sizeof(indices) / sizeof(unsigned int); | |
// Generate Vertex Array and Vertex Buffer and point the area of data to assign to each attribute. | |
glGenVertexArrays(1, &vao); | |
glBindVertexArray(vao); | |
glGenBuffers(1, &vertexVBO); | |
glBindBuffer(GL_ARRAY_BUFFER, vertexVBO); | |
glBufferData(GL_ARRAY_BUFFER, drawCount * stride, cube, GL_STATIC_DRAW); | |
glEnableVertexAttribArray(static_cast<GLuint>(VertexAttribute::Position)); | |
glVertexAttribPointer(static_cast<GLuint>(VertexAttribute::Position), 3, GL_FLOAT, GL_FALSE, stride, 0); | |
glEnableVertexAttribArray(static_cast<GLuint>(VertexAttribute::TexCoord)); | |
glVertexAttribPointer(static_cast<GLuint>(VertexAttribute::TexCoord), 2, GL_FLOAT, GL_FALSE, stride, (void*)textureCoordOffset); | |
// Generate Index Buffer and define the amount of indices to point to. | |
glGenBuffers(1, &indexVBO); | |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO); | |
glBufferData(GL_ELEMENT_ARRAY_BUFFER, drawCount * sizeof(indices[0]), indices, GL_STATIC_DRAW); | |
//Make sure to bind the vertex array to null if you wish to define more objects. | |
glBindVertexArray(0); | |
// Make the window no longer the active window for OpenGL calls | |
window.setActive(false); | |
// Create a clock for measuring the time elapsed | |
sf::Clock clock; | |
// Flag to track whether mipmapping is currently enabled | |
bool mipmapEnabled = true; | |
// Start game loop | |
while (window.isOpen()) | |
{ | |
// Process events | |
sf::Event event; | |
while (window.pollEvent(event)) | |
{ | |
// Close window: exit | |
if (event.type == sf::Event::Closed) | |
{ | |
exit = true; | |
window.close(); | |
} | |
// Escape key: exit | |
if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape)) | |
{ | |
exit = true; | |
window.close(); | |
} | |
// Return key: toggle mipmapping | |
if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Return)) | |
{ | |
if (mipmapEnabled) | |
{ | |
// We simply reload the texture to disable mipmapping | |
if (!texture.loadFromFile("resources/texture.jpg")) | |
return EXIT_FAILURE; | |
mipmapEnabled = false; | |
} | |
else | |
{ | |
texture.generateMipmap(); | |
mipmapEnabled = true; | |
} | |
} | |
// Space key: toggle sRGB conversion | |
if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Space)) | |
{ | |
sRgb = !sRgb; | |
window.close(); | |
} | |
// Adjust the viewport when the window is resized | |
if (event.type == sf::Event::Resized) | |
{ | |
// Make the window the active window for OpenGL calls | |
window.setActive(true); | |
glViewport(0, 0, event.size.width, event.size.height); | |
// Make the window no longer the active window for OpenGL calls | |
window.setActive(false); | |
} | |
} | |
// Clear the depth buffer | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
// Configure the viewport (the same size as the window) | |
glViewport(0, 0, window.getSize().x, window.getSize().y); | |
// Draw the background | |
window.pushGLStates(); | |
window.draw(background); | |
window.popGLStates(); | |
// Make the window the active window for OpenGL calls | |
window.setActive(true); | |
// Bind the texture | |
sf::Texture::bind(&texture); | |
glBindVertexArray(vao); | |
// We get the position of the mouse cursor, so that we can move the box accordingly | |
float x = sf::Mouse::getPosition(window).x * 200.f / window.getSize().x - 100.f; | |
float y = -sf::Mouse::getPosition(window).y * 200.f / window.getSize().y + 100.f; | |
// Apply some transformations | |
glm::mat4 matrix_pos = glm::translate(glm::vec3(x, y, -100.f)); | |
glm::mat4 matrix_rotX = glm::rotate(clock.getElapsedTime().asSeconds() * 50.f * (3.1415926f / 180.0f), glm::vec3(1.f, 0.f, 0.f)); | |
glm::mat4 matrix_rotY = glm::rotate(clock.getElapsedTime().asSeconds() * 30.f * (3.1415926f / 180.0f), glm::vec3(0.f, 1.f, 0.f)); | |
glm::mat4 matrix_rotZ = glm::rotate(clock.getElapsedTime().asSeconds() * 90.f * (3.1415926f / 180.0f), glm::vec3(0.f, 0.f, 1.f)); | |
glm::mat4 matrix_rotation = matrix_rotZ * matrix_rotY * matrix_rotX; | |
glm::mat4 transform = matrix_pos * matrix_rotation; | |
glm::mat4 identity; | |
glm::mat4 viewProj = projection * transform; | |
//Bind the shaders. | |
glUseProgram(program); | |
//Set the uniforms for the shader to use. | |
if (uniform[(int)UniformType::TransformPVM] >= 0) | |
glUniformMatrix4fv((unsigned int)uniform[(int)UniformType::TransformPVM], 1, GL_FALSE, &viewProj[0][0]); | |
// Draw the cube | |
glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_INT, 0); | |
// Reset the vertex array bound, shader and texture for other assets to draw. | |
glBindVertexArray(0); | |
glUseProgram(0); | |
glBindTexture(GL_TEXTURE_2D, 0); | |
// Make the window no longer the active window for OpenGL calls | |
window.setActive(false); | |
// Draw some text on top of our OpenGL object | |
window.pushGLStates(); | |
window.draw(text); | |
window.draw(sRgbInstructions); | |
window.draw(mipmapInstructions); | |
window.popGLStates(); | |
// Finally, display the rendered frame on screen | |
window.display(); | |
} | |
//Destroy all buffers, shaders and programs. | |
glDeleteBuffers(1, &vertexVBO); | |
glDeleteBuffers(1, &indexVBO); | |
glDeleteVertexArrays(1, &vao); | |
//Setting these values to zero will allow them to be initialised with new data on reset. | |
vertexVBO = 0; | |
indexVBO = 0; | |
vao = 0; | |
for (unsigned int i = 0; i < static_cast<unsigned int>(ShaderType::Count); i++) | |
{ | |
glDetachShader(program, shader[i]); | |
glDeleteShader(shader[i]); | |
shader[i] = 0; | |
} | |
for (unsigned int i = 0; i < static_cast<unsigned int>(UniformType::Count); i++) | |
{ | |
uniform[i] = -1; | |
} | |
glDeleteProgram(program); | |
program = 0; | |
} | |
return EXIT_SUCCESS; | |
} |
How is this expected to be linked?
Holy cow this saved me so much you have no idea how much hair pulling I was doing, I didn't realize you have to unbind everything manually for SFML graphics module to be able to draw.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@gamepopper thanks for providing this code.
one line 298 you're buferring the cube data:
glBufferData(GL_ARRAY_BUFFER, drawCount * stride, cube, GL_STATIC_DRAW);
but
drawCount
is set to be the number of elements inindices
rather thancube
. Is this is a mistake?