# OpenGL: Better Approach to Repeating Sprites (e.g. terrain)?

Preface: Linux (Ubuntu 16.04 Gnome), C++11, SDL2, GLEW 2.0, OpenGL 4.5

My conundrum is this: How do I handle draw a repeated texture (sprite) as a single object, at various dimensions and rotations, without manually draw each tile?

I posted a similar question earlier about rotating a row of textures, to which the user Lasse provided insight on utilizing matrices. After two days of reading up on (and playing with) glm::translate and glm::rotate, I have not been able to rotate a row of sprites in place (as they always scale off of the 0,0 origin in center of screen, regardless of translations, producing increasingly less desirable motions). Additionally, rotating one seems to be rotating all sprites, but I am suspect that this is related to how I am drawing the sprites in batches. My understanding of uniforms is still, admittedly, embarrassing. I can provide snippets of the process involving the uniforms and the actual drawing, if it would help.

Is it possible to utilize uniforms and glm::translate/glm::rotate to rotate specific objects (such as level terrain pieces on a platformer)? I have found multiple examples through tutorials online, but almost all utilize a single object, with normalized device coordinates, and never with any type of moving/scaling projection matrix (read: camera).

I had a thought about potentially "drawing" the object and creating a 2D texture from the "drawn" object, so it could simply be titled as a single object with some shenanigans against the vertices. This is another, potential approach but I am unsure how useful (or treacherous) it is to implement.

If it's not abundantly clear by this point, my OpenGL understanding is woefully unequal to what I am attempting, but learning occurs best for me when I'm buried, lost, and overwhelmed in a new topic. I greatly appreciate any and all incoming feedback, advice, and information! :)

EDIT: Thanks to MalphasWats for pointing out something, I'm using glm::translate and glm::rotate, not glTranslate and glRotate. From reading so many dated tutorials, I had a mental 'slip', so to speak.

From MalphasWats' other comment regarding uniforms applying to the entire buffer waiting to be drawn, that does help explain some "oddities". It gives me some ideas on how to approach it as simply changing the rotation for each terrain object (across dozens of terrain objects) is simply not feasible as it would result in many extra draw calls for one or a few objects at at time.

The current 'workflow' (at least the relevant bits) for my level editor looks something like this:

• Camera is created and initialized on screen creation (at 1440x900 with 32x scale for no particular reason)
• update() function is called (GUI updates to position, size, etc; FPS calculation; updates to objects movement)
• draw() function is called. Here is where my repeating texture drawing (and issue) occurs. During the draw function, one of three sprite drawing actions happens (normal sprite, rotated single-sprite, repeating-sprite).
• draw() function is wrapped up, with all spritebatches drawn.

Camera step:

void Camera2D::init(int scrWidth, int scrHeight)
{
m_screenWidth = scrWidth;
m_screenHeight = scrHeight;
m_orthoMatrix = glm::ortho(0.0f, static_cast<float>(m_screenWidth), 0.0f, static_cast<float>(m_screenHeight)); // building orthographic matrix
}

void Camera2D::update()
{
if(m_hasChanged)
{
// camera translation
glm::vec3 translate(-m_position.x + m_screenWidth/2, -m_position.y + m_screenHeight/2, 0.0f);
m_cameraMatrix = glm::translate(m_orthoMatrix, translate);
m_unscaledCameraMatrix = m_cameraMatrix;
// camera scale
glm::vec3 scale(m_scale, m_scale, 0.0f);
m_cameraMatrix = glm::scale(glm::mat4(1.0f), scale) * m_cameraMatrix;
m_hasChanged = false;
}
}


Omitting update() function as it's not relevant.

draw() function:

void LevelEditScreen::draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.45f, 0.5f, 0.55f, 1.0f);

m_textureProgram.use();

GLint textureUniform = m_textureProgram.getUniformLocation("mySampler");
glUniform1i(textureUniform, 0);
glActiveTexture(GL_TEXTURE0);

// camera matrix
glm::mat4 projectMatrix = m_camera.getCameraMatrix(); // mat4 ortho matrix (0, 1440, 0, 900), scaled by 32
GLint pUniform = m_textureProgram.getUniformLocation("translation");
glUniformMatrix4fv(pUniform, 1, GL_FALSE, &projectMatrix[0][0]);

