Jump to content

Vulkan Render Pipeline



Having completed a hard-coded rendering pipeline for one single shader, I am now working to create a more flexible system that can handle multiple material and shader definitions. If there's one way I can describe Vulkan, it's "take every single possible OpenGL setting, put it into a structure, and create an immutable cached object based on those settings that you can then use and reuse". This design is pretty rigid, but it's one of the reasons Vulkan is giving us an 80% performance increase over OpenGL. Something as simple as disabling backface culling requires recreation of the entire graphics pipeline, and I think this option is going away. The only thing we use it for is the underside of tree branches and fronds, so that light appears to shine through them, but that is not really correct lighting. If you shine a flashlight on the underside of the palm frond it won't brighten the surface if we are just showing the result of the backface lighting.


A more correct way to do this would be to calculate the lighting for the surface normal, and for the reverse vector, and then add the results together for the final color. In order to give the geometry faces for both direction, a plugin could be added that adds reverse triangles for all the faces of a selected part of the model in the model editor. At first the design of Vulkan feels restrictive, but I also appreciate the fact that it has a design goal other than "let's just do what feels good".

Using indirect drawing in Vulkan, we can create batches of batches, sorted by shader. This feature is also available in OpenGL, and in fact is used in our vegetation rendering system. Of course the code for all this is quite complex. Draw commands, instance IDs, material IDs, entity 4x4 matrices, and material data all has to be uploaded to the GPU in memory buffers, some of which are more or less static, and some of which are updated each frame, and some for each new visibility set. It is complicated stuff, but after some time I was able to get it working. The screenshot below shows a scene with five unique objects being drawn in one single draw call, and accessing two different materials with different diffuse colors. That means an entire complex scene like The Zone will be rendered in one or just a few passes, with the GPU treating all geometry as if it was a single collapsed object, even as different objects are hidden and shown. Everyone knows that instanced rendering is faster than unique objects, but at some point the number of batches can get high enough to be a bottleneck. Indirect rendering batches the batches to eliminate this slowdown.


This is one of the features that will help our new renderer run an order of magnitude faster, for high-performance VR and regular 3D games.

  • Like 3


Recommended Comments

There are no comments to display.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Blog Entries

    • By Josh in Josh's Dev Blog 2
      I've restructured the plugin SDK for our new engine and created a new repository on Github here:
      The GMF2 format will only be used as an internal data transfer protocol for model loader plugins. Our main supported file format will be GLTF.
      As of now, the plugin system can be used to write texture loaders for different file formats, model loaders, or to modify behavior of particles in the new particle system. The FreeImage texture loader has been moved out of the core engine and into a plugin so you no longer have to include the FreeImage DLL unless you want to use it. The main supported texture format will be DDS, but the FreeImage plugin supports many common image file formats.
    • By Admin in Leadwerks Company Blog 2
      The GMF2 file format provides the fastest possible load times for 3D models. A preliminary specification and SDK for loading and saving files in the GMF2 file format is now available on GitHub here:
      A Quake 3 MD3 model loader is included as an example.
    • By Josh in Josh's Dev Blog 2
      The Leadwerks 5 beta will soon be updated with particle emitters and an example particle system plugin. Previously, I showed some impressive results with physically interactive particles that collide with and exert forces on the environment. I decided to use the plugin system for controlling particle behavior, as this offers the best performance and can be run on the physics thread. 
      A particle system plugin uses some predefined structures and functions to modify the behavior of particles when they are emitted or as they are updated. This allows for unlimited features to be added to the particle system, because anything you want can be added with a plugin. A system for sending settings to the plugin will be implemented in the future so you can adjust the plugin settings and see the results. The default particle settings and features will probably stay pretty barebones and I will just use the plugin system to add any advanced functionality since it is so flexible.
      void EmitParticle(ParticleModifier* mod, ParticleSystem* particlesystem, Particle* particle) { if (mod->emissionshape == EMISSION_SHAPE_BOX) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); } else if (mod->emissionshape == EMISSION_SHAPE_CYLINDER) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); auto l = sqrt(particle->position[0] * particle->position[0] + particle->position[1] * particle->position[1] + particle->position[2] * particle->position[2]); if (l > 0.0f) { particle->position[0] /= l; particle->position[1] /= l; particle->position[2] /= l; } } particle->position[0] += particlesystem->matrix[12]; particle->position[1] += particlesystem->matrix[13]; particle->position[2] += particlesystem->matrix[14]; } There are three other new Lua examples included. Coroutines.lua shows how a sequence of actions can be added to an entity before the game starts, and the actions will be executed in order:
      --Create model local model = CreateBox(world) --Add some behaviors to be executed in order model:AddCoroutine(MoveToPoint, Vec3(3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(-3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(0,0,0), 2) --Main loop while window:Closed() == false do world:Update() world:Render(framebuffer) end This is great for setting up cut scenes or other sequences of events.
      An example showing how to enable tessellation is also included. Tessellation is now a per-camera setting.
      camera:SetTessellation(10) The number you input is the size in pixels of the tessellated primitives. Use zero to disable tessellation. Tessellation is disabled by default on all cameras.
      Finally, an example showing how to use a texture loader plugin is included. All you have to do is load the plugin and after that textures can be loaded in VTF format:
      local vtfloader = LoadPlugin("Plugins/VTF.dll") local tex = LoadTexture("Materials/wall01.vtf")  
  • Create New...