Jump to content

Josh

Staff
  • Content Count

    15,992
  • Joined

  • Last visited

Everything posted by Josh

  1. @gamecreator More info here:
  2. Previously I described how multiple cameras can be combined in the new renderer to create an unlimited depth buffer. That discussion lead into multi-world rendering and 2D drawing. Surprisingly, there is a lot of overlap in these features, and it makes sense to solve all of it at one time. Old 2D rendering systems are designed around the idea of storing a hierarchy of state changes. The renderer would crawl through the hierarchy and perform commands as it went along, rendering all 2D elements in the order they should appear. It made sense for the design of the first graphics cards, but this style of rendering is really inefficient on modern graphics hardware. Today's hardware works best with batches of objects, using the depth buffer to handle which object appears on top. We don't sort 3D objects back-to-front because it would be monstrously inefficient, so why should 2D graphics be any different? We can get much better results if we use the same fast rendering techniques we use for 3D graphics and apply it to 2D shapes. After all, the only difference between 3D and 2D rendering is the shape of the camera projection matrix. For this reason, Turbo Engine will use 2D-in-3D rendering for all 2D drawing. You can render a pure 2D scene by setting the camera projection mode to orthographic, or you can create a second orthographic camera and render it on top of your 3D scene. This has two big implications: Performance will be incredibly fast. I predict 100,000 uniquely textured sprites will render pretty much instantaneously. In fact anyone making a 2D PC game who is having trouble with performance will be interested in using Turbo Engine. Advanced 3D effects will be possible that we aren't used to seeing in 2D. For example, lighting works with 2D rendering with no problems, as you can see below. Mixing of 3D and 2D elements will be possible to make inventory systems and other UI items. Particles and other objects can be incorporated into the 2D display. The big difference you will need to adjust to is there are no 2D drawing commands. Instead you have persistent objects that use the same system as the 3D rendering. Sprites The primary 2D element you will work with is the Sprite entity, which works the same as the 3D sprites in Leadwerks 4. Instead of drawing rectangles in the order you want them to appear, you will use the Z position of each entity and let the depth buffer take care of the rest, just like we do with 3D rendering. I also am adding support for animation frames and other features, and these can be used with 2D or 3D rendering. Rotation and scaling of sprites is of course trivial. You could even use effects like distance fog! Add a vector joint to each entity to lock the Z axis in the same direction and Newton will transform into a nice 2D physics system. Camera Setup By default, with a zoom value of 1.0 an orthographic camera maps so that one meter in the world equals one screen pixel. We can position the camera so that world coordinates match screen coordinates, as shown in the image below. auto camera = CreateCamera(world); camera->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); camera->SetRange(-1,1); iVec2 screensize = framebuffer->GetSize(); camera->SetPosition(screensize.x * 0.5, -screensize.y * 0.5); Note that unlike screen coordinates in Leadwerks 4, world coordinates point up in the positive direction. We can create a sprite and reset its center point to the upper left hand corner of the square like so: auto sprite = CreateSprite(world); sprite->mesh->Translate(0.5,-0.5,0); sprite->mesh->Finalize(); sprite->UpdateBounds(); And then we can position the sprite in the upper left-hand corner of the screen and scale it: sprite->SetColor(1,0,0); sprite->SetScale(200,50); sprite->SetPosition(10,-10,0); This would result in an image something like this, with precise alignment of screen pixels: Here's an idea: Remember the opening sequence in Super Metroid on SNES, when the entire world starts tilting back and forth? You could easily do that just by rotating the camera a bit. Displaying Text Instead of drawing text with a command, you will create a text model. This is a series of rectangles of the correct size with their texture coordinates set to display a letter for each rectangle. You can include a line return character in the text, and it will create a block of multiple lines of text in one object. (I may add support for text made out of polygons at a later time, but it's not a priority right now.) shared_ptr<Model> CreateText(shared_ptr<World> world, shared_ptr<Font> font, const std::wstring& text, const int size) The resulting model will have a material with the rasterized text applied to it, shown below with alpha blending disabled so you can see the mesh background. Texture coordinates are used to select each letter, so the font only has to be rasterized once for each size it is used at: Every piece of text you display needs to have a model created for it. If you are displaying the framerate or something else that changes frequently, then it makes sense to create a cache of models you use so your game isn't constantly creating new objects. If you wanted, you could modify the vertex colors of a text model to highlight a single word. And of course all kinds of spatial transformations are easily achieved. Because the text is just a single textured mesh, it will render very fast. This is a big improvement over the DrawText() command in Leadwerks 4, which performs one draw call for each character. The font loading command no longer accepts a size. You load the font once and a new image will be rasterized for each text size the engine requests internally: auto font = LoadFont("arial.ttf"); auto text = CreateText(foreground, font, "Hello, how are you today?", 18); Combining 2D and 3D By using two separate worlds we can control which items the 3D camera draws and which item 2D camera draws: (The foreground camera will be rendered on top of the perspective camera, since it is created after it.) We need to use a second camera so that 2D elements are rendered in a second pass with a fresh new depth buffer. //Create main world and camera auto world = CreateWorld(); auto camera = CreateCamera(world); auto scene = LoadScene(world,"start.map"); //Create world for 2D rendering auto foreground = CreateWorld() auto fgcam = CreateCamera(foreground); fgcam->SetProjection(PROJECTION_ORTHOGRAPHIC); fgcam->SetClearMode(CLEAR_DEPTH); fgcam->SetRange(-1,1); auto UI = LoadScene(foreground,"UI.map"); //Combine rendering world->Combine(foreground); while (true) { world->Update(); world->Render(framebuffer); } Overall, this will take more work to set up and get started with than the simple 2D drawing in Leadwerks 4, but the performance and additional control you get are well worth it. This whole approach makes so much sense to me, and I think it will lead to some really cool possibilities. As I have explained elsewhere, performance has replaced ease of use as my primary design goal. I like the results I get with this approach because I feel the design decisions are less subjective.
  3. Definitely, this is the best time to discuss everything. I think this is the right way to go, even though it makes simple drawing harder. But which is harder, learning one system that does everything you want, or learning two systems that have some overlapping capabilities but work differently? 😨 I am working on text now. It works by creating a textured model for the text you want to draw. For an FPS counter, for example, I recommend creating models for each number you display, and storing them in a C++ map or Lua table, like this: void UpdateFPSDisplay(int fps) { currentFPSDisplayModel->Hide(); if (textcache[fps] == nullptr) textcache[fps] = CreateText(world, font, String(fps), 12); currentFPSDisplayModel = textcache[fps]; currentFPSDisplayModel->Show(); } This is more complicated than just calling DrawText(fps) but it is far more powerful. Text rendering with this system will be instantaneous, whereas it can be quite slow in Leadwerks 4, which performs one draw call for each character.
  4. I think it mostly boils down to making 3D models in the UI possible. I could see some other things working nicely like particles. So if we need to account for this, let’s just cut the foreplay and have one method that handles everything. Theres also a fundamental shift in my approach to design. I’m not trying to make the easiest to use game engine anymore, because the easiest game to develop is a pre-built asset flip. I am not interested in making Timmy’s first game maker so if that means Turbo Engine trades ease of use in the simple case for more speed and functionality I am okay with that. I almost feel like people don’t respect an API that isn’t difficult to use. we’ll see, there is still some time before this is finalized.
  5. Yes, you would just call Combine again. It just adds the indicated world to a list. It’s one-way, so “Combine” might not be the best nomenclature.
  6. Okay, I have rendering with multiple worlds working now. The command is like this: void World::Combine(shared_ptr<World> world) All the cameras in the added world will be used in rendering, using the contents of the world they belong to. There is no ordering of the worlds, instead the cameras within the world are drawn with the same rules as a single world with multiple cameras: By default, cameras are rendered in the order they are created. A camera order setting can be used to override this and become the primary sorting method. (If two cameras have the same order value, then the creation order is used to sort them.) So you can do something like this: //Create main world auto mainworld = CreateWorld(); auto maincamera = CreateCamera(world); //Create world for HUD rendering auto foreground = CreateWorld(); auto fgcam = CreateCamera(foreground); fgcam->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); fgcam->SetClearMode(CLEAR_DEPTH); auto healthbar = CreateSprite(foreground); mainworld->Combine(foreground); //Draw both worlds. Ortho HUD camera will be drawn on top since it was created last. mainworld->Render(framebuffer); That means that drawing 2D graphics on top of 3D graphics requires a world and camera to be created for this purpose. There is no "2D" commands really, there is just orthographic camera projection. This is also really really flexible, and the same fast rendering the normal 3D graphics use will make 2D graphics ridiculously fast. Leadwerks 4 used a lot of render-to-texture and caching to make the GUI fast, but that will be totally unnecessary here, I think.
  7. You would probably have three sprites with a material with alpha masking enabled, and position them along the Z axis the way you would want them to appear. Imagine if you were doing it in 3D. Which you are. Rotation of sprites is absolutely no problem with this approach, along with many other options. You can also use polygon meshes very easily for 2D rendering. For example, the clock hands could be a model, perhaps loaded from an SVG file.
  8. I thought perhaps 2D rendering would require an orthographic camera to be created and rendered on top of the 3D camera, but that would invalidate the depth buffer contents, and we want to hang onto that for post-processing effects. Unless we insert a post-processing step in between camera passes like this: Render perspective camera. Draw post-processing effects. Clear depth buffer and render 2D elements.
  9. I started digging into 2D drawing recently because @Lethal Raptor Games was asking about it in the private forum. I found that our model rendering system works well for 2D sprites as well, but in Vulkan you have no guarantee what order objects will be drawn in. I realized how stupid it is to do back-to-front rendering of 2D objects and that we should just use the depth buffer to handle this. I mean, we don't render 3D objects in order by distance, so why are 2D objects any different? I think the 2D rendering will take place with a separate framebuffer, and then the results will be drawn on top of the 3D view. I think DOOM 2016 did this, for the same reasons. See the section here on "User Interface": http://www.adriancourreges.com/blog/2016/09/09/doom-2016-graphics-study/ Naturally this means that 3D-in-2D elements are very simple to add, but it also means you only have a Z-position for ordering. The rendering speed of this will be unbelievably fast. 100,000 unique sprites each with a different image would be absolutely no problem.
  10. There are examples here: https://www.leadwerks.com/learn?page=API-Reference_Object_Math_Transform_Point
  11. I will have to experiment some more with this. Interestingly, this overlaps with some problems with 2D drawing. Now in reality there is no such thing as 2D graphics on your computer, it is always 3D graphics. I think my approach here will be to stop trying to hide the fact that 2D is really 3D even if it does not fit our conceptions of how it should be. Stay tuned...
  12. You could also just put the files in an unencrypted zip.
  13. Okay, it looks like that will probably not work. The data management is just too complicated. I think a filter value will probably work best, because that can be handled easily in the culling routine.
  14. You cannot read files from encrypted zip files. Otherwise the encryption would be pointless because you could just read anyone's game data! Keep those files in your game folder instead of in a zip package.
  15. I'm going to try adding a command like this: void World::AddRenderPass(shared_ptr<World> world, const int order) The order value can be -1 (background), 1 (foreground), or 0 (mix with current world). No guarantee yet but I will try and see how well it works.
  16. Since you say it is not acceptable as a professional tool, why are you here? I just received ten orders for the enterprise version from a little company called Northrop Grumman, but I guess you are the expert.
  17. In an abstract sense, yes, but there’s a thousand more details than that. The strictness of both Vulkan and the multithreaded architecture mean I can’t design things that are “fast and loose” anymore.
  18. What might work better is to have a layer / tag system that lets different cameras have a filter to render certain types of objects. To skip a camera, you can either hide it or set the projection mode to zero.
  19. There is a World::Render() command which basically says "tell the rendering thread to start using this world for rendering". So rendering two different worlds in one pass would be sort of difficult to manage.
  20. This would probably not be practical, because the rendering thread is so disconnected from the game logic thread.
  21. Current generation graphics hardware only supports up to a 32-bit floating point depth buffer, and that isn't adequate for large-scale rendering because there isn't enough precision to make objects appear in the correct order and prevent z-fighting. After trying out a few different approaches I found that the best way to support large-scale rendering is to allow the user to create several cameras. The first camera should have a range of 0.1-1000 meters, the second would use the same near / far ratio and start where the first one left off, with a depth range of 1000-10,000 meters. Because the ratio of near to far ranges is what matters, not the actual distance, the numbers can get very big very fast. A third camera could be added with a range out to 100,000 kilometers! The trick is to set the new Camera::SetClearMode() command to make it so only the furthest-range camera clears the color buffer. Additional cameras clear the depth buffer and then render on top of the previous draw. You can use the new Camera::SetOrder() command to ensure that they are drawn in the order you want. auto camera1 = CreateCamera(world); camera1->SetRange(0.1,1000); camera1->SetClearMode(CLEAR_DEPTH); camera1->SetOrder(1); auto camera2 = CreateCamera(world); camera2->SetRange(1000,10000); camera2->SetClearMode(CLEAR_DEPTH); camera2->SetOrder(2); auto camera3 = CreateCamera(world); camera3->SetRange(10000,100000000); camera3->SetClearMode(CLEAR_COLOR | CLEAR_DEPTH); camera3->SetOrder(3); Using this technique I was able to render the Earth, sun, and moon to-scale. The three objects are actually sized correctly, at the correct distance. You can see that from Earth orbit the sun and moon appear roughly the same size. The sun is much bigger, but also much further away, so this is exactly what we would expect. You can also use these features to render several cameras in one pass to show different views. For example, we can create a rear-view mirror easily with a second camera: auto mirrorcam = CreateCamera(world); mirrorcam->SetParent(maincamera); mirrorcam->SetRotation(0,180,0); mirrorcam=>SetClearMode(CLEAR_COLOR | CLEAR_DEPTH); //Set the camera viewport to only render to a small rectangle at the top of the screen: mirrorcam->SetViewport(framebuffer->GetSize().x/2-200,10,400,50); This creates a "picture-in-picture" effect like what is shown in the image below: Want to render some 3D HUD elements on top of your scene? This can be done with an orthographic camera: auto uicam = CreateCamera(world); uicam=>SetClearMode(CLEAR_DEPTH); uicam->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); This will make 3D elements appear on top of your scene without clearing the previous render result. You would probably want to move the UI camera far away from the scene so only your HUD elements appear in the last pass.
  22. People wanted a new engine so I am making a new engine. It takes years to develop these things. Just switching over to Vulkan took six months to get basic rendering working. Anyone who needs the performance and scale of the new engine is welcome to use it, otherwise I am not going to try to convince them. I expect that right now is going to be pretty much the lowest point of engagement because we have a new technology that has been a WIP for several years and isn't ready yet. And there is going to be some change in the community because Turbo Engine has a different type of user than Leadwerks has.
  23. You are using (0,0,-1) for your normals. Try using (0,1,0).
  24. Josh

    Next Steps

    @Lethal Raptor Games I think the next big feature will be to make the terrain system hierarchal. I will explain soon.
×
×
  • Create New...