I am working on an OpenGL 2D game with sprite graphics. I was recently advised that I should use OpenGL ES calls as it is a subset of OpenGL and would allow me to port it more easily to mobile platforms. The majority of the code is just calls to a draw_image function, which is defined so:
void draw_img(float x, float y, float w, float h, GLuint tex,float r=1,float g=1, float b=1) {
glColor3f(r,g,b);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f( x, y);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(w+x, y);
glTexCoord2f(1.0f, 1.0f);
glVertex2f( w+x, h+y);
glTexCoord2f(0.0f, 1.0f);
glVertex2f( x, h+y);
glEnd();
}
What do I need to change to make this OpenGL ES compatible? Also, the reason I am using fixed-function rather than shaders is that I am developing on a machine which doesn't support GLSL.
In OpenGL ES 1.1 use the glVertexPointer(), glColorPointer(), glTexCoordPointer() and glDrawArrays() functions to draw a quad. In contrast to your OpenGL implementation, you will have to describe the structures (vectors, colors, texture coordinates) that your quad consists of instead of just using the built-in glTexCoord2f, glVertex2f and glColor3f methods.
Here is some example code that should do what you want. (I have used the argument names you used in your function definition, so it should be simple to port your code from the example.)
First, you need to define a structure for one vertex of your quad. This will hold the quad vertex positions, colors and texture coordinates.
// Define a simple 2D vector
typedef struct Vec2 {
float x,y;
} Vec2;
// Define a simple 4-byte color
typedef struct Color4B {
GLbyte r,g,b,a;
};
// Define a suitable quad vertex with a color and tex coords.
typedef struct QuadVertex {
Vec2 vect; // 8 bytes
Color4B color; // 4 bytes
Vec2 texCoords; // 8 bytes
} QuadVertex;
Then, you should define a structure describing the whole quad consisting of four vertices:
// Define a quad structure
typedef struct Quad {
QuadVertex tl;
QuadVertex bl;
QuadVertex tr;
QuadVertex br;
} Quad;
Now, instantiate your quad and assign quad vertex information (positions, colors, texture coordinates):
Quad quad;
quad.bl.vect = (Vec2){x,y};
quad.br.vect = (Vec2){w+x,y};
quad.tr.vect = (Vec2){w+x,h+y};
quad.tl.vect = (Vec2){x,h+y};
quad.tl.color = quad.tr.color = quad.bl.color = quad.br.color
= (Color4B){r,g,b,255};
quad.tl.texCoords = (Vec2){0,0};
quad.tr.texCoords = (Vec2){1,0};
quad.br.texCoords = (Vec2){1,1};
quad.bl.texCoords = (Vec2){0,1};
Now tell OpenGL how to draw the quad. The calls to gl...Pointer provide OpenGL with the right offsets and sizes to your vertex structure's values, so it can later use that information for drawing the quad.
// "Explain" the quad structure to OpenGL ES
#define kQuadSize sizeof(quad.bl)
long offset = (long)&quad;
// vertex
int diff = offsetof(QuadVertex, vect);
glVertexPointer(2, GL_FLOAT, kQuadSize, (void*)(offset + diff));
// color
diff = offsetof(QuadVertex, color);
glColorPointer(4, GL_UNSIGNED_BYTE, kQuadSize, (void*)(offset + diff));
// texCoods
diff = offsetof(QuadVertex, texCoords);
glTexCoordPointer(2, GL_FLOAT, kQuadSize, (void*)(offset + diff));
Finally, assign the texture and draw the quad. glDrawArrays tells OpenGL to use the previously defined offsets together with the values contained in your Quad object to draw the shape defined by 4 vertices.
glBindTexture(GL_TEXTURE_2D, tex);
// Draw the quad
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_2D, 0);
Please also note that it is perfectly OK to use OpenGL ES 1 if you don't need shaders. The main difference between ES1 and ES2 is that, in ES2, there is no fixed pipeline, so you would need to implement a matrix stack plus shaders for the basic rendering on your own. If you are fine with the functionality offered by the fixed pipeline, just use OpenGL ES 1.
Related
I just started learning opengl technology.
My program draw 2d isometric tiles and program output this:
Be unknown reasons black lines appear when two textures overlap or two textures touch.
Code example:
typedef unsigned int ID;
class GraphicEngine {
public:
GraphicEngine();
~GraphicEngine();
void initShaders(const char* vertexShaderSource, const char* fragmentShaderSource);
void initRenderData(float vertices[], unsigned int size);
std::vector<ID> initTextures(std::vector<std::string>& paths);
void drawTextures(std::vector<ID> testuresIds);
private:
GraphicEngine(GraphicEngine&) = delete;
GraphicEngine(GraphicEngine&&) = delete;
GraphicEngine& operator=(const GraphicEngine& other) = delete;
private:
unsigned int VBO = 0;
unsigned int VAO = 0;
unsigned int EBO = 0;
unsigned int shaderProgram;
};
GraphicEngine::GraphicEngine() {
}
GraphicEngine::~GraphicEngine() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
}
void GraphicEngine::initShaders(const char* vertexShaderSource, const char* fragmentShaderSource) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
shaderProgram = glCreateProgram();
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
}
void GraphicEngine::initRenderData(float vertices[], unsigned int size) {
unsigned int indices[] = {
0, 1, 3,
1, 2, 3
};
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
}
std::vector<ID> GraphicEngine::initTextures(std::vector<std::string>& paths) {
std::vector<ID> ids(paths.size());
stbi_set_flip_vertically_on_load(true);
for (int i = 0; i < paths.size(); i++) {
unsigned int texture;
glGenTextures(1, &ids[i]);
glBindTexture(GL_TEXTURE_2D, ids[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int width, height, nrChannels;
unsigned char* data = stbi_load(paths[i].c_str(), &width, &height, &nrChannels, STBI_rgb_alpha);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
stbi_image_free(data);
}
return ids;
}
void GraphicEngine::drawTextures(std::vector<ID> testuresIds) {
static bool ex = false;
for (auto testureId : testuresIds) {
for (int i = 0; i < 4; i++) {
glBindTexture(GL_TEXTURE_2D, testureId);
glm::mat4 transform = glm::mat4(1.0f);
transform = glm::translate(transform, glm::vec3(i * 0.6f + 0.0f, 0.0f, 0.0f));
glUseProgram(shaderProgram);
unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
for (int i = 0; i < 4; i++) {
glBindTexture(GL_TEXTURE_2D, testureId);
glm::mat4 transform = glm::mat4(1.0f);
transform = glm::translate(transform, glm::vec3(i * 0.6f - 0.3f, -0.16f, 0.0f));
glUseProgram(shaderProgram);
unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
}
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
Window::Window():window(nullptr) {}
Window::~Window() {
glfwTerminate();
}
bool Window::initWindowResources() {
bool result = false;
if (glfwInit() == GLFW_TRUE) {
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window != nullptr) {
glfwMakeContextCurrent(window);
if (glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height); }) == NULL) {
if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
result = true;
}
}
}
}
return result;
}
const char* vertexShaderSource =
"#version 330 core\n"
"layout(location = 0) in vec3 aPos;\n"
"layout(location = 1) in vec2 aTexCoord;\n"
"out vec2 TexCoord;\n"
"uniform mat4 transform;\n"
"void main()\n"
"{\n"
" gl_Position = transform * vec4(aPos, 1.0);\n"
" TexCoord = vec2(aTexCoord.x, aTexCoord.y);\n"
"}\n\0";
const char* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"in vec2 TexCoord;\n"
"uniform sampler2D texture1;\n"
"void main()\n"
"{\n"
" FragColor = texture(texture1, TexCoord);\n"
"}\n\0";
void Window::mainWindowLoop() {
graphicEngine.initShaders(vertexShaderSource, fragmentShaderSource);
std::vector<std::string> pathsTextures = { "C:\\Users\\Олег\\\Desktop\\sea1.png" };
float vertices[] = {
// positions // colors // texture coords
-1.3f, 0.16f, 0.0f, 1.0f, 1.0f, // top right
-1.3f, -0.16f, 0.0f, 1.0f, 0.0f, // bottom right
-0.7f, -0.16f, 0.0f, 0.0f, 0.0f, // bottom left
-0.7f, 0.16f, 0.0f, 0.0f, 1.0f // top left
};
graphicEngine.initRenderData(vertices, sizeof(vertices));
std::vector<ID> idsTextures = graphicEngine.initTextures(pathsTextures);
while (!glfwWindowShouldClose(window))
{
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
graphicEngine.drawTextures(idsTextures);
glfwSwapBuffers(window);
glfwPollEvents();
}
}
int main()
{
Window window;
if (window.initWindowResources()) {
window.mainWindowLoop();
}
return 0;
}
Png: Size png: 62x34 pixels, Transparent sprite, use prog to created png: piskelapp
Please, pvoide information about this issue: inforamtion about reasons of this issue and how to fix this issue.
I was able to reproduce your issue. You are working with non-premultiplied alpha, this is known for producing undesirable results when rendering translucent images.
Take a look at this article: http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
Now, to solve your problem, first change your blend function to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
Second, stbi doesn't pre-multiply the alpha on load, you have to do it manually.
Each pixel is composed by 4 bytes, red, green, blue and alpha, on the 0-255 range. Convert each value to the normalized range (0.0f - 1.0f) by dividing by 255.0f, multiply r, g, and b by alpha, then multiply it back by 255.0f;
The dark lines at the edge of the tiles are results of alpha blending and texture filtering.
The linked tile image (PNG) contains three premultipled color channels (red, green, blue) and transparency information (alpha channel) with no partially transparent pixels (the alpha value is either 1.0 or 0.0 everywhere, which results in sharp edges):
This can be checked in an image editor (for example Gimp). The image uses premultiplied alpha, i.e. the color channels were masked by the alpha channel and only contain color information where the alpha channel is non-zero.
The area outside of the valid image region is all black, so when OpenGL uses linear texture interpolation (GL_LINEAR) it will mix the hidden black texels right at the edge with the visible colored texels, which can result in a dark color, depending on the used blending function.
Alpha blending mixes the already present color in the framebuffer (of the cleared background or the already written fragments) with the incoming ones.
The used blending function glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) instructs the hardware to do this for every pixel:
The result: dark artifacts at the edges of each tile caused by the interpolated alpha value at the edges of the tile, which darkens the source color (sRGB * sA) (modified example with original tile image, reproduced issue from the original post):
In other words:
https://shawnhargreaves.com/blog/texture-filtering-alpha-cutouts.html:
Texture filtering: alpha cutouts
(...)
Filtering applies equally to the RGB and alpha channels. When used on the alpha
channel of a cutout texture it will produce new fractional alpha
values around the edge of the shape, which makes things look nice and
antialiased. But filtering also produces new RGB colors, part way in
between the RGB of the solid and transparent parts of the texture.
Thus the RGB values of supposedly transparent pixels can bleed into
our final image.
This most often results in dark borders around alpha cutouts, since
the RGB of transparent pixels is often black. Depending on the
texture, the bleeding could alternatively be white, pink, etc.
To quick-fix the problem, the blending function could simply by changed to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA), since the tile image already has premultiplied RGB channels, which are all black (= 0) in transparent areas:
https://shawnhargreaves.com/blog/premultiplied-alpha.html:
Premultiplied alpha is better than conventional blending for several
reasons:
It works properly when filtering alpha cutouts (...)
It works properly when doing image composition (...)
It is a superset of both conventional and additive blending. If you set alpha to zero while RGB is non zero, you get an additive
blend. This can be handy for particle systems that want to smoothly
transition from additive glowing sparks to dark pieces of soot as the
particles age.
The result: dark artifacts disappear almost entirely after changing the blending function (modified example with original tile image, issue partially fixed):
Not perfect.
To fix this, some pixels could be drawn around the tile to enlarge the visible area a bit:
To let tiles overlap a bit, like that:
The result (with texture filtering, and overlapped pixels):
(Additionally, lines/other graphical elements could be drawn on top of the artifacts to cover them up. And if the pixelated jagged edges are not wanted, the actual textured polygons quads could be replaced by rhombuses that could be placed precisely next to each other in a continuous mesh that could be rendered in one draw call, no alpha blending required anymore, however sharp edges do not fit a pixelated look I guess.)
A possible solution using GL_NEAREST:
OpenGL texture parameters:
To get rid of the artefacts and blurred/filtered look, GL_LINEAR can be replaced by GL_NEAREST, which disables texture interpolation altogether for the selected texture and lets OpenGL render the raw pixels without applying texture filtering (GL_CLAMP_TO_EDGE makes sense here to avoid artifacts at the edges):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
Power of Two Textures:
OpenGL performance can be improved by always using texture dimensions that
are a power of two, e.g. 64x32 (instead of 60x32 in your case). - The tile image could be modified, e.g.: 2 pixels added on each side (and borders marked):
Side note: This restriction is not that important anymore, but in the past it was even necessary to use a special extension to enable NPOT textures:
Conventional OpenGL texturing is limited to images with
power-of-two dimensions and an optional 1-texel border.
ARB_texture_non_power_of_two extension relaxes the size restrictions
for the 1D, 2D, cube map, and 3D texture targets.
Snap to pixel:
There are multiple ways to do this with OpenGL.
I would recommend to scale the orthographic projection, so that 1 OpenGL
coordinate unit exactly matches 1 texel unit. That way, tiles can be precisely placed on the pixel grid (just shift coordinates of the tile vertices by 64 pixels/OpenGL units left/right, to get to the next one, in this example). Coordinates could be represented as integers in the engine now.
Modified code example:
void GraphicEngine::drawTextures(std::vector<ID> testuresIds, float wndRatio) {
const int countx = 3, county = 3; /* number of tiles */
const float scale = 100.0f; /* zoom */
const glm::mat4 mvp = glm::ortho(-wndRatio * scale, wndRatio * scale, -scale, scale, 2.0f, -2.0f);
const float offx = -((countx * TILE_WIDTH * 2.0f) * 0.5f - TILE_WIDTH);
const float offy = -TILE_WIDTH * 0.5f;
for (auto testureId : testuresIds) {
for (int y = 0; y < county; y++) {
for (int x = 0; x < countx - (y & 1 ? 1 : 0); x++) {
const glm::mat4 transform = mvp * glm::translate(glm::mat4(1.0f), glm::vec3(
offx + x * TILE_WIDTH * 2.0f + (y & 1 ? TILE_WIDTH : 0.0f),
offy + y * TILE_HEIGHT, 0.0f));
glBindTexture(GL_TEXTURE_2D, testureId);
const GLint transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
}
}
}
Screenshot of modified example:
And without marked edges:
Some hints on the use of "straight alpha textures":
Another approach to solve this might be the use of an unmasked/unpremultiplied/straight alpha texture. The color channels of the original tile image can be flood filled out like this:
(Note: The linked PNG image above can't be used directly. Imgur seems to convert transparent PNG images and automatically masks the color channels...)
This technique could help to reduce the artifacts when texture filtering and the conventional alpha blending function is used (i.e. GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). However, the background will always show through a tiny bit, because some pixels are always sightly transparent at the edges (caused by texture filtering):
(The result should be very similar to the first solution above, where the original premultiplied image is used with modified alpha blending (GL_ONE, GL_ONE_MINUS_SRC_ALPHA).)
If the tile contains not just a plain color, the color information at the edges of the tile would need to be extended outwards to avoid artifacts.
Obviously this doesn't solve the original issue completely, when a precise 2D look is the goal. But it could be useful in other situations, where the hidden pixels also generate bad results when other forms of transparency/blending/compositing are used, e.g. for particles, semi-transparent edges of foliage, text etc.
Some general hints that could help to solve the issue:
Check: glBlendFunc(), glBlendFuncSeparate(), glBlendEquation(), glBlendColor()
Do not waste precious video memory: glGenerateMipmap() is not required for a pure 2D presentation, where all pixels are visible all the time.
Alpha Testing: glAlphaFunc() has been removed from OpenGL 4, but alpha testing can be done manually in the fragment shader, just discard fragments depending on the alpha value, see OpenGL alpha test - How to replace AlphaFunc deprecated?.
glHint(): Using this OpenGL function to change implementation-specific hints can have an impact on the rendered result, sometimes in "surprising ways":
GL_POLYGON_SMOOTH_HINT
Indicates the sampling quality of antialiased polygons. Hinting
GL_NICEST can result in more pixel fragments being generated during
rasterization, if a larger filter function is applied.
The code in the original question comes with some issues, it does not compile like that (some parts of the class definitions are missing etc.). It takes some effort to reproduce to issue, which makes it more complicated to answer the question. - And it wasn't completely clear to me whether the intention is to just render seamless, pixelated tiles (solution: use GL_NEAREST), or if texture filtering is required...
Here my modified code example.
Related questions on Stack Overflow:
OpenGL normal blending with black alpha edges
opengl es2 premultiplied vs straight alpha + blending
Some links related to Alpha Blending / "premultiplied alpha":
Visual glBlendFunc + glBlendEquation Tool, by Anders Riggelsen
Premultiplied alpha, originally posted to Shawn Hargreaves Blog on MSDN, Friday, November 6, 2009
Texture filtering: alpha cutouts, originally posted to Shawn Hargreaves Blog on MSDN, Monday, November 2, 2009
What is Premultiplied Alpha? A Primer for Artists, by David Hart, July 6, 2016
GPUs prefer premultiplication, by Eric from www.realtimerendering.com
I'm trying to make a system that allows you to type in a position and scale and it will create a vector that automatically generates all the vertices. The problem is when I try to draw my object it just won't show up. I have used OpenGL's built-in debugging system but it didn't say anything was wrong. So then I tried to manually debug myself but everything seemed to draw just fine.
Renderer::createQuad() method:
Shape Renderer::createQuad(glm::vec2 position, glm::vec2 scale, Shader shader, Texture texture)
{
float x = position.x;
float y = position.y;
float width = scale.x;
float height = scale.y;
std::vector<float> vertices =
{
x+width, y+height, 1.0f, 1.0f, // TR
x+width, y-height, 1.0f, 0.0f, // BR
x-width, y-height, 0.0f, 0.0f, // BL
x-width, y+height, 0.0f, 1.0f // TL
};
std::vector<uint32_t> indices =
{
0, 1, 3,
1, 2, 3
};
m_lenVertices = vertices.size();
m_lenIndices = indices.size();
// these Create methods should be fine as OpenGL does not give me any error
// also I have another function that requires you to pass in the vertex data and indices that works just fine
// I bind the thing I am creating
createVAO();
createVBO(vertices);
createEBO(indices);
createTexture(texture);
createShader(shader.getVertexShader(), shader.getFragmentShader());
Shape shape;
glm::mat4 model(1.0f);
glUniformMatrix4fv(glGetUniformLocation(m_shader, "model"), 1, GL_FALSE, glm::value_ptr(model));
shape.setShader(m_shader);
shape.setVAO(m_VAO);
shape.setTexture(m_texture);
shape.setPosition(position);
return shape;
}
Renderer::draw() method:
void Renderer::draw(Shape shape)
{
if (!m_usingIndices)
{
// Unbinds any other shapes
glBindVertexArray(0);
glUseProgram(0);
shape.bindShader();
shape.bindVAO();
shape.bindTexture();
glDrawArrays(GL_TRIANGLES, 0, m_lenVertices);
}
else
{
// Unbinds any other shapes
glBindVertexArray(0);
glUseProgram(0);
shape.bindShader();
shape.bindVAO();
shape.bindTexture();
glDrawElements(GL_TRIANGLES, m_lenIndices, GL_UNSIGNED_INT, 0);
}
}
Projection matrix:
glm::mat4 m_projectionMat = glm::ortho(-Window::getWidth(), Window::getWidth(), -Window::getHeight(), Window::getHeight, 0.1f, 100.0f);
Creating then rendering the Quad:
// Creates the VBO, VAO, EBO, etc.
quad = renderer.createQuad(glm::vec2(500.0f, 500.0f), glm::vec2(200.0F, 200.0f), LoadFile::loadShader("Res/Shader/VertShader.glsl", "Res/Shader/FragShader.glsl"), LoadFile::loadTexture("Res/Textures/Lake.jpg"));
// In the main game loop we render the quad
quad.setCamera(camera); // Sets the View and Projection matrix for the quad
renderer.draw(quad);
Output:
Output of the code before
I take multiple images of the same mesh using OpenGL, GLEW and GLFW. The mesh (triangles) doesn't change in each shot, only the ModelViewMatrix does.
Here's the important code of my mainloop:
for (int i = 0; i < number_of_images; i++) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* set GL_MODELVIEW matrix depending on i */
glBegin(GL_TRIANGLES);
for (Triangle &t : mesh) {
for (Point &p : t) {
glVertex3f(p.x, p.y, p.z);
}
}
glReadPixels(/*...*/) // get picture and store it somewhere
glfwSwapBuffers();
}
As you can see, I set/transfer the triangle vertices for each shot I want to take. Is there a solution in which I only need to transfer them once? My mesh is quite large, so this transfer takes quite some time.
In the year 2016 you must not use glBegin/glEnd. No way. Use Vertex Array Obejcts instead; and use custom vertex and/or geometry shaders to reposition and modify your vertex data. Using these techniques, you will upload your data to the GPU once, and then you'll be able to draw the same mesh with various transformations.
Here is an outline of how your code may look like:
// 1. Initialization.
// Object handles:
GLuint vao;
GLuint verticesVbo;
// Generate and bind vertex array object.
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Generate a buffer object.
glGenBuffers(1, &verticesVbo);
// Enable vertex attribute number 0, which
// corresponds to vertex coordinates in older OpenGL versions.
const GLuint ATTRIBINDEX_VERTEX = 0;
glEnableVertexAttribArray(ATTRIBINDEX_VERTEX);
// Bind buffer object.
glBindBuffer(GL_ARRAY_BUFFER, verticesVbo);
// Mesh geometry. In your actual code you probably will generate
// or load these data instead of hard-coding.
// This is an example of a single triangle.
GLfloat vertices[] = {
0.0f, 0.0f, -9.0f,
0.0f, 0.1f, -9.0f,
1.0f, 1.0f, -9.0f
};
// Determine vertex data format.
glVertexAttribPointer(ATTRIBINDEX_VERTEX, 3, GL_FLOAT, GL_FALSE, 0, 0);
// Pass actual data to the GPU.
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*3*3, vertices, GL_STATIC_DRAW);
// Initialization complete - unbinding objects.
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 2. Draw calls.
while(/* draw calls are needed */) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(vao);
// Set transformation matrix and/or other
// transformation parameters here using glUniform* calls.
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0); // Unbinding just as an example in case if some other code will bind something else later.
}
And a vertex shader may look like this:
layout(location=0) in vec3 vertex_pos;
uniform mat4 viewProjectionMatrix; // Assuming you set this before glDrawArrays.
void main(void) {
gl_Position = viewProjectionMatrix * vec4(vertex_pos, 1.0f);
}
Also take a look at this page for a good modern accelerated graphics book.
#BDL already commented that you should abandon the immediate mode drawing calls (glBegin … glEnd) and switch to Vertex Array drawing (glDrawElements, glDrawArrays) that fetch their data from Vertex Buffer Objects (VBOs). #Sergey mentioned Vertex Array Objects in his answer, but those are actually state containers for VBOs.
A very important thing you have to understand – and the way you asked your question it's apparently something you're not aware of, yet – is, that OpenGL does not deal with "meshes", "scenes" or the like. OpenGL is just a drawing API. It draws points… lines… and triangles… one at a time… with no connection between them whatsoever. That's it. So when you show multiple views of the "same" thing, you must draw it several times. There's no way around this.
Most recent versions of OpenGL support multiple viewport rendering, but it still takes a geometry shader to multiply the geometry into several pieces to be drawn.
So I have a very complicated R^4 -> R^4 function, which I need to calculate for a lot of input glm::vec4s, in real time, so I want to do it on the GPU, for all vec4s parallel.
What I figured is that I would create a GL_RGBA32F texture, 1920x1 resolution (1920 is enough for my purposes), copy my input data onto the texture, then call a drawing of a line, so the rasterizer calls a fragment for each of my vec4s. Then either write the results back to the texture using imageload/store or render it to a 1920x1 framebuffer, and read it from there.
Problem is that for some reason opengl can't read my GL_RGBA32F texture.
Here is my code:
Setting up the texture (currently loaded with dummy data):
glm::vec4 texturedata[1920];
for (unsigned int i = 0; i < 1920; i++)
{
texturedata[i] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
glGenTextures(1, &datatexture);
glBindTexture(GL_TEXTURE_2D, datatexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1920, 1, 0, GL_RGBA, GL_FLOAT, texturedata);
Before each rendering:
glUseProgram(mprogram);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, datatexture);
glBindVertexArray(rasterizertriggervao);
glUniform1i(glGetUniformLocation(mprogram, "datatexture"), 0);
glDrawArrays(GL_LINES, 0, 2);
The rasterizertriggervao is 2 floats: -1, 1, and the vertex shader draws a nice line through the middle of my screen from that.
Fragment shader:
layout(binding = 0) uniform sampler2D datatexture;
out vec4 x;
void main()
{
x = vec4( (texture(datatexture, vec2(gl_FragCoord.x/1920.0, 0.0))).x, 0.0f, 0.0f, 1.0f );
}
So this should draw a nice red line in the middle of my screen for me. It draws a black one. The rasterizer called all 1920x1 fragments, and the texture is correctly copied to the GPU (I have Nvidia Nsight installed, which allows me to debug the GPU, check the contents of textures and whatnot on the GPU directly, and I checked, the texture is full of 1.0f).
However for some reason the sampling doesn't work.
I know that there are better ways to do GPGPU but this thing has to fit into a much bigger program nicely, and this is the way I need it to work, through textures :)
Your seem not to set the texture filter modes for your texture. Now, GL's defaults are (unfortunately) to use mip-mapping. But your texture is not mipmap-complete, so sampling from it will not work. You should add glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREAST) and probably also glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREAST).
As Andon M. Coleman already pointed out in the comments, you are not sampling the texture at the correct location. You should use vec2(gl_FragCoord.x/1920.0 + 0.5/1920.0, 0.5) in your case. I also agree with Andon M. Coleman's suggestion to directly use texelFetch(), since you can directly use the integer value `gl_FragCoord.x'.
I'm using glReadPixels to get depth value of select pixel, but i always get 1, how can i solve it? here is the code:
glEnable(GL_DEPTH_TEST);
..
glReadPixels(x, viewport[3] - y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, z);
Do I miss anything? And my rendering part is shown below. I use different shaders to draw different part of scene, so how should i make it correct to read depth value from buffer?
void onDisplay(void)
{
// Clear the window and the depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// calculate the view matrix.
GLFrame eyeFrame;
eyeFrame.MoveUp(gb_eye_height);
eyeFrame.RotateWorld(gb_eye_theta * 3.1415926 / 180.0, 1.0, 0.0, 0.0);
eyeFrame.RotateWorld(gb_eye_phi * 3.1415926 / 180.0, 0.0, 1.0, 0.0);
eyeFrame.MoveForward(-gb_eye_radius);
eyeFrame.GetCameraMatrix(gb_hit_modelview);
gb_modelViewMatrix.PushMatrix(gb_hit_modelview);
// draw coordinate system
if(gb_bCoord)
{
DrawCoordinateAxis();
}
if(gb_bTexture)
{
GLfloat vEyeLight[] = { -100.0f, 100.0f, 150.0f };
GLfloat vAmbientColor[] = { 0.2f, 0.2f, 0.2f, 1.0f };
GLfloat vDiffuseColor[] = { 1.0f, 1.0f, 1.0f, 1.0f};
glUseProgram(normalMapShader);
glUniform4fv(locAmbient, 1, vAmbientColor);
glUniform4fv(locDiffuse, 1, vDiffuseColor);
glUniform3fv(locLight, 1, vEyeLight);
glUniform1i(locColorMap, 0);
glUniform1i(locNormalMap, 1);
gb_treeskl.Display(SetGeneralColor, SetSelectedColor, 0);
}
else
{
if(!gb_bOnlyVoxel)
{
if(gb_bPoints)
{
//GLfloat vPointColor[] = { 1.0, 1.0, 0.0, 0.6 };
GLfloat vPointColor[] = { 0.2, 0.0, 0.0, 0.9 };
gb_shaderManager.UseStockShader(GLT_SHADER_FLAT, gb_transformPipeline.GetModelViewProjectionMatrix(), vPointColor);
gb_treeskl.Display(NULL, NULL, 1);
}
if(gb_bSkeleton)
{
GLfloat vEyeLight[] = { -100.0f, 100.0f, 150.0f };
glUseProgram(adsPhongShader);
glUniform3fv(locLight, 1, vEyeLight);
gb_treeskl.Display(SetGeneralColor, SetSelectedColor, 0);
}
}
if(gb_bVoxel)
{
GLfloat vEyeLight[] = { -100.0f, 100.0f, 150.0f };
glUseProgram(adsPhongShader);
glUniform3fv(locLight, 1, vEyeLight);
SetVoxelColor();
glPolygonMode(GL_FRONT, GL_LINE);
glLineWidth(1.0f);
gb_treeskl.DisplayVoxel();
glPolygonMode(GL_FRONT, GL_FILL);
}
}
//glUniformMatrix4fv(locMVP, 1, GL_FALSE, gb_transformPipeline.GetModelViewProjectionMatrix());
//glUniformMatrix4fv(locMV, 1, GL_FALSE, gb_transformPipeline.GetModelViewMatrix());
//glUniformMatrix3fv(locNM, 1, GL_FALSE, gb_transformPipeline.GetNormalMatrix());
//gb_sphereBatch.Draw();
gb_modelViewMatrix.PopMatrix();
glutSwapBuffers();
}
I think you are reading correctly the only problem is that you are not linearize the depth from buffer back to <znear...zfar> range hence the ~1 value for whole screen due to logarithmic dependence of depth (almost all the values are very close to 1).
I am doing this like this:
double glReadDepth(double x,double y,double *per=NULL) // x,y [pixels], per[16]
{
GLfloat _z=0.0; double m[16],z,zFar,zNear;
if (per==NULL){ per=m; glGetDoublev(GL_PROJECTION_MATRIX,per); } // use actual perspective matrix if not passed
zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0))); // compute zFar from perspective matrix
zNear=zFar*(per[10]+1.0)/(per[10]-1.0); // compute zNear from perspective matrix
glReadPixels(x,y,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&_z); // read depth value
z=_z; // logarithmic
z=(2.0*z)-1.0; // logarithmic NDC
z=(2.0*zNear*zFar)/(zFar+zNear-(z*(zFar-zNear))); // linear <zNear,zFar>
return -z;
}
Do not forget that x,y is in pixels and (0,0) is bottom left corner !!! The returned depth is in range <zNear,zFar>. The function is assuming you are using perspective transform like this:
void glPerspective(double fovy,double aspect,double zNear,double zFar)
{
double per[16],f;
for (int i=0;i<16;i++) per[i]=0.0;
// original gluProjection
// f=divide(1.0,tan(0.5*fovy*deg))
// per[ 0]=f/aspect;
// per[ 5]=f;
// corrected gluProjection
f=divide(1.0,tan(0.5*fovy*deg*aspect));
per[ 0]=f;
per[ 5]=f*aspect;
// z range
per[10]=divide(zFar+zNear,zNear-zFar);
per[11]=-1.0;
per[14]=divide(2.0*zFar*zNear,zNear-zFar);
glLoadMatrixd(per);
}
Beware the depth accuracy will be good only for close to camera object without linear depth buffer. For more info see:
How to correctly linearize depth in OpenGL ES in iOS?
If the problem persist there might be also another reason for this. Do you have Depth buffer in your pixel format? In windows You can check like this:
Getting a window's pixel format
Missing depth buffer could explain why the value is always 1 (not like ~0.997). In such case you need to change the init of your window enabling some bits for depth buffer (16/24/32). See:
What is the proper OpenGL initialisation on Intel HD 3000?
For more detailed info about using this technique (with C++ example) see:
OpenGL 3D-raypicking with high poly meshes
Well, you missed to past the really relevent parts of the code. Also the status of the depth testing unit has no influence on what glReadPixels delivers. How about you post your rendering code as well.
Update
After a buffer swap SwapBuffers the contents of the back buffer are undefined and the default state for frame buffer reads is to read from the back buffer. Technically double buffering happens on only the color component, not the depth and stencil component. But you might run into a driver issue with that.
I suggest two tests to rule out those:
Do a read of the depth buffer with glReadBuffer(GL_BACK); right before the SwapBuffers.
Select the front buffer with glReadBuffer(GL_FRONT); for reading after SwapBuffers
Also please specify in which context (program, not OpenGL, well the later, too) you did your glReadPixels when this problem occours. Also check if you can read color value correctly.