Jump to content
  • Advertisement
Sign in to follow this  
mcclure111

OpenGL Trying to make sense of this shader article [occlusion]

This topic is 2445 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

[font="Verdana, Helvetica, sans-serif"]Hi! haven't posted here before but some people from Tigsource said that this was a good place to go to ask technical questions. S[/font][font="Verdana, Helvetica, sans-serif"]omebody linked me this paper, it describes a shader technique. I'm having trouble understanding it, I think because it is written targeting DirectX (DX9, I think), which I do not use. I'm trying to figure out how to translate this to OpenGL-ese.[/font][font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]http://gamedeveloper...1109?pg=37#pg37[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]The article suggests you could use a GPU to accelerate large-scale occlusion detection. I think I get their general approach: You have a scene containing a number of objects which are in themselves complex, like people or buildings. If one of these objects is completely obscured by another object, you'd prefer not to even try to render it-- maybe these objects are so complex that even with z-buffer testing you're spending a lot by trying to draw it only to have its pixels discarded. So before you actually try to draw, you draw a simplified version of the scene-- instead of the building, you draw a plain box that has the same dimensions as the building-- and then you use a shader to report back information about which of the boxes wound up in the final scene. Okay, that makes sense. But then I start thinking about how to implement the technique they describe and I just wind up feeling like an idiot.[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]Here's where I'm lost:[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]- When do you do this? Are they really proposing doing this every frame?[/font]
[font="Verdana, Helvetica, sans-serif"]- Once the GPU has created the list of "here's what is and isn't occluded", the CPU has to act on it. Is it really cheaper to ship back data every frame from the GPU to the CPU than to do the occlusion tests in CPU? I thought shipping data GPU->CPU was basically the most expensive thing you could do.[/font]
[font="Verdana, Helvetica, sans-serif"]- My big point of confusion. They say:[/font][font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]
After dispatching the compute shader to test all the bounds for visibility, you'll have time to do some processing on the CPU while you wait for the results... Once the compute shader finishes, it's just a matter of locking the writable buffer from steps 3-5, and copying the float values representing visible or hidden objects into a CPU-accessible buffer so that a final list of visible objects can be prepared for rendering.[/quote][/font][font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]They suggest storing the floats in a "writable buffer". I don't think I've ever heard of such a concept. They also make (sec. 3) reference to a "compute shader", a piece of vocabulary I'm not familiar with. Are these DirectX things? Do they have OpenGL analogues? It sounds as if they are running a shader program that has access to a plain array, and can sample a texture at will and write into random-access places in that array. They're having individual indexes in the array correspond to objects that are being tested for occlusion, and somehow also the shader has access to the bounding boxes of the individual objects it's working on (they have a diagram where they show a 3D object, and then draw a bounding box for the object on the screen; they seem to think that they can get this bounding box information for any given object, in fact, they seem to believe getting this information is so easy they don't even bother telling you how to do it. Once they have this box they plan to test the four corners for visibility and conclude that if any corner is visible, so is the object. Doesn't sound like a fair assumption for a sphere, but...). Are these techniques that translate to OpenGL-land at all?[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]The closest I can get to making something like this work with the OpenGL tools I know of are:[/font][font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]1. Render the "simplified" scene such that the depth buffer goes into a texture.[/font]
[font="Verdana, Helvetica, sans-serif"]2. Render a very small scene (like, 16x16) consisting of one polygon covering the screen, with the vertex shader just passing a varying to each pixel which tells it its screen coordinate. This will give me 256 runs into a pixel shader.[/font]
[font="Verdana, Helvetica, sans-serif"]3. Each pixel shader runthrough uses its coordinate to determine which object it's responsible for testing; it samples the depth texture from (1), computes whether the object was visible or not, and writes either black or white at its pixel. [/font]
[font="Verdana, Helvetica, sans-serif"]4. Copy the small 16x16 texture back to the CPU and read each pixel to decide whether to draw the object.[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]...but, this doesn't work because I have NO IDEA how I would get the "bounding box", or, if I magically had a list of screen-bounding-boxes-for-objects. how I would pass this list of information into the pixel shader.[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"]Am I missing anything?![/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]Thanks for any responses! I don't actually exactly plan to use this technique, but I do want to develop my shader programming skillset to the point where I can at least UNDERSTAND an article like this :/[/font]