// handle sprites
m_spriteBatch->begin();
m_spriteBatch->draw(glm::vec4(0.0f, 0.0f, 1.0f, 1.0f), m_cursor.uvRect, m_cursor.textureID, 0.0f, GameEngine101::COLOR_RED); // debug box, 1 "tile", red, at center
glm::vec4 sizeBounds = glm::vec4(0.0f);
if(!rbNoRepeat->isTicked())
{
// this step repeats the texture, by drawing numerous sprites together, in an instant burst
// NOTE: The associated drawing function is somewhat WIP, so it's messy, inefficient, and barely functional
sizeBounds = glm::vec4( m_cursor.destRect.x, m_cursor.destRect.y, m_cursor.repeatSize[0], m_cursor.repeatSize[1] );
m_spriteBatch->drawRepeat(m_cursor.destRect, m_cursor.uvRect, sizeBounds, m_cursor.textureID, 0.0f, m_cursor.color, m_cursor.rotation, projectMatrix, m_camera.getScale());
}
else
{
// draw a normal sprite that can be batch drawn
sizeBounds = glm::vec4( m_cursor.destRect.x, m_cursor.destRect.y, m_cursor.destRect.z, m_cursor.destRect.w );
m_spriteBatch->draw(m_cursor.destRect, m_cursor.uvRect, m_cursor.textureID, 0.0f, m_cursor.color, m_cursor.rotation);
}

// tidy up sprite batches, generate batches
m_spriteBatch->end();

// actually draw batches
m_spriteBatch->renderBatch();

m_textureProgram.unuse();

// debug rendering
m_debugRenderer->drawBox(sizeBounds, m_cursor.color, m_cursor.rotation);
m_debugRenderer->end();
m_debugRenderer->render(projectMatrix, 2.0f);

m_gui->draw();
}


Finally, the basic Glyph constructor and the drawRepeat() function:

Glyph::Glyph(const glm::vec4& destRect, const glm::vec4& uvRect, GLuint Texture, float Depth, const ColorRGBA8& color) : texture(Texture), depth(Depth)
{
// top left
topLeft.setPosition(destRect.x, destRect.y + destRect.w);
topLeft.setUV(uvRect.x, uvRect.y + uvRect.w);
topLeft.color = color;
// bottom left
bottomLeft.setPosition(destRect.x, destRect.y);
bottomLeft.setUV(uvRect.x, uvRect.y);
bottomLeft.color = color;
// top right
topRight.setPosition(destRect.x + destRect.z, destRect.y + destRect.w);
topRight.setUV(uvRect.x + uvRect.z, uvRect.y + uvRect.w);
topRight.color = color;
// bottom right
bottomRight.setPosition(destRect.x + destRect.z, destRect.y);
bottomRight.setUV(uvRect.x + uvRect.z, uvRect.y);
bottomRight.color = color;
}

