Author: Giuseppe Modarelli

  • Descriptor Sets Management

    I’m writing the renderer for the Immersive open world RPG Tides of Revival. Here is a screenshot from the current version of the game.

    In today’s post I want to share my approach to GPU resource bindings.

    The DirectX 12 renderer for the game relies on bindless and HLSL 6.6 Dynamic Resources for almost all GPU resources. Still, there are some resources that need to be bound to shaders. For those, we have to use descriptors.

    Tides’ renderer is written on top of a custom fork of The-Forge, so we didn’t have to write our own management code for heaps and descriptor handles (among many other things), and could instead focus on building a GPU abstraction layer tailored to the needs of the game.

    I wanted to build a thin GPU-resource binding abstraction layer that would satisfy two main goals:

    • Make it easy to bind resources (textures, buffers, samplers, etc.) without having to deal with lower-level descriptors and descriptor sets data structures
    • Make the HLSL code the place where we declare resources, without having to duplicate their declarations in Zig

    To achieve both goals I extended The-Forge’s shader compilation pipeline. HLSL shaders are first compiled offline via DXC and their blobs are loaded during application startup.

    After a shader blob is successfully loaded, a list of ShaderReflectionDescriptors is generated from each D3D12_SHADER_DESC.BoundResources. You can find the full source code on Github, but here is what the struct looks like:

    typedef struct ShaderReflectionDescriptor
    {
    const char Name[32]; // Resource identifier used as binding key from higher-level APIs
    D3D_SHADER_INPUT_TYPE Type; // The fields from the `D3D12_SHADER_INPUT_BIND_DESC` struct.
    D3D_SRV_DIMENSION Dimension;
    uint32_t BindPoint;
    uint32_t BindCount;
    uint32_t Space;
    } ShaderReflectionDescriptor;

    The list of ShaderReflectionDescriptors is then processed to generate Descriptors that get associated to one of 4 DescriptorSets. The full source code is available here, but here’s the gist of what’s going on with a real example from a gaussian blur shader we’re using in the game.

    Any given shader can declare resources that belong to one of 4 “spaces”, where each space has a different update frequency (per-draw, per-pass, per-frame and persistent).

    struct BlurData { ... };
    cbuffer g_CBO : register(b0, SPACE_PerFrame)
    {
    BlurData g_blur_data;
    };
    Texture2D<float4> g_input : register(t0, SPACE_Persistent);
    RWTexture2D<float4> g_output : register(u1, SPACE_Persistent);
    // Rest of the shader

    This HLSL code will generate 1 per-frame and 1 persistent DescriptorSets. The first set will contain a single Descriptor for a constant buffer named g_CBO, the second will contain two Descriptors, one for a read-only 2D texture named g_input and one for a read-write 2D texture named g_output.

    From Zig we can then associate GPU resources handles to descriptors in descriptor sets.

    NOTEThis code is part of the graphics abstraction layer and there are data types and APIs that are still work in progress. I might write about in the future.

    // Per-frame descriptors
    for (0..zf.frames_in_flight_count) |frame_index| {
    const resource_binding_descs = [_]zf.ResourceBindingDesc{
    .{
    .name = "g_CBO",
    .binding_type = .buffer,
    .buffer_handle = gfx.gauss_blur_constant_buffers[frame_index],
    },
    };
    zf.updateDescriptorSet(
    &resource_binding_descs,
    .per_frame,
    @intCast(frame_index),
    gfx.gauss_blur_vertical_shader,
    gfx.gauss_blur_vertical_material.passes[0].per_frame_descriptor_set
    );
    }
    // Persistent descriptors
    {
    const resource_binding_descs = [_]zf.ResourceBindingDesc{
    .{
    .name = "g_input",
    .binding_type = .render_target,
    .render_target_handle = gfx.gbuffer0,
    },
    .{
    .name = "g_output",
    .binding_type = .render_texture,
    .render_texture_handle = gfx.gauss_blur_a,
    },
    };
    zf.updateDescriptorSet(
    &resource_binding_descs,
    .persistent,
    0,
    gfx.gauss_blur_horizontal_shader,
    gfx.gauss_blur_horizontal_material.passes[0].persistent_descriptor_set
    );
    }

    There is still a bit of duplication since if you rename a resource in a shader, you need to update the binding code on Zig as well. But my plan is to use as few shaders as possibile for the game, so the need for more complex solutions (like code-gen or a more data-driven approach) is not needed just yet.

    If you’re interested in graphics programming and what to chat about the renderer of Tides of Revival, you can find me on our Discord Server.

  • UGC – Map Editor

    Introduction

    I was contracted by Scattershot as Senior Tech Artist to work on the User Generated Content (UGC) Tools for their competitive FPS title Project Athena. The UGC Tools were supposed to be a set of tools that would allow players to create maps, game modes and skins for characters and weapons directly in game.

    I have worked on the tools together with an engineer colleague of mine, Giulio Auriemma, and our responsibility were roughly split this way:

    • Giulio was responsible for the overall code architecture, data serialization, server communication and game runtime
    • I was responsilbe for the runtime mesh modeling and rendering

    We started working on the map editor, which would then set the foundations for all other UGC tools.

    Here is a quick video of me demostrating the mesh modeling tools we have implemented.

    Technical Details

    Building a map editor, or UGC tools in general, is quite an undertaking, and this was my first professional experience with the Unreal Engine and C++. So I’ve started my task by researching what the engine source code had to offer.

    After a couple of days of research and exploration I found two internal modules that became the foundation of our UGC implementation, namely the Mesh Modeling Toolset and the Interactive Tools Framework. These modules are used by the Unreal Engine Editor to enable all of their modeling modes and tools.

    These modules are internal to the engine, are supposed to be used within the editor, but luckily they are not editor module. That means that we could use them at runtime, but we had to figure out what classes and functionalities we needed to code to make the modules work at runtime, from within the game.

    I ended up writing a non trivial amount of C++ code to expose all the needed modeling tools and to create an implementation of the Interactive Tool Framework runtime within which all the tools were managed. The framework in particular exposed functionalities like managing the active tools, gizmos, actors interactions like selection and transform, and the undo/redo system.

    Finally, all the mesh modeling and tools interactions I wrote were rendered into a separate editor window (as you can see from the timelapse above). My colleague Giulio was responsible for this part of the work. This specific functionality (running the map editor into a separate window) proved to be one of the most complicated to implement, since we were not allowed to make engine modifications. This added quite a degree of complexity to our task, but allowed us to implement features, like Docking UI, that are really useful to a user workflow working with our tools.

    Here is another quick video showcasing the Docking UI.

  • Character Creator

    Introduction

    At Twin Drums I was responsible for the asset pipeline of our characters, from the assets organization in Blender, to the in-game character creator written in C#.

    Together with the character artists, we came up with a way to split up our characters so that we could reuse as much of the existing assets as possible, without having to create new ones for every body feature combinations.

    As for the various body parts, we also wanted to have a single rig to reuse the same animations for all of our characters. To that hand I’ve created a main rig for the initial body shape of our characters and then duplicated it so that it would adapt to the second body shape we introduced to the game. The two rigs shared the exact same bones hierarchy, which made it possible to retarget animations from one to the other, with minor manual tweaks.

    Blender Character Assembler

    In order to author animations, clothes and accessories, I’ve created a Blender AddOn that our artists and animators would use to assemble a character in Blender to use as base for their work.

    This tool served many purposes:

    • Skin clothes and accessories to different body shapes
    • Author animations on the same assets that would then be assembled by the game
    • Automatic FBX export to Unity to ensure right settings and naming conventions
    • Bulk export of all character art assets – body parts, hairstyles, clothes, accessories, rigs and animations – whenever a change to the rigs was made

    Here’s a quick video showing the Character Assembler in Blender.

    In-Engine Character Assembler

    As counterpart to the Blender Character Assembler, I’ve built a Unity Character Assembler to validate all the assets produced for characters, not only bodies, hairstyles and clothes, but also weapons, tools and animations.

    This tool then also became the runtime client of the game’s Character Creator. The same C# class used in the editor is also used in the shipped version of the game.

    Here’s a quick video showing the Character Assembler in Unity.

    In-Game Character Creator

    You can see the Character Creator in action in this Early Access Trailer video

  • Level Editor: Suwan

    Introduction

    Suwan is a set of procedural tools I have developed together with our level designer, Jennika, at Twin Drums for the Afrofantasy MMORPG The Wagadu Chronicles. The world of the game is made up of tens of islands that appear once in the game and then disappear when a new cycle (in the game’s lore) begins, living space for new islands.

    Since we are an indie company with a small budget and a small team, we knew from day one we needed some procedural tools to help us produce the islands for the game.

    Here is a video overview of how Suwan works.

    Features

    The biggest strength of Suwan is that it enables Jennika to never leave the editor and to be in a constant flow while working on the islands. Here is a list of some of the features offered by Suwan:

    • Map generation and editing
    • Unity Terrain generation and painting
    • Ocean and Lakes generation, with SDF-driven shores detection
    • Cliffs and ramps generation to connect different terrain elevations
    • Biomes and sub-biomes vegetation scattering
    • Gameplay resources scattering
    • NPC encounters scattering
    • Minimap generation

    Technical Details

    The main goal of Suwan has always been to improve productivity. Together with Jennika we have identified the most time consuming tasks in her manual process and constantly replaced those with procedural or automated tools. Once we had a set of tools to replace the most time-consuming and manual tasks, I focused on performance: if the tools are slow and unresponsive they break your flow and make you less productive.

    At the heart of Suwan is a set of Compute Shaders that take as input all the parameters Jennika needs to define an island, and generate a set of 2D Textures that encode variours landscape features: elevation, terrain/water type, sub-biomes, scattered vegation and so on.

    These 2D textures are then fed to other C# systems to generate their 3D, in-engine counterparts: painted terrain tiles with lakes and oceans, scattered vegetation, interactable resources and NPCs and more.

  • Day Night Cycle

    This is one of the first systems I’ve developed at Twin Drums for the Afrofantasy MMORPG The Wagadu Chronicles.

    The Day Night Cycle has gone through many iterations, but what I present here is the final version that’s shipped in the Early Access of the game on Steam.

    A day in Wagadu lasts 24 hours like here on Earth, but in-game those 24 hours pass by in 20 minutes. This is important for gameplay reasons, as there are things you can only do at night in the game and we wanted our players to experience the full set of features in a normal play session.

    Here’s a video of 24 hours passing by in Wagadu in just 30 seconds.

    The system allows for many customizations. Here’s a few of them:

    • duration in minutes of a 24-hours Wagadian day
    • daytime vs nighttime speed curve
    • light color gradient and light intensity curve
    • ambient light type and gradients
    • fog color gradient and density curve
    • post-process override curves for specific effects (like Bloom)

    Night Bioluminescence

    One of my favorite feature of the Day Night Cycle is Night Bioluminesence: at night plants start to glow, and we can control the intensity of the glow by a setting on the plants’ materials and the Bloom Threshold and Intensity curves, as I demonstrate in the video below (the exaggerated bloom intensity is for demonstration purpose alone, don’t do this at home!).