Jump to content
  • Advertisement
Sign in to follow this  

OpenGL Pros and Cons for Batching Sprites

This topic is 1726 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I thought sprite batches were the way to go for the last 3 years, but the more I use my sprite batch system, the more I wonder if it's actually helping? I understand the benefits: fewer texture swaps, shader swaps and draw calls overall.

 

The way I use my sprite batch class is to accomplish what I described above. I have a Sprite class which I create instances of, and add them to my SpriteBatch instance which also has a reference to a texture atlas. I can setup each sprite's blitting data, transform them, etc. and the Sprite Batch will keep track of every sprite's vertices in a single array.

 

Life is good, isn't it? Then, I started analyzing the cons I've found using them:

-Most sprites aren't using the same texture all that frequently unless you're using a tile map, but tile maps can be handled as a special-case anyway. Outside of that, it's very rare to have the same sprite show up multiple times onscreen.

-Batching requires all sprites to software-transform the vertices they own in the vertex array in OpenGL ES 2.0 --very common right now for mobile games

-If you wanted to make a sprite invisible because you didn't want to render it at that time, you couldn't just simply not render it. You'd have to give that sprite's vertices an alpha value of zero so they're completely transparent. This is a blending performance killer on mobile devices

-Frustum culling is inefficient. If you use a sprite batch for a large area that has many of the same sprite, say, the same enemy, you'll be able to draw them all at once easily, but usually only a couple of them, if any, are onscreen at a time

-Color data is needed per vertex to apply a color overlay to a sprite instead of a single uniform because its vertices are batched with the rest

-Any time you want to draw a sprite, you need a sprite batch. This is a pain if you're making an RPG with 4 main characters where they each have their own batches. Each character needs its own sprite batch for their sprite even though they're the only instance that'll ever need it at a time

-Storing a sprite in multiple lists are a pain as well. I'm writing a 2D engine where each Sprite is a scene object with its own transform matrix. Not only does it have to be included in the scene's object dictionary (STL map), but now its corresponding sprite batch needs to keep a pointer to it in its own list. This is problematic because if the Sprite is released from memory, I now need to make sure both the batch and scene know it.

 

Just my thoughts

Share this post


Link to post
Share on other sites
Advertisement

I'm currently having similar thoughts about implementing a sprite batcher, and your post even got me thinking about some problems I hadn't thought of before. As such, remember that all follows are answers from the top of my head that may or may not work as I have yet to implement this myself.

 


-Batching requires all sprites to software-transform the vertices they own in the vertex array in OpenGL ES 2.0 --very common right now for mobile games

If done at load time this shouldn't be a problem, no?

I'm assuming you only geometry-batch static sprites.

 


-If you wanted to make a sprite invisible because you didn't want to render it at that time, you couldn't just simply not render it. You'd have to give that sprite's vertices an alpha value of zero so they're completely transparent. This is a blending performance killer on mobile devices

Maybe increasing draw calls to only draw the visible indices would help. Once an object is invisible for more than a number of frames or more than X objects are hidden, rebuild the indices to only contain the visible instances. Having some pools of indices would help limiting the work done when rebuilding the indices.

 


-Frustum culling is inefficient. If you use a sprite batch for a large area that has many of the same sprite, say, the same enemy, you'll be able to draw them all at once easily, but usually only a couple of them, if any, are onscreen at a time