void SpriteBatch::drawRepeat(const glm::vec4& glyphSize, const glm::vec4& uvRect, const glm::vec4& sizeBounds, GLuint texture, float depth, const ColorRGBA8& color, float angle, glm::mat4& translation, float scale) // angle in radians
{
// fail without info provided
//if(!m_screenHeight || !m_screenWidth || !m_scale) { GameEngine101::fatalError("No screen width/height or scale specified for drawRepeat!"); }

int x_iterations = static_cast<int>(sizeBounds.z / glyphSize.z);
int y_iterations = static_cast<int>(sizeBounds.w / glyphSize.w);

std::vector<Glyph> glyphs;
glyphs.resize(x_iterations * y_iterations);
std::vector<Vertex> vertices;
std::vector<RenderBatch> renderBatches;
int cv = 0; // current vertex
int glyphOffset = 0;

// tmp variable
glm::vec4 tmpDestRect = glyphSize;

// rotate by angle, converted from radians to degrees, on the z-axis
//glm::mat4 groupRotation = translation;
//glm::mat4 groupRotation = glm::mat4(1.0f);
glm::mat4 groupRotation;
groupRotation = glm::translate(groupRotation, glm::vec3(sizeBounds.x, sizeBounds.y, 0.0f));
//groupRotation = glm::translate(groupRotation, glm::vec3(0.5f * sizeBounds.z, 0.5f * sizeBounds.w, 0.0f));
groupRotation = glm::rotate(groupRotation, angle, glm::vec3(0.0f, 0.0f, 1.0f));
//groupRotation = glm::translate(groupRotation, glm::vec3(-0.5f * sizeBounds.z, -0.5f * sizeBounds.w, 0.0f));
//groupRotation = glm::scale(groupRotation, glm::vec3(scale, scale, 0.0f));
groupRotation = glm::translate(groupRotation, glm::vec3(-sizeBounds.x, -sizeBounds.y, 0.0f));
GLint pUniformM = m_glsl->getUniformLocation("rotation");
glUniformMatrix4fv(pUniformM, 1, GL_FALSE, &groupRotation[0][0]);

// create glyphs
for(uint ix = 0; ix < x_iterations; ix++)
{
tmpDestRect.x = glyphSize.x + ( glyphSize.z * ix ); // set X position

for(uint iy = 0; iy < y_iterations; iy++)
{
tmpDestRect.y = glyphSize.y + ( glyphSize.w * iy ); // set Y position
glyphs.emplace_back(tmpDestRect, uvRect, texture, depth, color);
}
}

// resize verticies vector
vertices.resize(glyphs.size() * 6);

// emplace_back uses the constructor for whatever is being stored
//      in this case, it's RenderBatch
renderBatches.emplace_back(glyphOffset, 6, texture);

vertices[cv++] = glyphs[0].topLeft; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[0].bottomLeft; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[0].bottomRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[0].bottomRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[0].topRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[0].topLeft; // execute, THEN add 1 to cv
glyphOffset += 6;

for(uint cg = 1; cg < glyphs.size(); cg++)
{
if(glyphs[cg].texture != glyphs[cg - 1].texture) { renderBatches.emplace_back(glyphOffset, 6, glyphs[cg].texture); }
else { renderBatches.back().numVertices += 6; }

vertices[cv++] = glyphs[cg].topLeft; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[cg].bottomLeft; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[cg].bottomRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[cg].bottomRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[cg].topRight; // execute, THEN add 1 to cv
vertices[cv++] = glyphs[cg].topLeft; // execute, THEN add 1 to cv
glyphOffset += 6;
}

glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), nullptr, GL_DYNAMIC_DRAW); // orphan the buffer
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(Vertex), vertices.data());

// unbind buffer on completion
glBindBuffer(GL_ARRAY_BUFFER, 0);

// actually render
glBindVertexArray(m_vao);
for(uint i = 0; i < renderBatches.size(); i++)
{
glBindTexture(GL_TEXTURE_2D, renderBatches[i].texture);
glDrawArrays(GL_TRIANGLES, renderBatches[i].offset, renderBatches[i].numVertices);
}
glBindVertexArray(0);
}


I understand debugging code based on snippets is hilariously difficult, but I am under the impression that I'm doing something obviously wrong. Hopefully this helps clear some of it up. If there is anything else that I can provide, please let me know!

const char* GLVERT_SRC = R"(
#version 130

// input data from the VBO. Each vertex is 2 floats
in vec2 vertexPosition;
in vec4 vertexColor;
in vec2 vertexUV;

out vec2 fragmentPosition;
out vec4 fragmentColor;
out vec2 fragmentUV;

// projection matrix
uniform mat4 translation;
// model matrix (rotation)
uniform mat4 rotation;
// scale matrix
//uniform mat4 scaling;

void main()
{
// Set the x,y position on the screen
//gl_Position = translation * rotation * scaling * vec4(vertexPosition.xy, 0.0, 1.0);
// gl_Position.xy = (projection * vec4(vertexPosition, 0.0, 1.0)).xy;
gl_Position = translation * rotation * vec4(vertexPosition.xy, 0.0, 1.0);

// the z position is zero since we are in 2D
gl_Position.z = 0.0;

//Indicate that the coordinates are normalized
gl_Position.w = 1.0;

fragmentPosition = vertexPosition;

fragmentColor = vertexColor;

fragmentUV = vec2(vertexUV.x, 1.0 - vertexUV.y);
})";

const char* GLFRAG_SRC = R"(
#version 130

in vec2 fragmentPosition;
in vec4 fragmentColor;
in vec2 fragmentUV;

// This is the 3 component float vector that gets outputted to the screen
// for each pixel.
out vec4 color;

uniform sampler2D mySampler;

