2024-04-04 07:27:01 +00:00
|
|
|
---
|
2021-03-10 03:43:53 +00:00
|
|
|
category: framework
|
2024-12-09 11:34:00 +00:00
|
|
|
name: OpenGL
|
2024-04-04 07:27:01 +00:00
|
|
|
filename: learnopengl.cpp
|
|
|
|
contributors:
|
|
|
|
- ["Simon Deitermann", "s.f.deitermann@t-online.de"]
|
|
|
|
---
|
|
|
|
|
|
|
|
**Open Graphics Library** (**OpenGL**) is a cross-language cross-platform application programming interface
|
|
|
|
(API) for rendering 2D computer graphics and 3D vector graphics.<sup>[1]</sup> In this tutorial we will be
|
|
|
|
focusing on modern OpenGL from 3.3 and above, ignoring "immediate-mode", Displaylists and
|
|
|
|
VBO's without use of Shaders.
|
|
|
|
I will be using C++ with SFML for window, image and context creation aswell as GLEW
|
|
|
|
for modern OpenGL extensions, though there are many other librarys available.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// Creating an SFML window and OpenGL basic setup.
|
|
|
|
#include <GL/glew.h>
|
|
|
|
#include <GL/gl.h>
|
|
|
|
#include <SFML/Graphics.h>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
// First we tell SFML how to setup our OpenGL context.
|
|
|
|
sf::ContextSettings context{ 24, // depth buffer bits
|
|
|
|
8, // stencil buffer bits
|
|
|
|
4, // MSAA samples
|
|
|
|
3, // major opengl version
|
|
|
|
3 }; // minor opengl version
|
|
|
|
// Now we create the window, enable VSync
|
|
|
|
// and set the window active for OpenGL.
|
|
|
|
sf::Window window{ sf::VideoMode{ 1024, 768 },
|
|
|
|
"opengl window",
|
|
|
|
sf::Style::Default,
|
|
|
|
context };
|
|
|
|
window.setVerticalSyncEnabled(true);
|
|
|
|
window.setActive(true);
|
|
|
|
// After that we initialise GLEW and check if an error occurred.
|
|
|
|
GLenum error;
|
|
|
|
glewExperimental = GL_TRUE;
|
|
|
|
if ((err = glewInit()) != GLEW_OK)
|
|
|
|
std::cout << glewGetErrorString(err) << std::endl;
|
|
|
|
// Here we set the color glClear will clear the buffers with.
|
|
|
|
glClearColor(0.0f, // red
|
|
|
|
0.0f, // green
|
|
|
|
0.0f, // blue
|
|
|
|
1.0f); // alpha
|
|
|
|
// Now we can start the event loop, poll for events and draw objects.
|
|
|
|
sf::Event event{ };
|
|
|
|
while (window.isOpen()) {
|
|
|
|
while (window.pollEvent(event)) {
|
|
|
|
if (event.type == sf::Event::Closed)
|
|
|
|
window.close;
|
|
|
|
}
|
|
|
|
// Tell OpenGL to clear the color buffer
|
|
|
|
// and the depth buffer, this will clear our window.
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
// Flip front- and backbuffer.
|
|
|
|
window.display();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Loading Shaders
|
|
|
|
|
|
|
|
After creating a window and our event loop we should create a function,
|
|
|
|
that sets up our shader program.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
GLuint createShaderProgram(const std::string& vertexShaderPath,
|
|
|
|
const std::string& fragmentShaderPath) {
|
|
|
|
// Load the vertex shader source.
|
|
|
|
std::stringstream ss{ };
|
|
|
|
std::string vertexShaderSource{ };
|
|
|
|
std::string fragmentShaderSource{ };
|
|
|
|
std::ifstream file{ vertexShaderPath };
|
|
|
|
if (file.is_open()) {
|
|
|
|
ss << file.rdbuf();
|
|
|
|
vertexShaderSource = ss.str();
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
// Clear the stringstream and load the fragment shader source.
|
|
|
|
ss.str(std::string{ });
|
|
|
|
file.open(fragmentShaderPath);
|
|
|
|
if (file.is_open()) {
|
|
|
|
ss << file.rdbuf();
|
|
|
|
fragmentShaderSource = ss.str();
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
// Create the program.
|
|
|
|
GLuint program = glCreateProgram();
|
|
|
|
// Create the shaders.
|
|
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
|
|
// Now we can load the shader source into the shader objects and compile them.
|
|
|
|
// Because glShaderSource() wants a const char* const*,
|
|
|
|
// we must first create a const char* and then pass the reference.
|
|
|
|
const char* cVertexSource = vertexShaderSource.c_str();
|
|
|
|
glShaderSource(vertexShader, // shader
|
|
|
|
1, // number of strings
|
|
|
|
&cVertexSource, // strings
|
|
|
|
nullptr); // length of strings (nullptr for 1)
|
|
|
|
glCompileShader(vertexShader);
|
|
|
|
// Now we have to do the same for the fragment shader.
|
|
|
|
const char* cFragmentSource = fragmentShaderSource.c_str();
|
|
|
|
glShaderSource(fragmentShader, 1, &cFragmentSource, nullptr);
|
|
|
|
glCompileShader(fragmentShader);
|
|
|
|
// After attaching the source and compiling the shaders,
|
|
|
|
// we attach them to the program;
|
|
|
|
glAttachShader(program, vertexShader);
|
|
|
|
glAttachShader(program, fragmentShader);
|
|
|
|
glLinkProgram(program);
|
|
|
|
// After linking the shaders we should detach and delete
|
|
|
|
// them to prevent memory leak.
|
|
|
|
glDetachShader(program, vertexShader);
|
|
|
|
glDetachShader(program, fragmentShader);
|
|
|
|
glDeleteShader(vertexShader);
|
|
|
|
glDeleteShader(fragmentShader);
|
|
|
|
// With everything done we can return the completed program.
|
|
|
|
return program;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If you want to check the compilation log you can add the following between <code>glCompileShader()</code> and <code>glAttachShader()</code>.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
GLint logSize = 0;
|
|
|
|
std::vector<GLchar> logText{ };
|
|
|
|
glGetShaderiv(vertexShader, // shader
|
|
|
|
GL_INFO_LOG_LENGTH, // requested parameter
|
|
|
|
&logSize); // return object
|
|
|
|
if (logSize > 0) {
|
|
|
|
logText.resize(logSize);
|
|
|
|
glGetShaderInfoLog(vertexShader, // shader
|
|
|
|
logSize, // buffer length
|
|
|
|
&logSize, // returned length
|
|
|
|
logText.data()); // buffer
|
|
|
|
std::cout << logText.data() << std::endl;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The same is possible after <code>glLinkProgram()</code>, just replace <code>glGetShaderiv()</code> with <code>glGetProgramiv()</code>
|
|
|
|
and <code>glGetShaderInfoLog()</code> with <code>glGetProgramInfoLog()</code>.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// Now we can create a shader program with a vertex and a fragment shader.
|
|
|
|
// ...
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
|
|
|
|
GLuint program = createShaderProgram("vertex.glsl", "fragment.glsl");
|
|
|
|
|
|
|
|
sf::Event event{ };
|
|
|
|
// ...
|
|
|
|
// We also have to delete the program at the end of the application.
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
glDeleteProgram(program);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
2024-09-30 23:03:06 +00:00
|
|
|
Of course we have to create the vertex and fragment shader before we can load them,
|
2024-04-04 07:27:01 +00:00
|
|
|
so lets create two basic shaders.
|
|
|
|
|
|
|
|
**Vertex Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
// Declare which version of GLSL we use.
|
|
|
|
// Here we declare, that we want to use the OpenGL 3.3 version of GLSL.
|
|
|
|
#version 330 core
|
|
|
|
// At attribute location 0 we want an input variable of type vec3,
|
|
|
|
// that contains the position of the vertex.
|
|
|
|
// Setting the location is optional, if you don't set it you can ask for the
|
|
|
|
// location with glGetAttribLocation().
|
|
|
|
layout(location = 0) in vec3 position;
|
|
|
|
// Every shader starts in it's main function.
|
|
|
|
void main() {
|
|
|
|
// gl_Position is a predefined variable that holds
|
|
|
|
// the final vertex position.
|
|
|
|
// It consists of a x, y, z and w coordinate.
|
|
|
|
gl_Position = vec4(position, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**Fragment Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
// The fragment shader does not have a predefined variable for
|
|
|
|
// the vertex color, so we have to define a output vec4,
|
|
|
|
// that holds the final vertex color.
|
|
|
|
out vec4 outColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
// We simply set the output color to red.
|
|
|
|
// The parameters are red, green, blue and alpha.
|
|
|
|
outColor = vec4(1.0, 0.0, 0.0, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## VAO and VBO
|
|
|
|
Now we need to define some vertex position we can pass to our shaders. Lets define a simple 2D quad.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// The vertex data is defined in a counter-clockwise way,
|
|
|
|
// as this is the default front face.
|
|
|
|
std::vector<float> vertexData {
|
|
|
|
-0.5f, 0.5f, 0.0f,
|
|
|
|
-0.5f, -0.5f, 0.0f,
|
|
|
|
0.5f, -0.5f, 0.0f,
|
|
|
|
0.5f, 0.5f, 0.0f
|
|
|
|
};
|
|
|
|
// If you want to use a clockwise definition, you can simply call
|
|
|
|
glFrontFace(GL_CW);
|
|
|
|
// Next we need to define a Vertex Array Object (VAO).
|
|
|
|
// The VAO stores the current state while its active.
|
|
|
|
GLuint vao = 0;
|
|
|
|
glGenVertexArrays(1, &vao);
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
// With the VAO active we can now create a Vertex Buffer Object (VBO).
|
|
|
|
// The VBO stores our vertex data.
|
|
|
|
GLuint vbo = 0;
|
|
|
|
glGenBuffers(1, &vbo);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
|
|
// For reading and copying there are also GL_*_READ and GL_*_COPY,
|
|
|
|
// if your data changes more often use GL_DYNAMIC_* or GL_STREAM_*.
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, // target buffer
|
|
|
|
sizeof(vertexData[0]) * vertexData.size(), // size
|
|
|
|
vertexData.data(), // data
|
|
|
|
GL_STATIC_DRAW); // usage
|
|
|
|
// After filling the VBO link it to the location 0 in our vertex shader,
|
|
|
|
// which holds the vertex position.
|
|
|
|
// ...
|
|
|
|
// To ask for the attribute location, if you haven't set it:
|
|
|
|
GLint posLocation = glGetAttribLocation(program, "position");
|
|
|
|
// ..
|
|
|
|
glEnableVertexAttribArray(0);
|
|
|
|
glVertexAttribPointer(0, 3, // location and size
|
|
|
|
GL_FLOAT, // type of data
|
|
|
|
GL_FALSE, // normalized (always false for floats)
|
|
|
|
0, // stride (interleaved arrays)
|
|
|
|
nullptr); // offset (interleaved arrays)
|
|
|
|
// Everything should now be saved in our VAO and we can unbind it and the VBO.
|
|
|
|
glBindVertexArray(0);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
// Now we can draw the vertex data in our render loop.
|
|
|
|
// ...
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Tell OpenGL we want to use our shader program.
|
|
|
|
glUseProgram(program);
|
|
|
|
// Binding the VAO loads the data we need.
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
// We want to draw a quad starting at index 0 of the VBO using 4 indices.
|
|
|
|
glDrawArrays(GL_QUADS, 0, 4);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
window.display();
|
|
|
|
// ...
|
|
|
|
// Ofcource we have to delete the allocated memory for the VAO and VBO at
|
|
|
|
// the end of our application.
|
|
|
|
// ...
|
|
|
|
glDeleteBuffers(1, &vbo);
|
|
|
|
glDeleteVertexArrays(1, &vao);
|
|
|
|
glDeleteProgram(program);
|
|
|
|
return 0;
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
|
|
|
You can find the current code here: [OpenGL - 1](https://pastebin.com/W8jdmVHD).
|
|
|
|
|
|
|
|
## More VBO's and Color
|
|
|
|
Let's create another VBO for some colors.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
std::vector<float> colorData {
|
|
|
|
1.0f, 0.0f, 0.0f,
|
|
|
|
0.0f, 1.0f, 0.0f,
|
|
|
|
0.0f, 0.0f, 1.0f,
|
|
|
|
1.0f, 1.0f, 0.0f
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
Next we can simply change some previous parameters to create a second VBO for our colors.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// ...
|
|
|
|
GLuint vbo[2];
|
|
|
|
glGenBuffers(2, vbo);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
|
|
|
// ...
|
|
|
|
glDeleteBuffers(2, vbo);
|
|
|
|
/ ...
|
|
|
|
// With these changes made we now have to load our color data into the new VBO
|
|
|
|
// ...
|
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(colorData[0]) * colorData.size(),
|
|
|
|
colorData.data(), GL_STATIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(1);
|
|
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
|
|
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
|
|
|
Next we have to change our vertex shader to pass the color data to the fragment shader.<br>
|
|
|
|
**Vertex Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
layout(location = 0) in vec3 position;
|
|
|
|
// The new location has to differ from any other input variable.
|
|
|
|
// It is the same index we need to pass to
|
|
|
|
// glEnableVertexAttribArray() and glVertexAttribPointer().
|
|
|
|
layout(location = 1) in vec3 color;
|
|
|
|
|
|
|
|
out vec3 fColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
fColor = color;
|
|
|
|
gl_Position = vec4(position, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**Fragment Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
in vec3 fColor;
|
|
|
|
|
|
|
|
out vec4 outColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
outColor = vec4(fColor, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
We define a new input variable ```color``` which represents our color data, this data
|
|
|
|
is passed on to ```fColor```, which is an output variable of our vertex shader and
|
|
|
|
becomes an input variable for our fragment shader.
|
2024-09-30 23:03:06 +00:00
|
|
|
It is important that variables passed between shaders have the exact same name
|
2024-04-04 07:27:01 +00:00
|
|
|
and type.
|
|
|
|
|
|
|
|
## Handling VBO's
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// If you want to completely clear and refill a VBO use glBufferData(),
|
|
|
|
// just like we did before.
|
|
|
|
// ...
|
|
|
|
// There are two mains ways to update a subset of a VBO's data.
|
|
|
|
// To update a VBO with existing data
|
|
|
|
std::vector<float> newSubData {
|
|
|
|
-0.25f, 0.5f, 0.0f
|
|
|
|
};
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
|
|
|
glBufferSubData(GL_ARRAY_BUFFER, // target buffer
|
|
|
|
0, // offset
|
|
|
|
sizeof(newSubData[0]) * newSubData.size(), // size
|
|
|
|
newSubData.data()); // data
|
|
|
|
// This would update the first three values in our vbo[0] buffer.
|
|
|
|
// If you want to update starting at a specific location just set the second
|
|
|
|
// parameter to that value and multiply by the types size.
|
|
|
|
// ...
|
|
|
|
// If you are streaming data, for example from a file,
|
|
|
|
// it is faster to directly pass the data to the buffer.
|
|
|
|
// Other access values are GL_READ_ONLY and GL_READ_WRITE.
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
|
|
|
// You can static_cast<float*>() the void* to be more safe.
|
|
|
|
void* Ptr = glMapBuffer(GL_ARRAY_BUFFER, // buffer to map
|
|
|
|
GL_WRITE_ONLY); // access to buffer
|
|
|
|
memcpy(Ptr, newSubData.data(), sizeof(newSubData[0]) * newSubData.size());
|
|
|
|
// To copy to a specific location add a destination offset to memcpy().
|
|
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
|
|
// ...
|
|
|
|
// There is also a way to copy data from one buffer to another,
|
|
|
|
// If we have two VBO's vbo[0] and vbo[1], we can copy like so
|
|
|
|
// You can also read from GL_ARRAY_BUFFER.
|
|
|
|
glBindBuffer(GL_COPY_READ_BUFFER, vbo[0]);
|
|
|
|
// GL_COPY_READ_BUFFER and GL_COPY_WRITE_BUFFER are specifically for
|
|
|
|
// copying buffer data.
|
|
|
|
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo[1]);
|
|
|
|
glCopyBufferSubData(GL_COPY_READ_BUFFER, // read buffer
|
|
|
|
GL_COPY_WRITE_BUFFER, // write buffer
|
|
|
|
0, 0, // read and write offset
|
|
|
|
sizeof(vbo[0]) * 3); // copy size
|
|
|
|
// This will copy the first three elements from vbo[0] to vbo[1].
|
|
|
|
```
|
|
|
|
|
|
|
|
## Uniforms
|
|
|
|
|
|
|
|
**Fragment Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
// Uniforms are variables like in and out, however,
|
|
|
|
// we can change them easily by passing new values with glUniform().
|
|
|
|
// Lets define a time variable in our fragment shader.
|
|
|
|
#version 330 core
|
|
|
|
// Unlike a in/out variable we can use a uniform in every shader,
|
|
|
|
// without the need to pass it to the next one, they are global.
|
|
|
|
// Don't use locations already used for attributes!
|
|
|
|
// Uniform layout locations require OpenGL 4.3!
|
|
|
|
layout(location = 10) uniform float time;
|
|
|
|
|
|
|
|
in vec3 fColor;
|
|
|
|
|
|
|
|
out vec4 outColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
// Create a sine wave from 0 to 1 based on the time passed to the shader.
|
|
|
|
float factor = (sin(time * 2) + 1) / 2;
|
|
|
|
outColor = vec4(fColor.r * factor, fColor.g * factor, fColor.b * factor, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Back to our source code.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// If we haven't set the layout location, we can ask for it.
|
|
|
|
GLint timeLocation = glGetUniformLocation(program, "time");
|
|
|
|
// ...
|
|
|
|
// Also we should define a Timer counting the current time.
|
|
|
|
sf::Clock clock{ };
|
|
|
|
// In out render loop we can now update the uniform every frame.
|
|
|
|
// ...
|
|
|
|
window.display();
|
|
|
|
glUniform1f(10, // location
|
|
|
|
clock.getElapsedTime().asSeconds()); // data
|
|
|
|
}
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
|
|
|
With the time getting updated every frame the quad should now be changing from
|
|
|
|
fully colored to pitch black.
|
|
|
|
There are different types of glUniform() you can find simple documentation here:
|
|
|
|
[glUniform - OpenGL Refpage](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUniform.xhtml)
|
|
|
|
|
|
|
|
## Indexing and IBO's
|
|
|
|
|
|
|
|
Element Array Buffers or more commonly Index Buffer Objects (IBO) allow us to use the
|
|
|
|
same vertex data again which makes drawing a lot easier and faster. here's an example:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// Lets create a quad from two rectangles.
|
|
|
|
// We can simply use the old vertex data from before.
|
|
|
|
// First, we have to create the IBO.
|
|
|
|
// The index is referring to the first declaration in the VBO.
|
|
|
|
std::vector<unsigned int> iboData {
|
|
|
|
0, 1, 2,
|
|
|
|
0, 2, 3
|
|
|
|
};
|
|
|
|
// That's it, as you can see we could reuse 0 - the top left
|
|
|
|
// and 2 - the bottom right.
|
|
|
|
// Now that we have our data, we have to fill it into a buffer.
|
|
|
|
// Note that this has to happen between the two glBindVertexArray() calls,
|
|
|
|
// so it gets saved into the VAO.
|
|
|
|
GLuint ibo = 0;
|
|
|
|
glGenBufferrs(1, &ibo);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(iboData[0]) * iboData.size(),
|
|
|
|
iboData.data(), GL_STATIC_DRAW);
|
|
|
|
// Next in our render loop, we replace glDrawArrays() with:
|
|
|
|
glDrawElements(GL_TRIANGLES, iboData.size(), GL_UNSIGNED_INT, nullptr);
|
|
|
|
// Remember to delete the allocated memory for the IBO.
|
|
|
|
```
|
|
|
|
|
|
|
|
You can find the current code here: [OpenGL - 2](https://pastebin.com/R3Z9ACDE).
|
|
|
|
|
|
|
|
## Textures
|
|
|
|
|
|
|
|
To load out texture we first need a library that loads the data, for simplicity I will be
|
|
|
|
using SFML, however there are a lot of librarys for loading image data.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// Lets save we have a texture called "my_tex.tga", we can load it with:
|
|
|
|
sf::Image image;
|
|
|
|
image.loadFromFile("my_tex.tga");
|
|
|
|
// We have to flip the texture around the y-Axis, because OpenGL's texture
|
|
|
|
// origin is the bottom left corner, not the top left.
|
|
|
|
image.flipVertically();
|
|
|
|
// After loading it we have to create a OpenGL texture.
|
|
|
|
GLuint texture = 0;
|
|
|
|
glGenTextures(1, &texture);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
|
|
// Specify what happens when the coordinates are out of range.
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
|
|
// Specify the filtering if the object is very large.
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
// Load the image data to the texture.
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getSize().x, image.getSize().y,
|
|
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, image.getPixelsPtr());
|
|
|
|
// Unbind the texture to prevent modifications.
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
// Delete the texture at the end of the application.
|
|
|
|
// ...
|
|
|
|
glDeleteTextures(1, &texture);
|
|
|
|
```
|
|
|
|
|
2024-09-30 23:03:06 +00:00
|
|
|
Of course there are more texture formats than only 2D textures,
|
2024-04-04 07:27:01 +00:00
|
|
|
You can find further information on parameters here:
|
|
|
|
[glBindTexture - OpenGL Refpage](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindTexture.xhtml)<br>
|
|
|
|
[glTexImage2D - OpenGL Refpage](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml)<br>
|
|
|
|
[glTexParameter - OpenGL Refpage](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexParameter.xhtml)<br>
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// With the texture created, we now have to specify the UV,
|
|
|
|
// or in OpenGL terms ST coordinates.
|
|
|
|
std::vector<float> texCoords {
|
|
|
|
// The texture coordinates have to match the triangles/quad
|
|
|
|
// definition.
|
|
|
|
0.0f, 1.0f, // start at top-left
|
|
|
|
0.0f, 0.0f, // go round counter-clockwise
|
|
|
|
1.0f, 0.0f,
|
|
|
|
1.0f, 1.0f // end at top-right
|
|
|
|
};
|
|
|
|
// Now we increase the VBO's size again just like we did for the colors.
|
|
|
|
// ...
|
|
|
|
GLuint vbo[3];
|
|
|
|
glGenBuffers(3, vbo);
|
|
|
|
// ...
|
|
|
|
glDeleteBuffers(3, vbo);
|
|
|
|
// ...
|
|
|
|
// Load the texture coordinates into the new buffer.
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords[0]) * texCoords.size(),
|
|
|
|
texCoords.data(), GL_STATIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(2);
|
|
|
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
|
|
|
|
// Because the VAO does not store the texture we have to bind it before drawing.
|
|
|
|
// ...
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
|
|
glDrawElements(GL_TRIANGLES, iboData.size(), GL_UNSIGNED_INT, nullptr);
|
|
|
|
// ...
|
|
|
|
```
|
|
|
|
|
|
|
|
Change the shaders to pass the data to the fragment shader.<br>
|
|
|
|
|
|
|
|
**Vertex Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
layout(location = 0) in vec3 position;
|
|
|
|
layout(location = 1) in vec3 color;
|
|
|
|
layout(location = 2) in vec2 texCoords;
|
|
|
|
|
|
|
|
out vec3 fColor;
|
|
|
|
out vec2 fTexCoords;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
fColor = color;
|
|
|
|
fTexCoords = texCoords;
|
|
|
|
gl_Position = vec4(position, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**Fragment Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
// sampler2D represents our 2D texture.
|
|
|
|
uniform sampler2D tex;
|
|
|
|
uniform float time;
|
|
|
|
|
|
|
|
in vec3 fColor;
|
|
|
|
in vec2 fTexCoords;
|
|
|
|
|
|
|
|
out vec4 outColor;
|
|
|
|
|
|
|
|
void main() {
|
2024-09-30 23:03:06 +00:00
|
|
|
// texture() loads the current texture data at the specified texture coords,
|
2024-04-04 07:27:01 +00:00
|
|
|
// then we can simply multiply them by our color.
|
|
|
|
outColor = texture(tex, fTexCoords) * vec4(fColor, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
You can find the current code here: [OpenGL - 3](https://pastebin.com/u3bcwM6q)
|
|
|
|
|
|
|
|
## Matrix Transformation
|
|
|
|
|
|
|
|
**Vertex Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
layout(location = 0) in vec3 position;
|
|
|
|
layout(location = 1) in vec3 color;
|
|
|
|
layout(location = 2) in vec2 texCoords;
|
|
|
|
// Create 2 4x4 matricies, 1 for the projection matrix
|
|
|
|
// and 1 for the model matrix.
|
|
|
|
// Because we draw in a static scene, we don't need a view matrix.
|
|
|
|
uniform mat4 projection;
|
|
|
|
uniform mat4 model;
|
|
|
|
|
|
|
|
out vec3 fColor;
|
|
|
|
out vec2 fTexCoords;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
fColor = color;
|
|
|
|
fTexCoords = texCoords;
|
|
|
|
// Multiplay the position by the model matrix and then by the
|
|
|
|
// projection matrix.
|
|
|
|
// Beware order of multiplication for matricies!
|
|
|
|
gl_Position = projection * model * vec4(position, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
In our source we now need to change the vertex data, create a model- and a projection matrix.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// The new vertex data, counter-clockwise declaration.
|
|
|
|
std::vector<float> vertexData {
|
|
|
|
0.0f, 1.0f, 0.0f, // top left
|
|
|
|
0.0f, 0.0f, 0.0f, // bottom left
|
|
|
|
1.0f, 0.0f, 0.0f, // bottom right
|
|
|
|
1.0f, 1.0f, 0.0f // top right
|
|
|
|
};
|
|
|
|
// Request the location of our matricies.
|
|
|
|
GLint projectionLocation = glGetUniformLocation(program, "projection");
|
|
|
|
GLint modelLocation = glGetUniformLocation(program, "model");
|
|
|
|
// Declaring the matricies.
|
|
|
|
// Orthogonal matrix for a 1024x768 window.
|
|
|
|
std::vector<float> projection {
|
|
|
|
0.001953f, 0.0f, 0.0f, 0.0f,
|
|
|
|
0.0f, -0.002604f, 0.0f, 0.0f,
|
|
|
|
0.0f, 0.0f, -1.0f, 0.0f,
|
|
|
|
-1.0f, 1.0f, 0.0f, 1.0f
|
|
|
|
};
|
|
|
|
// Model matrix translating to x 50, y 50
|
|
|
|
// and scaling to x 200, y 200.
|
|
|
|
std::vector<float> model {
|
|
|
|
200.0f, 0.0f, 0.0f, 0.0f,
|
|
|
|
0.0f, 200.0f, 0.0f, 0.0f,
|
|
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
|
|
50.0f, 50.0f, 0.0f, 1.0f
|
|
|
|
};
|
|
|
|
// Now we can send our calculated matricies to the program.
|
|
|
|
glUseProgram(program);
|
|
|
|
glUniformMatrix4fv(projectionLocation, // location
|
|
|
|
1, // count
|
|
|
|
GL_FALSE, // transpose the matrix
|
|
|
|
projection.data()); // data
|
|
|
|
glUniformMatrix4fv(modelLocation, 1, GL_FALSE, model.data());
|
|
|
|
glUseProgram(0);
|
|
|
|
// The glUniform*() calls have to be done, while the program is bound.
|
|
|
|
```
|
|
|
|
|
|
|
|
The application should now display the texture at the defined position and size.<br>
|
|
|
|
You can find the current code here: [OpenGL - 4](https://pastebin.com/9ahpFLkY)
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
// There are many math librarys for OpenGL, which create
|
|
|
|
// matricies and vectors, the most used in C++ is glm (OpenGL Mathematics).
|
|
|
|
// Its a header only library.
|
|
|
|
// The same code using glm would look like:
|
|
|
|
glm::mat4 projection{ glm::ortho(0.0f, 1024.0f, 768.0f, 0.0f) };
|
|
|
|
glUniformMatrix4fv(projectionLocation, 1, GL_FALSE,
|
|
|
|
glm::value_ptr(projection));
|
|
|
|
// Initialise the model matrix to the identity matrix, otherwise every
|
|
|
|
// multiplication would be 0.
|
|
|
|
glm::mat4 model{ 1.0f };
|
|
|
|
model = glm::translate(model, glm::vec3{ 50.0f, 50.0f, 0.0f });
|
|
|
|
model = glm::scale(model, glm::vec3{ 200.0f, 200.0f, 0.0f });
|
|
|
|
glUniformMatrix4fv(modelLocation, 1, GL_FALSE,
|
|
|
|
glm::value_ptr(model));
|
|
|
|
```
|
|
|
|
|
|
|
|
## Geometry Shader
|
|
|
|
|
|
|
|
Geometry shaders were introduced in OpenGL 3.2, they can produce vertices
|
|
|
|
that are send to the rasterizer. They can also change the primitive type e.g.
|
|
|
|
they can take a point as an input and output other primitives.
|
|
|
|
Geometry shaders are inbetween the vertex and the fragment shader.
|
|
|
|
|
|
|
|
**Vertex Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
layout(location = 0) in vec3 position;
|
|
|
|
layout(location = 1) in vec3 color;
|
2024-09-30 23:03:06 +00:00
|
|
|
// Create an output interface block passed to the next shader stage.
|
2024-04-04 07:27:01 +00:00
|
|
|
// Interface blocks can be used to structure data passed between shaders.
|
|
|
|
out VS_OUT {
|
|
|
|
vec3 color;
|
|
|
|
} vs_out;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
vs_out.color = color
|
|
|
|
gl_Position = vec4(position, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**Geometry Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
#version 330 core
|
|
|
|
// The geometry shader takes in points.
|
|
|
|
layout(points) in;
|
|
|
|
// It outputs a triangle every 3 vertices emitted.
|
|
|
|
layout(triangle_strip, max_vertices = 3) out;
|
|
|
|
// VS_OUT becomes an input variable in the geometry shader.
|
|
|
|
// Every input to the geometry shader in treated as an array.
|
|
|
|
in VS_OUT {
|
|
|
|
vec3 color;
|
|
|
|
} gs_in[];
|
|
|
|
// Output color for the fragment shader.
|
|
|
|
// You can also simply define color as 'out vec3 color',
|
|
|
|
// If you don't want to use interface blocks.
|
|
|
|
out GS_OUT {
|
|
|
|
vec3 color;
|
|
|
|
} gs_out;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
// Each emit calls the fragment shader, so we set a color for each vertex.
|
|
|
|
gs_out.color = mix(gs_in[0].color, vec3(1.0, 0.0, 0.0), 0.5);
|
|
|
|
// Move 0.5 units to the left and emit the new vertex.
|
|
|
|
// gl_in[] is the current vertex from the vertex shader, here we only
|
|
|
|
// use 0, because we are receiving points.
|
|
|
|
gl_Position = gl_in[0].gl_Position + vec4(-0.5, 0.0, 0.0, 0.0);
|
|
|
|
EmitVertex();
|
|
|
|
gs_out.color = mix(gs_in[0].color, vec3(0.0, 1.0, 0.0), 0.5);
|
|
|
|
// Move 0.5 units to the right and emit the new vertex.
|
|
|
|
gl_Position = gl_in[0].gl_Position + vec4(0.5, 0.0, 0.0, 0.0);
|
|
|
|
EmitVertex();
|
|
|
|
gs_out.color = mix(gs_in[0].color, vec3(0.0, 0.0, 1.0), 0.5);
|
|
|
|
// Move 0.5 units up and emit the new vertex.
|
|
|
|
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.75, 0.0, 0.0);
|
|
|
|
EmitVertex();
|
|
|
|
EndPrimitive();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**Fragment Shader**
|
|
|
|
|
|
|
|
```glsl
|
|
|
|
in GS_OUT {
|
|
|
|
vec3 color;
|
|
|
|
} fs_in;
|
|
|
|
|
|
|
|
out vec4 outColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
outColor = vec4(fs_in.color, 1.0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If you now store a single point with a single color in a VBO and draw them,
|
|
|
|
you should see a triangle, with your color mixed half way between
|
|
|
|
red, green and blue on each vertex.
|
|
|
|
|
|
|
|
|
|
|
|
## Quotes
|
|
|
|
<sup>[1]</sup>[OpenGL - Wikipedia](https://en.wikipedia.org/wiki/OpenGL)
|
|
|
|
|
|
|
|
## Books
|
|
|
|
|
|
|
|
- OpenGL Superbible - Fifth Edition (covering OpenGL 3.3)
|
|
|
|
- OpenGL Programming Guide - Eighth Edition (covering OpenGL 4.3)
|