Try force-splitting the batches in chunks of a certain area. Say, a chunk only covers a 512x512 pixel area at most (only count the sprites' centers, not their dimensions).This will make even less sprites fit a batch, but maybe it's still worh it... This may however add to the difficulty of properly sorting the faces to render front-to-back (if you need that).

 


-Any time you want to draw a sprite, you need a sprite batch. This is a pain if you're making an RPG with 4 main characters where they each have their own batches. Each character needs its own sprite batch for their sprite even though they're the only instance that'll ever need it at a time

Assuming your engine is aware of the difference between static sprites and dynamic sprites, the performance loss of putting your character sprites through the system should be negligible.

The interface you use to feed the sprites to your engine should also be simple enough that the user will barely notice that the sprites will get batched before rendering.

 


-Storing a sprite in multiple lists are a pain as well. I'm writing a 2D engine where each Sprite is a scene object with its own transform matrix. Not only does it have to be included in the scene's object dictionary (STL map), but now its corresponding sprite batch needs to keep a pointer to it in its own list. This is problematic because if the Sprite is released from memory, I now need to make sure both the batch and scene know it.

There probably are many solutions to this problem, Maybe your Sprite class should be the one notifying the batcher of its existence and destruction in it's ctor and dtor respectively? Or maybe you could use something like boost::signals to expose some events on the sprite, something like sprite->getSignal_onDestroy().connect(...). Or implement a sprite::addListener( ISpriteListenet* ).

 

 

The solutions also depend on the type of scene; how many static sprites are there, how many dynamic ones, is instancing used etc.

Share this post


Link to post
Share on other sites

-Most sprites aren't using the same texture all that frequently unless you're using a tile map, but tile maps can be handled as a special-case anyway. Outside of that, it's very rare to have the same sprite show up multiple times onscreen.

If performance is critical and your sprite usage is very dynamic (i.e. you can't create a texture atlas offline) you need to create a system that would take the textures being used, and batch them into a single texture atlas.
It's not easy, but it isn't that hard either.

If you're developing for DX11/GL3; then you can use texture arrays instead which is much simpler and solves the issue.
 

-Batching requires all sprites to software-transform the vertices they own in the vertex array in OpenGL ES 2.0 --very common right now for mobile games

And this is a problem because...?
 

-If you wanted to make a sprite invisible because you didn't want to render it at that time, you couldn't just simply not render it. You'd have to give that sprite's vertices an alpha value of zero so they're completely transparent. This is a blending performance killer on mobile devices

If you're manipulating the sprite vertices to change the alpha value, then:
  • You can just not include the vertices when regenerating the vertex buffer.
  • If you're overwriting just a subregion, then a more clever approach than setting alpha to 0 is to move the vertices out of the viewport region. All vertices will be culled and pixel processing power won't be wasted on them; only for the vertices.

-Frustum culling is inefficient. If you use a sprite batch for a large area that has many of the same sprite, say, the same enemy, you'll be able to draw them all at once easily, but usually only a couple of them, if any, are onscreen at a time

Yes. This is a trade off. Partition your batch into smaller batches, but not small enough to become one batch per sprite.
Additionally, if your API supports instancing (or you're passing each sprite position through a constant register) then culling is still possible. If you can draw up to 80 sprites but only 2 are visible, then divide the number of vertices by 40.
The fact that your vertex buffer can hold 80 * 4 vertices (assuming 4 vertices per sprite) doesn't mean you can't pass less to the draw call.
If you're using instancing, just pass a lower instance count.

-Any time you want to draw a sprite, you need a sprite batch. This is a pain if you're making an RPG with 4 main characters where they each have their own batches. Each character needs its own sprite batch for their sprite even though they're the only instance that'll ever need it at a time

I prefer a more generic batching system that accepts "any" kind of sprite and batches it together (generating the atlas on the fly) and then reuse. However this only works well if the sprites can be grouped by shared properties that last long enough.

Indeed, batching isn't a silver bullet, it does come with trade offs or problems. But generally speaking does it's job in improving performance (with some exceptions, when all the content is too dynamic), and some of the problems you're mentioning are easily solvable.

Share this post


Link to post
Share on other sites

I only transform the vertices if something has changed since the last frame, which is nice if it's static. A downside to consider, however, is that each sprite requires as second set of position, rotation and scale data which can be bloating, and if even just one thing changes, you'd have to re-multiply the translation, rotation and scale matrices together to get the new transform matrix, then multiply pre-multiply its parent matrix by that if it's attached to another scene element, and finally multiply each vertex by that matrix. It can be memory-intensive too as my old sprite module's size was around 1500 bytes per sprite instance! It had all kinds of features though, but imagine using thousands of those instances as static sprites in a tile map. That's megabytes of just metadata lol... (That's not counting the actual vertex/index data in the batch).

 

I've thought about index pools as alpha blending combined with limited filtrate on mobile devices are huge restrictions. It'd just be bad to have to hide a 256x256 sprite with alpha blending from all the texture look-ups alone.

 

Btw, as far as sorting goes, I use the depth buffer along with 3D coordinates in my vertex format. I thought about trimming the vertex struct down to 2D positions with a "depth" value per sprite, but that won't work with batching since OpenGL ES 2.0 doesn't support UBOs like desktop OpenGL 3.3 lol. Due to this, color and depth information per sprite is copied into each of the sprite's vertices until we start seeing OpenGL ES 3.0 hardware.

 


Vincent_M, on 25 Aug 2013 - 5:30 PM, said:

-Any time you want to draw a sprite, you need a sprite batch. This is a pain if you're making an RPG with 4 main characters where they each have their own batches. Each character needs its own sprite batch for their sprite even though they're the only instance that'll ever need it at a time
Assuming your engine is aware of the difference between static sprites and dynamic sprites, the performance loss of putting your character sprites through the system should be negligible.
The interface you use to feed the sprites to your engine should also be simple enough that the user will barely notice that the sprites will get batched before rendering.

 

By static, do you mean moving/non-moving? My sprite batch just contains a single STL vector of vertices for all sprites attached to it. Having only a single sprite in a sprite batch wouldn't be slower than drawing sprites individually, but it's more setup code as you'd have to load the texture, allocate the sprite batch, link the texture to the sprite batch, allocate a sprite, link it to the sprite batch, setup sprite blitting params, then transform/update animations as necessary.

 

Then again, my SpriteBatch class does contain a list of metadata for sprite sheet animations too. You'd define the dimensions of the animation frame, how many frames, etc in a SpriteAnimation object, then link that to your SpriteBatch instance that's keeping reference to the sprite sheet texture. Then, the sprite instance just needs to use SetAnimation(), frame trimming, playback state, playback framerate (all optional), etc.

Share this post


Link to post
Share on other sites

@Matias: I'm trying to add onto my reply above since your post came in after my reply, but it doesn't seem to let me, so I apologize in advance for the double post. Here's are my replies to your points:

 

-I've seen some good things in OpenGL 3.3, but my main target is mobile (yep, I'm on  the mobile bandwagon...). I'm excited for OpenGL ES 3.0 since the specification does appear to support batching and MRTs for deferred rendering, but it looks like it could be a while before we start seeing devices that completely support the spec.

 

-Software rendering is probably a benefit for static sprites as you just transform them once, and update them whenever something changes in the future instead of every frame in a vertex shader. However, software transformation is kind of intense when something actually does change because there's 3, possibly 4 matrix4x4 multiplications and 4 matrix4x4 * vector3 operations. That's 48 to 64 dot products for the matrix multiplication and 12 more dot products to transform the vertices.

 

-As far as frustum culling is concerned, I may stop using my current Sprite class for tile maps and create a simpler sprite class that treats the sprites as static and generates sprite batches based on a quadtree. If I want anything dynamic, I'll treat them as actual sprites with full functionality that character sprites would have.

 

-So, if I move the vertices out of the viewport, the GPU won't attempt to draw them? I'm still sending the data to the GPU, but if it's outside the viewport upon final transformation in the vertex shader, the pixel shader won't be processed? If I understood that correctly, then I feel better about my fill rate concerns. I'm still sending unnecessary data to the GPU, but then again 3D games are sending way more vertex data to GPU with frustum culling than you'd encounter in an entire 2D scene, generally. I would think anyway.

 

-I've thought about writing an atlas generator, but it was meant to be a tool to generate them offline and produce meta data to tell my SpriteBatch class the coordinates of the sprites and animation frames held in the atlas. On-the-fly generation seems as if it could produce long load times as you'd be loading many separate image files at once instead of one larger one, and possibly generating sprite definition data on-the-spot.

Share this post


Link to post
Share on other sites
Most of thee worries involve negligible amounts of data and processing: for example, adding a colour to every one of hundreds and hundreds of sprites amounts to 1 (index) to 8 (16-bit RGBA) hundreds and hundreds of bytes per frame. Are there technical reasons to use many batches instead of putting all compatible sprites in the same batch? Singleton or almost singleton entities like the "4 main characters" can have reserved places in a shared vertex buffer rather than their separate batches.

Share this post


Link to post
Share on other sites

For batching 3d meshes I have a system which allows me to:

 

- batch "batches" (store only the list pointer)

- batch single meshes (store the data in a local list)

 

I use the mesh batches for blocks which don't change so often. So in this case efficient culling comes at price of drawing out-of-frustum objects. 

Instead of copying data (except for the single mesh case) I only add a batch list to a list of batches. Before drawing I upload the data to a buffer object (could be constant buffer or vertex buffer). So this way I can have one draw call per mesh type regardless how the meshes are batched. This can be implemented with sprites too. Of course it involves copying data around which may be less efficient with mobile devices.

 

Cheers!

Share this post


Link to post
Share on other sites

Most of thee worries involve negligible amounts of data and processing: for example, adding a colour to every one of hundreds and hundreds of sprites amounts to 1 (index) to 8 (16-bit RGBA) hundreds and hundreds of bytes per frame. Are there technical reasons to use many batches instead of putting all compatible sprites in the same batch? Singleton or almost singleton entities like the "4 main characters" can have reserved places in a shared vertex buffer rather than their separate batches.

The way I process my sprites currently are by texture: if I have a bunch of sprites using a texture, or group of similar textures for animated sprite sheets, etc, then all my sprites are listed in that sprite batch. Now, since the OpenGL specs I'm confined to at this time do not allow for the manipulation of my batches on a per object basis, I have to simulate that by copying that data into my vertices.

 

Right now, the idea is that I have a texture atlas with a bunch of images within it that my sprites can use. I link my sprite to the appropriate batch, and it usually never changes since it makes sense to stick with that batch. Now, the only way a sprite can be rendered is if it's attached to a sprite batch because it is what has access to the textures, not the sprite. I could very well have multiple batches referencing the same texture, which would simulate a quad tree as far as scene rendering goes...

Share this post


Link to post
Share on other sites

possibly 4 matrix4x4 multiplications and 4 matrix4x4 * vector3 operations. That's 48 to 64 dot products for the matrix multiplication and 12 more dot products to transform the vertices.

If your sprites don't rotate (or you classify between rotating & not rotating), you can get away with no matrix multiplication at all.
After all the transformation is just:
outPos.xy = inVertex.xy * scale.xy + position.xy;
outPos.zw = float2( -1, 1 );
That's just 1 dot product per vertex. Just make sure outPos.xy is in the [-1; 1] range.

-So, if I move the vertices out of the viewport, the GPU won't attempt to draw them? I'm still sending the data to the GPU, but if it's outside the viewport upon final transformation in the vertex shader, the pixel shader won't be processed? If I understood that correctly, then I feel better about my fill rate concerns. I'm still sending unnecessary data to the GPU, but then again 3D games are sending way more vertex data to GPU with frustum culling than you'd encounter in an entire 2D scene, generally. I would think anyway.

Yes, that's exactly what happens. Make sure all 3 vertices from the triangle lay outside the viewport. But don't make the number too big (i.e. don't place it at 8192.0 in screen coordinates; just bigger than 1.0 or less than -1.0 but not too far away; and W must not be 0)

Share this post


Link to post
Share on other sites

I like your thinking, and I do have a combiner method in my Matrix class that'll put a position and scale vector into a matrix as their elements are mutually exclusive. Rotation isn't all that common for many things, so that could be a nice workaround. The only problem is that many different types of objects 'can' rotate. What I could do is check to see if the rotation angle has changed between frames. If not, but position or scale have, then I can just use that combiner method in the class itself, possibly.

 

The code posted above does appear to be lightning fast. I'll keep that in mind for moving my triangles out of the way. When you say bigger than 1.0, is that how many pixels you suggest moving them out? My ortho matrix is based off of pixels.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Popular Now

  • Advertisement
  • Similar Content

    • By mmmax3d
      Hi everyone,
      I would need some assistance from anyone who has a similar experience
      or a nice idea!
      I have created a skybox (as cube) and now I need to add a floor/ground.
      The skybox is created from cubemap and initially it was infinite.
      Now it is finite with a specific size. The floor is a quad in the middle
      of the skybox, like a horizon.
      I have two problems:
      When moving the skybox upwards or downwards, I need to
      sample from points even above the horizon while sampling
      from the botton at the same time.  I am trying to create a seamless blending of the texture
      at the points of the horizon, when the quad is connected
      to the skybox. However, I get skew effects. Does anybody has done sth similar?
      Is there any good practice?
      Thanks everyone!
    • By mmmax3d
      Hi everyone,
      I would need some assistance from anyone who has a similar experience
      or a nice idea!
      I have created a skybox (as cube) and now I need to add a floor/ground.
      The skybox is created from cubemap and initially it was infinite.
      Now it is finite with a specific size. The floor is a quad in the middle
      of the skybox, like a horizon.
      I have two problems:
      When moving the skybox upwards or downwards, I need to
      sample from points even above the horizon while sampling
      from the botton at the same time.  I am trying to create a seamless blending of the texture
      at the points of the horizon, when the quad is connected
      to the skybox. However, I get skew effects. Does anybody has done sth similar?
      Is there any good practice?
      Thanks everyone!
    • By iArtist93
      I'm trying to implement PBR into my simple OpenGL renderer and trying to use multiple lighting passes, I'm using one pass per light for rendering as follow:
      1- First pass = depth
      2- Second pass = ambient
      3- [3 .. n] for all the lights in the scene.
      I'm using the blending function glBlendFunc(GL_ONE, GL_ONE) for passes [3..n], and i'm doing a Gamma Correction at the end of each fragment shader.
      But i still have a problem with the output image it just looks noisy specially when i'm using texture maps.
      Is there anything wrong with those steps or is there any improvement to this process?
    • By babaliaris
      Hello Everyone!
      I'm learning openGL, and currently i'm making a simple 2D game engine to test what I've learn so far.  In order to not say to much, i made a video in which i'm showing you the behavior of the rendering.
      Video: 
       
      What i was expecting to happen, was the player moving around. When i render only the player, he moves as i would expect. When i add a second Sprite object, instead of the Player, this new sprite object is moving and finally if i add a third Sprite object the third one is moving. And the weird think is that i'm transforming the Vertices of the Player so why the transformation is being applied somewhere else?
       
      Take a look at my code:
      Sprite Class
      (You mostly need to see the Constructor, the Render Method and the Move Method)
      #include "Brain.h" #include <glm/gtc/matrix_transform.hpp> #include <vector> struct Sprite::Implementation { //Position. struct pos pos; //Tag. std::string tag; //Texture. Texture *texture; //Model matrix. glm::mat4 model; //Vertex Array Object. VertexArray *vao; //Vertex Buffer Object. VertexBuffer *vbo; //Layout. VertexBufferLayout *layout; //Index Buffer Object. IndexBuffer *ibo; //Shader. Shader *program; //Brains. std::vector<Brain *> brains; //Deconstructor. ~Implementation(); }; Sprite::Sprite(std::string image_path, std::string tag, float x, float y) { //Create Pointer To Implementaion. m_Impl = new Implementation(); //Set the Position of the Sprite object. m_Impl->pos.x = x; m_Impl->pos.y = y; //Set the tag. m_Impl->tag = tag; //Create The Texture. m_Impl->texture = new Texture(image_path); //Initialize the model Matrix. m_Impl->model = glm::mat4(1.0f); //Get the Width and the Height of the Texture. int width = m_Impl->texture->GetWidth(); int height = m_Impl->texture->GetHeight(); //Create the Verticies. float verticies[] = { //Positions //Texture Coordinates. x, y, 0.0f, 0.0f, x + width, y, 1.0f, 0.0f, x + width, y + height, 1.0f, 1.0f, x, y + height, 0.0f, 1.0f }; //Create the Indicies. unsigned int indicies[] = { 0, 1, 2, 2, 3, 0 }; //Create Vertex Array. m_Impl->vao = new VertexArray(); //Create the Vertex Buffer. m_Impl->vbo = new VertexBuffer((void *)verticies, sizeof(verticies)); //Create The Layout. m_Impl->layout = new VertexBufferLayout(); m_Impl->layout->PushFloat(2); m_Impl->layout->PushFloat(2); m_Impl->vao->AddBuffer(m_Impl->vbo, m_Impl->layout); //Create the Index Buffer. m_Impl->ibo = new IndexBuffer(indicies, 6); //Create the new shader. m_Impl->program = new Shader("Shaders/SpriteShader.shader"); } //Render. void Sprite::Render(Window * window) { //Create the projection Matrix based on the current window width and height. glm::mat4 proj = glm::ortho(0.0f, (float)window->GetWidth(), 0.0f, (float)window->GetHeight(), -1.0f, 1.0f); //Set the MVP Uniform. m_Impl->program->setUniformMat4f("u_MVP", proj * m_Impl->model); //Run All The Brains (Scripts) of this game object (sprite). for (unsigned int i = 0; i < m_Impl->brains.size(); i++) { //Get Current Brain. Brain *brain = m_Impl->brains[i]; //Call the start function only once! if (brain->GetStart()) { brain->SetStart(false); brain->Start(); } //Call the update function every frame. brain->Update(); } //Render. window->GetRenderer()->Draw(m_Impl->vao, m_Impl->ibo, m_Impl->texture, m_Impl->program); } void Sprite::Move(float speed, bool left, bool right, bool up, bool down) { if (left) { m_Impl->pos.x -= speed; m_Impl->model = glm::translate(m_Impl->model, glm::vec3(-speed, 0, 0)); } if (right) { m_Impl->pos.x += speed; m_Impl->model = glm::translate(m_Impl->model, glm::vec3(speed, 0, 0)); } if (up) { m_Impl->pos.y += speed; m_Impl->model = glm::translate(m_Impl->model, glm::vec3(0, speed, 0)); } if (down) { m_Impl->pos.y -= speed; m_Impl->model = glm::translate(m_Impl->model, glm::vec3(0, -speed, 0)); } } void Sprite::AddBrain(Brain * brain) { //Push back the brain object. m_Impl->brains.push_back(brain); } pos *Sprite::GetPos() { return &m_Impl->pos; } std::string Sprite::GetTag() { return m_Impl->tag; } int Sprite::GetWidth() { return m_Impl->texture->GetWidth(); } int Sprite::GetHeight() { return m_Impl->texture->GetHeight(); } Sprite::~Sprite() { delete m_Impl; } //Implementation Deconstructor. Sprite::Implementation::~Implementation() { delete texture; delete vao; delete vbo; delete layout; delete ibo; delete program; }  
      Renderer Class
      #include "Renderer.h" #include "Error.h" Renderer::Renderer() { } Renderer::~Renderer() { } void Renderer::Draw(VertexArray * vao, IndexBuffer * ibo, Texture *texture, Shader * program) { vao->Bind(); ibo->Bind(); program->Bind(); if (texture != NULL) texture->Bind(); GLCall(glDrawElements(GL_TRIANGLES, ibo->GetCount(), GL_UNSIGNED_INT, NULL)); } void Renderer::Clear(float r, float g, float b) { GLCall(glClearColor(r, g, b, 1.0)); GLCall(glClear(GL_COLOR_BUFFER_BIT)); } void Renderer::Update(GLFWwindow *window) { /* Swap front and back buffers */ glfwSwapBuffers(window); /* Poll for and process events */ glfwPollEvents(); }  
      Shader Code
      #shader vertex #version 330 core layout(location = 0) in vec4 aPos; layout(location = 1) in vec2 aTexCoord; out vec2 t_TexCoord; uniform mat4 u_MVP; void main() { gl_Position = u_MVP * aPos; t_TexCoord = aTexCoord; } #shader fragment #version 330 core out vec4 aColor; in vec2 t_TexCoord; uniform sampler2D u_Texture; void main() { aColor = texture(u_Texture, t_TexCoord); } Also i'm pretty sure that every time i'm hitting the up, down, left and right arrows on the keyboard, i'm changing the model Matrix of the Player and not the others.
       
      Window Class:
      #include "Window.h" #include <GL/glew.h> #include <GLFW/glfw3.h> #include "Error.h" #include "Renderer.h" #include "Scene.h" #include "Input.h" //Global Variables. int screen_width, screen_height; //On Window Resize. void OnWindowResize(GLFWwindow *window, int width, int height); //Implementation Structure. struct Window::Implementation { //GLFW Window. GLFWwindow *GLFW_window; //Renderer. Renderer *renderer; //Delta Time. double delta_time; //Frames Per Second. int fps; //Scene. Scene *scnene; //Input. Input *input; //Deconstructor. ~Implementation(); }; //Window Constructor. Window::Window(std::string title, int width, int height) { //Initializing width and height. screen_width = width; screen_height = height; //Create Pointer To Implementation. m_Impl = new Implementation(); //Try initializing GLFW. if (!glfwInit()) { std::cout << "GLFW could not be initialized!" << std::endl; std::cout << "Press ENTER to exit..." << std::endl; std::cin.get(); exit(-1); } //Setting up OpenGL Version 3.3 Core Profile. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); /* Create a windowed mode window and its OpenGL context */ m_Impl->GLFW_window = glfwCreateWindow(width, height, title.c_str(), NULL, NULL); if (!m_Impl->GLFW_window) { std::cout << "GLFW could not create a window!" << std::endl; std::cout << "Press ENTER to exit..." << std::endl; std::cin.get(); glfwTerminate(); exit(-1); } /* Make the window's context current */ glfwMakeContextCurrent(m_Impl->GLFW_window); //Initialize GLEW. if(glewInit() != GLEW_OK) { std::cout << "GLEW could not be initialized!" << std::endl; std::cout << "Press ENTER to exit..." << std::endl; std::cin.get(); glfwTerminate(); exit(-1); } //Enabling Blending. GLCall(glEnable(GL_BLEND)); GLCall(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); //Setting the ViewPort. GLCall(glViewport(0, 0, width, height)); //**********Initializing Implementation**********// m_Impl->renderer = new Renderer(); m_Impl->delta_time = 0.0; m_Impl->fps = 0; m_Impl->input = new Input(this); //**********Initializing Implementation**********// //Set Frame Buffer Size Callback. glfwSetFramebufferSizeCallback(m_Impl->GLFW_window, OnWindowResize); } //Window Deconstructor. Window::~Window() { delete m_Impl; } //Window Main Loop. void Window::MainLoop() { //Time Variables. double start_time = 0, end_time = 0, old_time = 0, total_time = 0; //Frames Counter. int frames = 0; /* Loop until the user closes the window */ while (!glfwWindowShouldClose(m_Impl->GLFW_window)) { old_time = start_time; //Total time of previous frame. start_time = glfwGetTime(); //Current frame start time. //Calculate the Delta Time. m_Impl->delta_time = start_time - old_time; //Get Frames Per Second. if (total_time >= 1) { m_Impl->fps = frames; total_time = 0; frames = 0; } //Clearing The Screen. m_Impl->renderer->Clear(0, 0, 0); //Render The Scene. if (m_Impl->scnene != NULL) m_Impl->scnene->Render(this); //Updating the Screen. m_Impl->renderer->Update(m_Impl->GLFW_window); //Increasing frames counter. frames++; //End Time. end_time = glfwGetTime(); //Total time after the frame completed. total_time += end_time - start_time; } //Terminate GLFW. glfwTerminate(); } //Load Scene. void Window::LoadScene(Scene * scene) { //Set the scene. m_Impl->scnene = scene; } //Get Delta Time. double Window::GetDeltaTime() { return m_Impl->delta_time; } //Get FPS. int Window::GetFPS() { return m_Impl->fps; } //Get Width. int Window::GetWidth() { return screen_width; } //Get Height. int Window::GetHeight() { return screen_height; } //Get Input. Input * Window::GetInput() { return m_Impl->input; } Renderer * Window::GetRenderer() { return m_Impl->renderer; } GLFWwindow * Window::GetGLFWindow() { return m_Impl->GLFW_window; } //Implementation Deconstructor. Window::Implementation::~Implementation() { delete renderer; delete input; } //OnWindowResize void OnWindowResize(GLFWwindow *window, int width, int height) { screen_width = width; screen_height = height; //Updating the ViewPort. GLCall(glViewport(0, 0, width, height)); }  
      Brain Class
      #include "Brain.h" #include "Sprite.h" #include "Window.h" struct Brain::Implementation { //Just A Flag. bool started; //Window Pointer. Window *window; //Sprite Pointer. Sprite *sprite; }; Brain::Brain(Window *window, Sprite *sprite) { //Create Pointer To Implementation. m_Impl = new Implementation(); //Initialize Implementation. m_Impl->started = true; m_Impl->window = window; m_Impl->sprite = sprite; } Brain::~Brain() { //Delete Pointer To Implementation. delete m_Impl; } void Brain::Start() { } void Brain::Update() { } Window * Brain::GetWindow() { return m_Impl->window; } Sprite * Brain::GetSprite() { return m_Impl->sprite; } bool Brain::GetStart() { return m_Impl->started; } void Brain::SetStart(bool value) { m_Impl->started = value; } Script Class (Its a Brain Subclass!!!)
      #include "Script.h" Script::Script(Window *window, Sprite *sprite) : Brain(window, sprite) { } Script::~Script() { } void Script::Start() { std::cout << "Game Started!" << std::endl; } void Script::Update() { Input *input = this->GetWindow()->GetInput(); Sprite *sp = this->GetSprite(); //Move this sprite. this->GetSprite()->Move(200 * this->GetWindow()->GetDeltaTime(), input->GetKeyDown("left"), input->GetKeyDown("right"), input->GetKeyDown("up"), input->GetKeyDown("down")); std::cout << sp->GetTag().c_str() << ".x = " << sp->GetPos()->x << ", " << sp->GetTag().c_str() << ".y = " << sp->GetPos()->y << std::endl; }  
      Main:
      #include "SpaceShooterEngine.h" #include "Script.h" int main() { Window w("title", 600,600); Scene *scene = new Scene(); Sprite *player = new Sprite("Resources/Images/player.png", "Player", 100,100); Sprite *other = new Sprite("Resources/Images/cherno.png", "Other", 400, 100); Sprite *other2 = new Sprite("Resources/Images/cherno.png", "Other", 300, 400); Brain *brain = new Script(&w, player); player->AddBrain(brain); scene->AddSprite(player); scene->AddSprite(other); scene->AddSprite(other2); w.LoadScene(scene); w.MainLoop(); return 0; }  
       
      I literally can't find what is wrong. If you need more code, ask me to post it. I will also attach all the source files.
      Brain.cpp
      Error.cpp
      IndexBuffer.cpp
      Input.cpp
      Renderer.cpp
      Scene.cpp
      Shader.cpp
      Sprite.cpp
      Texture.cpp
      VertexArray.cpp
      VertexBuffer.cpp
      VertexBufferLayout.cpp
      Window.cpp
      Brain.h
      Error.h
      IndexBuffer.h
      Input.h
      Renderer.h
      Scene.h
      Shader.h
      SpaceShooterEngine.h
      Sprite.h
      Texture.h
      VertexArray.h
      VertexBuffer.h
      VertexBufferLayout.h
      Window.h
    • By Cristian Decu
      Hello fellow programmers,
      For a couple of days now i've decided to build my own planet renderer just to see how floating point precision issues
      can be tackled. As you probably imagine, i've quickly faced FPP issues when trying to render absurdly large planets.
       
      I have used the classical quadtree LOD approach;
      I've generated my grids with 33 vertices, (x: -1 to 1, y: -1 to 1, z = 0).
      Each grid is managed by a TerrainNode class that, depending on the side it represents (top, bottom, left right, front, back),
      creates a special rotation-translation matrix that moves and rotates the grid away from the origin so that when i finally
      normalize all the vertices on my vertex shader i can get a perfect sphere.
      T = glm::translate(glm::dmat4(1.0), glm::dvec3(0.0, 0.0, 1.0)); R = glm::rotate(glm::dmat4(1.0), glm::radians(180.0), glm::dvec3(1.0, 0.0, 0.0)); sides[0] = new TerrainNode(1.0, radius, T * R, glm::dvec2(0.0, 0.0), new TerrainTile(1.0, SIDE_FRONT)); T = glm::translate(glm::dmat4(1.0), glm::dvec3(0.0, 0.0, -1.0)); R = glm::rotate(glm::dmat4(1.0), glm::radians(0.0), glm::dvec3(1.0, 0.0, 0.0)); sides[1] = new TerrainNode(1.0, radius, R * T, glm::dvec2(0.0, 0.0), new TerrainTile(1.0, SIDE_BACK)); // So on and so forth for the rest of the sides As you can see, for the front side grid, i rotate it 180 degrees to make it face the camera and push it towards the eye;
      the back side is handled almost the same way only that i don't need to rotate it but simply push it away from the eye.
      The same technique is applied for the rest of the faces (obviously, with the proper rotations / translations).
      The matrix that result from the multiplication of R and T (in that particular order) is send to my vertex shader as `r_Grid'.
      // spherify vec3 V = normalize((r_Grid * vec4(r_Vertex, 1.0)).xyz); gl_Position = r_ModelViewProjection * vec4(V, 1.0); The `r_ModelViewProjection' matrix is generated on the CPU in this manner.
      // No the most efficient way, but it works. glm::dmat4 Camera::getMatrix() { // Create the view matrix // Roll, Yaw and Pitch are all quaternions. glm::dmat4 View = glm::toMat4(Roll) * glm::toMat4(Pitch) * glm::toMat4(Yaw); // The model matrix is generated by translating in the oposite direction of the camera. glm::dmat4 Model = glm::translate(glm::dmat4(1.0), -Position); // Projection = glm::perspective(fovY, aspect, zNear, zFar); // zNear = 0.1, zFar = 1.0995116e12 return Projection * View * Model; } I managed to get rid of z-fighting by using a technique called Logarithmic Depth Buffer described in this article; it works amazingly well, no z-fighting at all, at least not visible.
      Each frame i'm rendering each node by sending the generated matrices this way.
      // set the r_ModelViewProjection uniform // Sneak in the mRadiusMatrix which is a matrix that contains the radius of my planet. Shader::setUniform(0, Camera::getInstance()->getMatrix() * mRadiusMatrix); // set the r_Grid matrix uniform i created earlier. Shader::setUniform(1, r_Grid); grid->render(); My planet's radius is around 6400000.0 units, absurdly large, but that's what i really want to achieve;
      Everything works well, the node's split and merge as you'd expect, however whenever i get close to the surface
      of the planet the rounding errors start to kick in giving me that lovely stairs effect.
      I've read that if i could render each grid relative to the camera i could get better precision on the surface, effectively
      getting rid of those rounding errors.
       
      My question is how can i achieve this relative to camera rendering in my scenario here?
      I know that i have to do most of the work on the CPU with double, and that's exactly what i'm doing.
      I only use double on the CPU side where i also do most of the matrix multiplications.
      As you can see from my vertex shader i only do the usual r_ModelViewProjection * (some vertex coords).
       
      Thank you for your suggestions!
       
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!