void main()
{
// cos(x) returns a number between -1 and 1. To convert it into the range 0 to 1
// you simply do (cos(x) + 1.0) * 0.5
vec4 textureColor = texture(mySampler, fragmentUV);

// Make crazy colors using time and position
color = fragmentColor * textureColor;
})";

• Are you actually using shaders or are you just using the old OpenGL 1.x immediate pipeline? – Honeybunch Jan 13 '17 at 22:34
• Honeybunch, thank you for pointing this out. MalphasWats pointed this out as well. I am NOT using immediate mode, just a mental slip up. glm::rotate and glm::translate, not glRotate and glTranslate. Sorry! – Beau B. Jan 14 '17 at 3:55

I'm about 2 questions ahead of you in my own OpenGL journey, but I'm starting to get a handle on things now. My project started in Python and then I ported to Javascript, and I'm not using SDL so it's a little different to you but the GL principles should be the same.

firstly, using matrices makes things simpler in the longer term, so if you can get your head around them, keep at it. Try this tutorial that came in handy for me. In a nutshell though, any matrix transforms you apply will apply to everything currently in a buffer being drawn, so your rotations will apply to everything currently being drawn, which is probably not what you want.

Depending on how many things are being rotated and how often, you will likely want to rework your pipeline a little so that the things being rotated are drawn individually, but this gets 'expensive' the more things you draw this way.

Your origin issue can be solved elegantly with a matrix. You mention glTranslate() and glRotate() which suggests you're using the older style 'fixed pipeline' openGL stuff, and I'm less familiar with that.

I'm building a 2D gl framework, and I use the following matrix to convert 'pixel coordinates' that I pass as vertices (30, 30), (120, 192) etc, into gl coordinates between (-1.0 and 1.0). It also flips the y-axis so top is -1 and then it translates it all up and left so top-left becomes (0,0):

projection_matrix = [
2 / viewport_width, 0, 0, 0,
0, -2 / viewport_height, 0, 0,
0, 0, 2 / viewport_height, 0,
-1, 1, 0, 1,
];


That's all very complicated, so let's break it down into a few steps:

identity_matrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]


This is a special matrix that basically does nothing, whatever you pass in comes out the same the other end.

We can then translate things along the x, y, z axes with the bottom row:

translation_matrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
]


This would translate everything up and left, so you would effectively be working with gl coordinates (0.0 - 2.0), so (0,0) should be at the top-left (where I prefer it).

I will point out that I finally learnt all of this about 2 days ago tearing my hair out trying to work out how to make a pixel-ndc coordinate 'projection' matrix (say that 5 times fast!).

In my top matrix, where I do this conversion, we can think of it like this:

I have a screen that is 500 pixels wide and 2.0 gl-thingies (sorry, I have no idea what they are called).

2.0 / 500 = 0.004 gl-thingies.


meaning each of the 500 pixels I want to draw is 0.004 gl-thingies. So if I want to draw something at x=50, I can work out the gl-coordinates by multiplying that by 0.004, so:

50 * 0.004 = 0.2


I can build that into the matrix, which moves all the maths onto the GPU, which is probably where you want it (but I'm not the boss of you so...):

projection_matrix = [
0.004, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
]


then obviously you can do the same for the other axes, say the screen is 400pixels high:

2.0 / 400 = 0.005

projection_matrix = [
0.004, 0,     0, 0,
0,     0.005, 0, 0,
0,     0,     1, 0,
-1,   -1,     0, 1,
]


and so on for z (I'm glossing over because my work has been 2d based so I just make up useful depths for layering things and just make it the same as the height).

So you can also build in the rotation and any scaling you want to do, but it will apply to everything you multiply by this matrix.

What you would want to do is something like:

1. create a buffer for all the 'main stuff'
2. load that buffer up with the data
3. set the matrix
4. draw the buffer

5. create a buffer for things rotated x degrees

7. set the new matrix including rotation
8. draw the buffer

For performance purposes, you should only do 1 and 5 once per program.

2 and 6 should only be done when something in those buffers has changed, similarly, 3 and 7 should only be done when something about the view has changed - in my project, I write an entire level worth of tiles into my tile buffer at the start, then use a second matrix to translate things around as the player moves (I don't know how to apply 2 'translation' type matrices using fixed function OpenGL, so I guess that would have to be done in your code?).

4 and 8 should be done every frame - stuff stays in the buffers until you change it, uniforms keep their values until you write new ones in. (this is something I learnt way too late!!!).