Share this post


Link to post
Share on other sites
Advertisement
[font="Verdana, Helvetica, sans-serif"]
When do you do this? Are they really proposing doing this [/font][font="Verdana, Helvetica, sans-serif"]every frame?[/quote]Yes, occlusion testing is done every frame.
[/font][font="Verdana, Helvetica, sans-serif"]- Once the GPU has created the list of "here's what is and isn't occluded", the CPU has to act on it. Is it really cheaper to ship back data every frame from the GPU to the CPU than to do the occlusion tests in CPU? I thought shipping data GPU->CPU was basically the most expensive thing you could do.[/quote]In general, sending data back to the CPU from the GPU is a very bad idea, yes, due to the latency involved.[/font]
[font="Verdana, Helvetica, sans-serif"]Often, to get around this problem, people use the results with a one frame delay -- e.g. [/font]
[font="Verdana, Helvetica, sans-serif"][Update #1] [Occlusion #1] [Render #1 using results from update #1 and Occlusion #0]
[/font][font="Verdana, Helvetica, sans-serif"][Update #2] [Occlusion #2] [Render #2 using results from update #2 and Occlusion #1] [/font]
[font="Verdana, Helvetica, sans-serif"]^^this causes "popping" artefacts where objects take a single frame to apper after they come into view, so it's an unpopular technique for many people.
[/font][font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]However, occlusion results only need one bit per object. If you're occluding 10,000 objects, that's ~1KiB of data, which isn't very much.[/font]
[font="Verdana, Helvetica, sans-serif"]You still have to find a way to mask the latency -- e.g.[/font]
[font="Verdana, Helvetica, sans-serif"]Render occlusion volumes.[/font]
[font="Verdana, Helvetica, sans-serif"]"Render" occlusion tests.[/font]
[font="Verdana, Helvetica, sans-serif"]Initiate data transfer to CPU.[/font]
[font="Verdana, Helvetica, sans-serif"]Render something that doesn't depend on the occlusion results (keeping GPU busy during the latency/stall period).[/font]
[font="Verdana, Helvetica, sans-serif"]Do some kind of non-rendering task on the CPU [/font][font="Verdana, Helvetica, sans-serif"] (keeping CPU busy during the latency/stall period).[/font]
[font="Verdana, Helvetica, sans-serif"]Render the things that do depend on the occluion tests.[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"]They suggest storing the floats in a "writable buffer". I don't think I've ever heard of such a concept.[/quote]A render-target, frame-buffer, whateveryouwanttocallit is a writeable buffer.[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"]
They also make (sec. 3) reference to a "compute shader", a piece of vocabulary I'm not familiar with. Are these DirectX things? Do they have OpenGL analogues?[/quote]Compute shaders were introduced in DX11. If you're not using DirectX, you can use them via OpenCL. I believe you can find some of the compute-shader code on the author's blog.[/font]
[font="Verdana, Helvetica, sans-serif"]However, this technique can be implemented using only pixel/vertex shaders instead of using compute shaders.[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"]they seem to believe getting this information is so easy they don't even bother telling you how to do it.[/quote]The information comes from the context of GDC/etc, attended by people who would find it obvious and be bored by the explanation of how to do it ;)[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]
It sounds as if they are running a shader program that has access to a plain array, and can sample a texture at will and write into random-access places in that array. They're having individual indexes in the array correspond to objects that are being tested for occlusion, and somehow also the shader has access to the bounding boxes of the individual objects it's working on[/quote]It's a lot simpler a problem than it first sounds - picture this:[/font]
[font="Verdana, Helvetica, sans-serif"]You've got a 64x64 render-target/frame-buffer, which is basically an array of 4096 pixels.[/font]
[font="Verdana, Helvetica, sans-serif"]You've got a VBO with 4096 vertices in it -- each vertex represents a single object to be tested by the occlusion system.[/font]
[font="Verdana, Helvetica, sans-serif"]Each vertex has a 2d position (which corresponds to a unique pixel position in the render target) -- this is where the output/result will be stored.[/font]
[font="Verdana, Helvetica, sans-serif"]Each vertex also has a 3D AABB encoded in it's texture coordinates.[/font]
[font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]You render this VBO to this render-target using "points" primitives, so each vertex becomes a single output pixel.[/font]
[font="Verdana, Helvetica, sans-serif"]You use a vertex shader which simply places the output vertex at the 2D pixel position specified in the input vertex and passes through the "texcoord"/attribute data.[/font]
[font="Verdana, Helvetica, sans-serif"]You use a pixel shader that takes the attribute data, decodes the AABB from it and does all the occlusion logic. During this occlusion logic, a few texture samples are taken from the HZB and compared against the AABB's depth.[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]Alternatively, the method that you describe -- of rendering a full-screen polygon -- would also work. In that case you would:[/font]
[font="Verdana, Helvetica, sans-serif"]Encode all of the AABB's into a 64x64 texture (or several textures if the data won't fit in one).[/font]
[font="Verdana, Helvetica, sans-serif"]Draw a full-screen quad to a 64x64 render-target.[/font]
[font="Verdana, Helvetica, sans-serif"]In the pixel shader, use the screen-position to read an AABB from the input texture(s). Perform the occlusion test on the AABB using the HZB texture and output black/white.[/font]
[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"][/font]
[font="Verdana, Helvetica, sans-serif"]With either of these, you end up with a 64x64 (or whatever size you need) render-target containing black or white pixels, with each pixel representing the visibility result of a single object. [/font][font="Verdana, Helvetica, sans-serif"]You can then download that back to the CPU to determine which objects to render.[/font][font="Verdana, Helvetica, sans-serif"]
[/font]
[font="Verdana, Helvetica, sans-serif"]As an additional step, you can first compress this texture before moving it to the CPU -- each pixel in the texture takes up 32bits (assuming RGBA8 render-targets), but you only require 1 bit of data per pixel.[/font]
[font="Verdana, Helvetica, sans-serif"]So, you can take this 'results' texture and render a full-screen quad to a 2x64 sized target. In the pixel shader, you take 32 texture samples horizontally and pack them together into a single RGBA8 output pixel.[/font]

[font="Verdana, Helvetica, sans-serif"]
[/font][font="Verdana, Helvetica, sans-serif"] Once they have this box they plan to test the four corners for visibility and conclude that if any corner is visible, so is the object.[/quote]No, they calculate a mip-level of the HZB where 4 samples will cover the entire area of the AABB, not just it's corners.[/font]

Share this post


Link to post
Share on other sites
Look up occlusion query. You can query back how many pixels have been drawn from start() to stop(). You would draw your big occluders like buildings all normal shaded and such, then you would draw your basic bounding volumes for your small objects like cars etc (without allowing writing to the screen glColorMask). For each object you make an occlusion query start, and after you are done drawing them all, you request the queries back all at once.

I don't see much use for occlusion queries other than cities, so you could optimize it by spatially dividing your city blocks and having all cars etc being inside those city blocks. This way you would only have a few queries to actually check and then you mark everything inside the block as visible or not.

Share this post


Link to post
Share on other sites
Look up occlusion query. ... I don't see much use for occlusion queries other than cities
The above technique doesn't use hardware occlusion queries. In the "rendering with conviction" explanation of the above technique, they mention that the cost of a single occlusion query for them was 0.05ms, whereas performing the above technique for 20,000 objects cost only 0.4ms total (0.00002ms each).

Many current games, even ones based on popular engines like Unreal, use this technique for their visibility culling (instead of PVS, etc). They render a low-poly mesh of the entire level, and then batch-test every single object in the level at once (without any fancy hierarchical structures at all).

Share this post


Link to post
Share on other sites
single occlusion query for them was 0.05ms, whereas performing the above technique for 20,000 objects cost only 0.4ms total (0.00002ms each).[/quote]
But this does not mean 20,000 queries = .05*20,000. I don't know what it equals because I have never used it. It is also a lot easier than implementing it on cpu as he seems confused already anyway. I'll have to try and pull up my old version of this on the cpu and time it. Sounds about right, but again .05ms might just be the overhead and starting the query process. 20,000 could be .05 overhead + .0000001ms for each query. I haven't seen too many numbers on occ. queries cpu vs gpu though.

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!