|
8170
|
Lethal Company Sim Cruiser (G25) by @BigSaltyBeans Lethal Company Sim Cruiser (G25) by @BigSaltyBeans | Stardance...
|
BigSaltyBeans
|
1
|
10.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-21T03:59:40Z", "hours": 10.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "SimCruiser Ship! SimCruiser G25 is a Lethal Company mod that changes the Company Cruiser’s input logic to make it compatible with a Logitech G25 Sim Wheel. It’s been a journey but this mod is such a blast to use when playing with friends and still support tech like Cruiser jumps and interacting. Features: Adds input bindings for the G25 wheel Rewrites the Company Cruiser input system to make driving feel more realistic with the G25 Shifter changes between Drive, Reverse and Park Wheel is 1:1 with the cruiser Pedals accelerate and break You can install this Mod through Thunderstore or download the .dll directly. Have fun! Liam", "repo": "https://github.com/LRGuess/SimCruiser-G25", "demo": "https://youtu.be/XbiNcqWlCIg"}]...
|
[{"post_id": 12852, "ts": "202 [{"post_id": 12852, "ts": "2026-06-21T01:35:28Z", "hours": 0.7128, "text": "Devlog 5 After I got the steering working, I wanted to be able to use the G25’s shifter to change gears in the company cruiser. This required more expirimenting to figure out how the shifter is interpreted by unity’s input system. Investigating It turns out that every position on the shifter is assigned a button, and that button is considered pressed when the shifter is in that gear. Below are the button assignements I found: In 6 Gear Mode 1st Gear: Button 9 2nd Gear: Button 10 3rd Gear: Button 11 4th Gear: Button 12 5th Gear: Button 13 6th Gear: Button 14 Reverse: Button 15 In +/- Mode Forward: Button 9 Back: Button 10 I also took a look at how the player sets the cruiser’s gears, and it’s with a method called ShiftToGearAndSync(desiredGear) . So when changing gears this is the method I need to call. Implementation Getting the shifting working was pretty easy. First I created 2 new actions and bindings for the Drive and Reverse gears and bound then to the 3rd and 4th gears on the shifter. I chose those as it keep it centered and feels more natural. Then I added some code into the same method we edited for the steering that: Stores the current gear. Checks if the stick is in a position: If yes: Is the stick in the same position as it was last frame? If yes ignore If no set the cruiser’s gear to the new gear If no: Set the cruiser’s gear into park as no gear is selected. So in our case, what IRL is neutral is park, forward (or 3rd) is drive and back (or 4th) is reverse. Feels pretty epic when driving the cruiser! Next up: The mod is basically done, but there are a few things we can still do: Add bindings for things like the radio, wipers, etc Ship the final version in Thunderstore Cheers! Liam"}, {"post_id": 12688, "ts": "2026-06-20T18:43:30Z", "hours": 2.1686, "text": "Devlog 4 Finished re-writing the cruiser’s steering!Not too much to say, but still a pretty big update: The cruiser’s steering wheel is now 1:1 with the physical G25 in multiple ways. Originally the Company Cruiser was driven by holding down the A or D keys to steer. The wheel would gradually rotate in the direction you were holding and stop rotating but maintain the rotation on release. This logic obviously sucks when you want to use a sim wheel, because you want the CC’s wheel rotation to be a direct replica of the physical wheel’s rotation. This means the logic had to be redone, therefore a rewrite of the cruiser’s GetVehicleInput() needed to be done. Implementation Zeekerss original code for steering was this: float num = __instance.steeringWheelTurnSpeed; __instance.steeringInput = Mathf.Clamp(__instance.steeringInput + __instance.moveInputVector.x * num * Time.deltaTime, -3f, 3f); Modifing these lines was pretty straightforward: Remove num entirely as we done need the wheel’s turn speed Don’t multiply by Time.deltaTime as it’ll be direct Don’t add the old input values to the new one as we want 1:1 with the G25 In the end the code looked like this: __instance.steeringInput = Mathf.Clamp(__instance.moveInputVector.x * 3f, -3f, 3f); We need to multiply by 3 as we want the max to be 3 but the input can only reach a max value of 1. Animation This new wheel logic worked great except one problem: The G25 would be at 90deg at it’s max but the game would show it at 225deg. The solution to this was to adjust the anim’s clam values from +/- 1 to +/- 2.4. Why? Because we need to remove 1.4f from the max value, and for some reason the anim needs us to add it onto 1 instead of remove ¯ \\ _ (ツ)_/¯ Next Up: Going forward all that’s really left is using the G25’s shifter to change the cruiser’s gears from Park, Reverse and Drive! Cheers, Liam"}, {"post_id": 12295, "ts": "2026-06-20T02:01:25Z", "hours": 0.9897, "text": "Devlog 3 Tried driving the company cruiser with the wheel, and a few things need to be done: The CC’s steering needs to be turned 1:1 with the wheel’s input Acceleration and breaking needs to be rewritten to feel more realistic Published the input part of the mod onto Thunderstore tho to make testing easier. I’ll make a more interesting update once I start working that out! Cheers, Liam"}, {"post_id": 12045, "ts": "2026-06-19T17:21:31Z", "hours": 4.5825, "text": "Devlog 2 Finally finished the first and most cruicial part of the mod! The first step to this mod was to let Lethal Company’s input system receive bindings from the G25. Now this in theory should have been a fast and easy implementation. Here’s how it turned into a 4 hour journey: Writing base code I started off by writing the essential parts of the mod. This mod uses BenInEx and HarmonyLib to patch my custom code into Lethal Company, so I made the public BasicUnityPlugin class that applies all patches in the .GetExecutingAssembly() . This means that it’ll look at all the code in the built .dll and patch automatically instead of me specifying what classes I want to patch. The AddWheelBindings class This is the center of attention when it comes to the input patching. This class needs to do 3 things: Find the localPlayerController Get all InputActionAssets that affect the localPlayerController Add new bindings to the ActionAsset’s InputAction that needs it By fiddling around with the input system in another project, I found the name of the wheel to be <HID::G25 Racing Wheel> . Knowing the device name, I was ready to start patching the new inputs into the input assets. The class that we will be patching is the StartOfRound class as it handles a lot of the local player and game logic. First I found the local player through StartOfRound.Instance?.localPlayerController; , then found the player’s playerActions asset and assigned a var to that. Then I made a function that takes the InputActionAsset, finds the actions we want, such as the ‘move’ action, and applies a new binding to that action. For example, we can grab the ‘interact’ action with InputAction interact = actions.FindAction(\"Interact\"); and give it a new binding specifically for the G25: jump.AddBinding(\"<HID::G25 Racing Wheel>/button7\").WithInteraction(\"press\"); Weirdness Once I finished adding all the bindings to the right actions along with some null checks, I build the .dll and got to testing. Right off the bat nothing worked, but a quick change to what method we needed to patch… kind of fixed things? Something weird was going on: the bindings I’d made to the look action were working, but none of the other were. 3 hour long story short, Zeekerss made his PlayerControllerB read from two different instances of one InputActionAsset, so the bindings and actions across the two were the same but each one was being referanced in different spots. The camera worked right away because the line that read the camera’s input was: playerActions.Movement.Look.ReadValue<Vector2>() as opposed to all other controls read as such: InputSystem.actions.FindAction(\"Move\").ReadValue<Vector2>(); . The InputActionAsset I was patching was the one created in the player’s Awake() method, playerActions , and not the second global one.The only reason I found for Zeekerss to make two is that the mouse was being read by the first because the look input is not one that can be rebound in the settings menu. All the controls pulled from the second asset are controls that can be rebound in settings, meaning that the second one was the player’s local input settings when the first was the default. Fix All that needed to be done for the G25 controls to be read for all the actions was to also patch InputSystem.actions ’s asset. It took about 3 1/2 hours to figure this out, but at least it works now! Next up: Going forward we need to: Test the current system on the cruiser See what needs changes to feel more realistic Cheers! Liam"}, {"post_id": 11658, "ts": "2026-06-19T00:16:10Z", "hours": 1.3206, "text": "Devlog 1 Recently, and as summer’s been coming around, I’ve been playing more and more lethal company with the bros. Now I wouldn’t say we’re sweats or anything, but we’re pretty good and can get to 3000+ quotas without many resets. On thing that’s made that possible is the wonderful Company Cruiser! For anyone who’s driven the Company Cruiser before, you already know how hellish it can be. The gear changes, janky steering and funny physics make it truly an experience to manage. I’ve been driving this thing for a while now and would like to think that I’m quite proficient, but looking around my room got me thinking: What if I could use my Logitech steering wheel to drive the cruiser? Now of course the base game has no support for it and the input system used to drive the Cruiser is 1-0 based and not Vector based (W key accelerates at max when a pedal would be an axis), but having modded lethal before I was sure It’d be feasible. So I got cracking. Configuring the Wheel The Logitech G25 is a legacy wheel and is not supported in newer Logitech software. After much fiddling I figured out I needed to use a very old version of Logitech Gaming Software to get it to register properly on my computer. Bringing it into Unity To start I needed to learn how the wheel interacts in and gets registered by Unity, specifically Unity 2022.3.9f , the version Zeekerss used to build LC. I began by making a unity project in that version with the URP Template, as it already had a preconfigured controller using the new Input System. I also downloaded a unity project by Juandarn called SpaceVR that used the Logitech G25 to figure out how it configured the wheel in it’s input system. Here’s what I learnt: The G25 is interpreted as a joystick with the wheel being Left and Right (X), Accelerator being Y+, and the Brake being Y-. All the buttons are regular. The Dpad is also a joystick. Z axis is the accelerator pedal but inverted (???) RZ axis is the brake pedal but inverted (also ???) Trigger is just button 1 Slider is inverted Clutch pedal Looking at Lethal Company’s input system As the goal is to be able to drive the CC with the G25, it was also time to start looking at how Zeekerss configured the Input system for LC. I did this by using an asset ripper to reverse engineer Zeekerss LC build and turn it back into a Unity Project. So now that I have LC’s source code, it turns out it’s pretty simple and all the controls under one Action Map, so in the end I’ll need to add the G25’s axis configurations to the Actions themselves. I don’t think it’ll be too hard but for that I’ll need to start on some research related to interacting with a player’s input system through an LC mod patch. Next up: Programming basic mod for Lethal that adds the wheel’s axis to the base game’s player input actions. Looking into how the Company Cruiser interprets player inputs to see what needs to be reprogrammed. This will be quite the fun mod and journey, post a comment if you have any ideas or suggestions! Cheers, Liam"}]...
|
Title: Lethal Company Sim Cruiser (G25) by @BigSal Title: Lethal Company Sim Cruiser (G25) by @BigSaltyBeans | Stardance
Devlogs:
Devlog 1 Recently, and as summer’s been coming around, I’ve been playing more and more lethal company with the bros. Now I wouldn’t say we’re sweats or anything, but we’re pretty good and can get to 3000+ quotas without many resets. On thing that’s made that possible is the wonderful Company Cruiser! For anyone who’s driven the Company Cruiser before, you already know how hellish it can be. The gear changes, janky steering and funny physics make it truly an experience to manage. I’ve been driving this thing for a while now and would like to think that I’m quite proficient, but looking around my room got me thinking: What if I could use my Logitech steering wheel to drive the cruiser? Now of course the base game has no support for it and the input system used to drive the Cruiser is 1-0 based and not Vector based (W key accelerates at max when a pedal would be an axis), but having modded lethal before I was sure It’d be feasible. So I got cracking. Configuring the Wheel The Logitech G25 is a legacy wheel and is not supported in newer Logitech software. After much fiddling I figured out I needed to use a very old version of Logitech Gaming Software to get it to register properly on my computer. Bringing it into Unity To start I needed to learn how the wheel interacts in and gets registered by Unity, specifically Unity 2022.3.9f , the version Zeekerss used to build LC. I began by making a unity project in that version with the URP Template, as it already had a preconfigured controller using the new Input System. I also downloaded a unity project by Juandarn called SpaceVR that used the Logitech G25 to figure out how it configured the wheel in it’s input system. Here’s what I learnt: The G25 is interpreted as a joystick with the wheel being Left and Right (X), Accelerator being Y+, and the Brake being Y-. All the buttons are regular. The Dpad is also a joystick. Z axis is the accelerator pedal but inverted (???) RZ axis is the brake pedal but inverted (also ???) Trigger is just button 1 Slider is inverted Clutch pedal Looking at Lethal Company’s input system As the goal is to be able to drive the CC with the G25, it was also time to start looking at how Zeekerss configured the Input system for LC. I did this by using an asset ripper to reverse engineer Zeekerss LC build and turn it back into a Unity Project. So now that I have LC’s source code, it turns out it’s pretty simple and all the controls under one Action Map, so in the end I’ll need to add the G25’s axis configurations to the Actions themselves. I don’t think it’ll be too hard but for that I’ll need to start on some research related to interacting with a player’s input system through an LC mod patch. Next up: Programming basic mod for Lethal that adds the wheel’s axis to the base game’s player input actions. Looking into how the Company Cruiser interprets player inputs to see what needs to be reprogrammed. This will be quite the fun mod and journey, post a comment if you have any ideas or suggestions! Cheers, Liam
Devlog 2 Finally finished the first and most cruicial part of the mod! The first step to this mod was to let Lethal Company’s input system receive bindings from the G25. Now this in theory should have been a fast and easy implementation. Here’s how it turned into a 4 hour journey: Writing base code I started off by writing the essential parts of the mod. This mod uses BenInEx and HarmonyLib to patch my custom code into Lethal Company, so I made the public BasicUnityPlugin class that applies all patches in the .GetExecutingAssembly() . This means that it’ll look at all the code in the built .dll and patch automatically instead of me specifying what classes I want to patch. The AddWheelBindings class This is the center of attention when it comes to the input patching. This class needs to do 3 things: Find the localPlayerController Get all InputActionAssets that affect the localPlayerController Add new bindings to the ActionAsset’s InputAction that needs it By fiddling around with the input system in another project, I found the name of the wheel to be <HID::G25 Racing Wheel> . Knowing the device name, I was ready to start patching the new inputs into the input assets. The class that we will be patching is the StartOfRound class as it handles a lot of the local player and game logic. First I found the local player through StartOfRound.Instance?.localPlayerController; , then found the player’s playerActions asset and assigned a var to that. Then I made a function that takes the InputActionAsset, finds the actions we want, such as the ‘move’ action, and applies a new binding to that action. For example, we can grab the ‘interact’ action with InputAction interact = actions.FindAction("Interact"); and give it a new binding specifically for the G25: jump.AddBinding("<HID::G25 Racing Wheel>/button7").WithInteraction("press"); Weirdness Once I finished adding all the bindings to the right actions along with some null checks, I build the .dll and got to testing. Right off the bat nothing worked, but a quick change to what method we needed to patch… kind of fixed things? Something weird was going on: the bindings I’d made to the look action were working, but none of the other were. 3 hour long story short, Zeekerss made his PlayerControllerB read from two different instances of one InputActionAsset, so the bindings and actions across the two were the same but each one was being referanced in different spots. The camera worked right away because the line that read the camera’s input was: playerActions.Movement.Look.ReadValue<Vector2>() as opposed to all other controls read as such: InputSystem.actions.FindAction("Move").ReadValue<Vector2>(); . The InputActionAsset I was patching was the one created in the player’s Awake() method, playerActions , and not the second global one.The only reason I found for Zeekerss to make two is that the mouse was being read by the first because the look input is not one that can be rebound in the settings menu. All the controls pulled from the second asset are controls that can be rebound in settings, meaning that the second one was the player’s local input settings when the first was the default. Fix All that needed to be done for the G25 controls to be read for all the actions was to also patch InputSystem.actions ’s asset. It took about 3 1/2 hours to figure this out, but at least it works now! Next up: Going forward we need to: Test the current system on the cruiser See what needs changes to feel more realistic Cheers! Liam
Devlog 3 Tried driving the company cruiser with the wheel, and a few things need to be done: The CC’s steering needs to be turned 1:1 with the wheel’s input Acceleration and breaking needs to be rewritten to feel more realistic Published the input part of the mod onto Thunderstore tho to make testing easier. I’ll make a more interesting update once I start working that out! Cheers, Liam
Devlog 4 Finished re-writing the cruiser’s steering!Not too much to say, but still a pretty big update: The cruiser’s steering wheel is now 1:1 with the physical G25 in multiple ways. Originally the Company Cruiser was driven by holding down the A or D keys to steer. The wheel would gradually rotate in the direction you were holding and stop rotating but maintain the rotation on release. This logic obviously sucks when you want to use a sim wheel, because you want the CC’s wheel rotation to be a direct replica of the physical wheel’s rotation. This means the logic had to be redone, therefore a rewrite of the cruiser’s GetVehicleInput() needed to be done. Implementation Zeekerss original code for steering was this: float num = __instance.steeringWheelTurnSpeed; __instance.steeringInput = Mathf.Clamp(__instance.steeringInput + __instance.moveInputVector.x * num * Time.deltaTime, -3f, 3f); Modifing these lines was pretty straightforward: Remove num entirely as we done need the wheel’s turn speed Don’t multiply by Time.deltaTime as it’ll be direct Don’t add the old input values to the new one as we want 1:1 with the G25 In the end the code looked like this: __instance.steeringInput = Mathf.Clamp(__instance.moveInputVector.x * 3f, -3f, 3f); We need to multiply by 3 as we want the max to be 3 but the input can only reach a max value of 1. Animation This new wheel logic worked great except one problem: The G25 would be at 90deg at it’s max but the game would show it at 225deg. The solution to this was to adjust the anim’s clam values from +/- 1 to +/- 2.4. Why? Because we need to remove 1.4f from the max value, and for some reason the anim needs us to add it onto 1 instead of remove ¯ \ _ (ツ)_/¯ Next Up: Going forward all that’s really left is using the G25’s shifter to change the cruiser’s gears from Park, Reverse and Drive! Cheers, Liam
Devlog 5 After I got the steering working, I wanted to be able to use the G25’s shifter to change gears in the company cruiser. This required more expirimenting to figure out how the shifter is interpreted by unity’s input system. Investigating It turns out that every position on the shifter is assigned a button, and that button is considered pressed when the shifter is in that gear. Below are the button assignements I found: In 6 Gear Mode 1st Gear: Button 9 2nd Gear: Button 10 3rd Gear: Button 11 4th Gear: Button 12 5th Gear: Button 13 6th Gear: Button 14 Reverse: Button 15 In +/- Mode Forward: Button 9 Back: Button 10 I also took a look at how the player sets the cruiser’s gears, and it’s with a method called ShiftToGearAndSync(desiredGear) . So when changing gears this is the method I need to call. Implementation Getting the shifting working was pretty easy. First I created 2 new actions and bindings for the Drive and Reverse gears and bound then to the 3rd and 4th gears on the shifter. I chose those as it keep it centered and feels more natural. Then I added some code into the same method we edited for the steering that: Stores the current gear. Checks if the stick is in a position: If yes: Is the stick in the same position as it was last frame? If yes ignore If no set the cruiser’s gear to the new gear If no: Set the cruiser’s gear into park as no gear is selected. So in our case, what IRL is neutral is park, forward (or 3rd) is drive and back (or 4th) is reverse. Feels pretty epic when driving the cruiser! Next up: The mod is basically done, but there are a few things we can still do: Add bindings for things like the radio, wipers, etc Ship the final version in Thunderstore Cheers! Liam
Ships:
SimCruiser Ship! SimCruiser G25 is a Lethal Company mod that changes the Company Cruiser’s input logic to make it compatible with a Logitech G25 Sim Wheel. It’s been a journey but this mod is such a blast to use when playing with friends and still support tech like Cruiser jumps and interacting. Features: Adds input bindings for the G25 wheel Rewrites the Company Cruiser input system to make driving feel more realistic with the G25 Shifter changes between Drive, Reverse and Park Wheel is 1:1 with the cruiser Pedals accelerate and break You can install this Mod through Thunderstore or download the .dll directly. Have fun! Liam...
|
1793242344.9670365
|
1793328744.9670365
|
|
8178
|
GVK renderer by @gungu | Stardance
|
gungu
|
2
|
60.0
|
1009
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-09-07T14:22:35Z", "hours": 13.0, "multiplier": 19.44, "payout": 261, "blessing": "neutral", "text": "This is the second ship of my game engine / renderer. To explain, I like calling it a renderer instead of a game engine, because I purposefully tried to add as few bloat features as I could, I just wanted something that made rendering something that I don’t have to think about. More info on the README page, this isn’t a good explanation. Just know that it isn’t just a basic Vulkan abstraction, this is a full renderer with PBR, many post-processing effects, skyboxes, dynamic lights, rigged and animated meshes and frustrum culling ’cause why not. Changelog since the last ship: 2 new post-processing effects, pixelation and fog I also added a feature where shiny objects reflect the skybox, which isn’t realistic, but it adds a great sense of realism and I like to add it to my games. one of the largest changes was turning this into an actual library instead of an executable, which allows easier integration. I also made a full game as a tech demo for this project, it can be found at this link or under my stardance profile. It turned out really well, and I’m proud of myself, because I finally made a game engine that I can actually make games in easily. (The screenshot is from the game) and the smallest but still a significant change was rewriting the entire 2D rendering part of the engine for 2 reasons: it had a memory leak, and text rendering was buggy, slow and barely worked.", "repo": "https://github.com/gungugugungu/GVK-Engine", "demo": "https://github.com/gungugugungu/GVK-Engine/releases"}, {"ship_num": 2, "ts": "2026-08-17T20:15:46Z", "hours": 47.0, "multiplier": 15.86, "payout": 748, "blessing": "neutral", "text": "GVK (short for gungu vulkan) is my new renderer using the Vulkan graphics api. I miss the times when I worked with pygame and love2d, where rendering an image was one simple line of code, and I intend to bring that experience to 3D with this project. Everything is designed to be as easy as possible for the user, just writes gvk::draw_mesh(mesh, material, position, scale, rotation); and the mesh is where you want it, how you want it. But don’t mistake it, this is not a game engine, this is just a renderer, something you can build an engine on. I made this tool so my later projects have a graphical backbone to build on, but every game needs a different engine. Ever since I started graphics programming last year I knew that one day I had to learn Vulkan if I ever wanted a job in it in the future, and I’m very proud of myself for finally doing it. This was the greatest challenge I faced in programming so far, and I so glad I tackled it. And as for AI: This project was created so I could learn Vulkan, and if I copied everything from AI that would have been to effective. My goal was to use as little AI as I could, while still leaving out the headaches of development. Here’s what I used AI for: Many of the shaders were written by AI, but that’s because I haven’t learned any of the maths I need to know to write any of these shader. When a shader wasn’t too math heavy I wrote it myself, but there weren’t too many of those. The GLTF loading isn’t hard, it’s just a headache to work with it. GLTF is a horrible format for developers, but it’s the best I knew, so I rather had AI write the GLTF loading functions. I’m still glad I did that, I would have learned nothing from writing those.", "repo": "https://github.com/gungugugungu/GVK-Engine", "demo": "https://github.com/gungugugungu/GVK-Engine/releases"}]...
|
[{"post_id": 63563, "ts": "202 [{"post_id": 63563, "ts": "2026-09-07T14:07:41Z", "hours": 0.4622, "text": "I made a few minor tweaks to fog, and I started debugging it, which I ended up completely stopping and reverting, as it was useless. To be honest I’m really trying to find where this 27 minutes appeared, because I don’t remember spending half an hour on this"}, {"post_id": 61085, "ts": "2026-09-03T20:06:12Z", "hours": 0.76, "text": "FOG Yeah I just added fog because I thought it would look cool in my game. Obviously it’s over the top in this image because I wanted to show it off. I’m pretty proud of this one, definitely one of the more complex post-processing effects."}, {"post_id": 60850, "ts": "2026-09-03T14:06:32Z", "hours": 0.2822, "text": "SKYBOX REFLECTIONS This is a neat trick I did in my last engine and I’m happy I recreated it in this one. Every single shiny surface reflects the skybox, which while isn’t realistic, it does make those surfaces look a lot more shiny than what they actually are, which is a great hack. It sure as hell looks better in the environments I make."}, {"post_id": 60834, "ts": "2026-09-03T13:41:51Z", "hours": 0.5078, "text": "Screen-space reflections would be a major challenge, but I learned that making the skybox reflect off of shiny surfaces is a pretty good way of making your renders seem much better. As of right now it technically works, but I came to realize that I wasn’t loading materials right, so if an image didn’t have a roughness texture, then it would end up completely shiny. I’m almost done fixing it."}, {"post_id": 59082, "ts": "2026-08-31T18:07:57Z", "hours": 0.5186, "text": "I also fixed a bug where the bloom’s blur looped around to the other side of the screen."}, {"post_id": 58887, "ts": "2026-08-31T13:42:45Z", "hours": 0.8592, "text": "PIXELATION POST-PROCESSING EFFECT I made my post-processing system so expandable that I just casually added it ‘cause I thought it would look cool in the game I’m making. I’m sure as hell right about that."}, {"post_id": 58002, "ts": "2026-08-30T10:41:46Z", "hours": 3.0525, "text": "I wasted so much time trying to optimize it all to lead to nothing, but I did end up optimizing some stuff with the Surfaces along with actual color blending for them. I did end up optimizing a bit, but that’s basically nothing. This is the most worthless update to this entire project, I don’t even know how I spent 3 hours on this."}, {"post_id": 52632, "ts": "2026-08-22T21:10:39Z", "hours": 1.3003, "text": "I completely rewrote the text rendering in the Surface class and also fixed a giant memory leak. Here’s a picture of it in use in my game."}, {"post_id": 51872, "ts": "2026-08-21T19:11:56Z", "hours": 0.6219, "text": "I rewrote the texture loading part of GLTF loading because it just plain didn’t work since I updated everything to tinygltf3, but I didn’t notice because it was after the demo. Anyways, it’s done now."}, {"post_id": 51319, "ts": "2026-08-20T22:03:59Z", "hours": 0.4669, "text": "I also rewrote the single mesh gltf loading function to return the material and the entire vertex and index arrays, because I’ll need them for a feature I’m making for my game. This is slowly turning into engine development and I kinda like it."}, {"post_id": 50898, "ts": "2026-08-20T11:18:10Z", "hours": 4.5806, "text": "I worked for over 4 hours, but the renderer finally works as a library!!! That’s right, just put it in as a git submodule, add it as a subdirectory to you CMake project, and there it is! Way too much work for something so simple, but I had to debug a whole lot of stuff. I also edited the README to feature usage instructions and also how to try the demo."}, {"post_id": 49166, "ts": "2026-08-17T20:06:43Z", "hours": 1.8336, "text": "THE GREAT DEBUGGING and A DEMO This is the final post as long as everything works as intended. I fixed every single validation layer error that occurred, so this project is officially bug-free (to my knowledge) . I also made a really simple demo that I uploaded to the github releases tab of the repository. I also finalized the readme, so now the project also has a fancy description. What a ride, I love this project."}, {"post_id": 47631, "ts": "2026-08-15T17:22:29Z", "hours": 0.6358, "text": "FINAL POLISHING (mostly just making the API more useable) I genuinely can’t believe this. I did it, I made a renderer in Vulkan. Well, I’m still not in the clear yet, but I’m very close. Changelog: updated Skin struct to include find_joint() function added Pose struct updated existing skinned mesh systems to work with the Pose struct added sample_clip(), compute_global_pose(), compute_skinning_matrices(), pose_set_local(), pose_set_global(), pose_get_global(), update_animation() and finalize_pose(). These are all helpers for making work with animated and rigged meshes a whole lot easier moved all of the functions inside the gvk namespace, because they were about half and half before."}, {"post_id": 47191, "ts": "2026-08-14T22:29:34Z", "hours": 3.0672, "text": "SKINNED MESHES AND RIGGING This renderer is extremely close to done. I just implemented fully rigged (and animated) meshes. They render properly and animations work too (although I didn’t really care enough to show an example) . Now I am at the last 2 steps of this renderer before starting work on a game in it: 1. Polish up all the API commands so it’s more user friendly. For example, skinned meshes work and can be adjusted manually, but I have a really good idea on how to make working with them super easy, so I’ll definitely implement that. 2. Make a demo that includes basically everything I have developed so far. This step also includes writing compatibility for multiple platforms, because so far I’ve only tested this project on Ubuntu, and I’ll definitely need a windows version too. And that’s it, the project is nearing it’s completion. I can’t wait to make a proper game in this, which I’ll document too of course. Also, I’m writing all of this while I’m on a vacation in Denmark, so I’m quite far from home, but I’m extremely motivated to get this project finished."}, {"post_id": 45337, "ts": "2026-08-11T18:43:26Z", "hours": 3.4436, "text": "SSAO (screen-space ambient occlusion) This one was fun. Ambient occlusion is just a little shadow thing at the edge of meshes. It is not realistic, technically it doesn’t exist in the real world, yet it looks so much better with it. To be honest I don’t have a lot to say about this, it’s done and it’s cool."}, {"post_id": 42894, "ts": "2026-08-07T21:01:17Z", "hours": 1.6756, "text": "SCENE LOADING I just got home from a 10 day adventure that’ll never forget, but I’m back to code, and to get myself to shake into the civilized world again I decided to do a simpler feature before shadow mapping, so I implemented full scene loading from GLTF files. It’s a pretty handy feature, I’m glad I finally made it. It doesn’t just load models and materials, but lights too, pretty handy."}, {"post_id": 35091, "ts": "2026-07-26T09:27:19Z", "hours": 0.3281, "text": "NORMAL MAPPING This was a really easy update, most of the shader changes were copied from my last engine, and the CPU side of things was just changing the vertex struct and loading the tangents in the mesh loading function. But it works."}, {"post_id": 34858, "ts": "2026-07-25T22:07:52Z", "hours": 3.8317, "text": "Well well well. Seems like it’s time for LIGHTING !!!!! finally This was not easy, and yet it was made so much easier by having done it once before last year. I added support for a directional light, a dynamic (aka not hard coded, so pratically infinite amount of) point lights and spot lights, full PBR, and I also made a little handy editor to go along with it, although that’s not in the renderer that’s just in the demo project. Anyways, this was an incredible journey filled with pain and suffering like never before, and I’m so glad I’m through it all. And if this was a challenge, I don’t know what shadow mapping is going to be, which is going to be basically right now, I’m going to add tanget space normal mapping and then it’s already shadowmapping. I’m mentally unprepared, but oh well what can I do?"}, {"post_id": 33983, "ts": "2026-07-24T11:18:42Z", "hours": 1.8858, "text": "This change is not too visible, but it is a major change in the engine MATERIALS Everybody who worked in any sort of 3D graphics knows what materials are. And those know, that the next step after this is lighting . This wouldn’t have been that big of a change, if not for one issue. I’m developing on linux, and every once in a while my GPU drivers uninstall themselves, so everything runs on the integrated graphics on the CPU. That happened, but I thought that the code just became really slow because I was sending 6 images to the GPU every single frame, and that slowed it down. So I spent a bunch of time making a large optimization, and it worked like a charm, it ran fast. Then I found out the entire thing was running on the CPU and I didn’t even need to optimize it. Anyway, it’s here, and it’s fast"}, {"post_id": 33448, "ts": "2026-07-23T12:32:42Z", "hours": 2.7142, "text": "POST-PROCESSING STACK and TONEMAPPING AND COLOR GRADING (post-processing effect 5) This is the finale to the post-processing saga, because I’m finished with tonemapping and color grading. Everything will look really good and cool and whatever, altough I’ve been coding non-stop for 2.5 hours and I don’t really care to give an explanation right now. I don’t have to explain color grading everybody knows that, and tonemapping is the process of mapping the internal HDR render image to an LDR (whatever it’s called) image that the display can actually render properly, how cool is that?"}, {"post_id": 32903, "ts": "2026-07-22T11:24:50Z", "hours": 1.3728, "text": "BLOOM (post-processing effect 4) I’m almost done with all of the post-processing effects, and this was by far the hardest of them all, but I managed to push through it. I’m really proud of myself, because I finally understand Vulkan enough where I didn’t need any sort of outside help to debug this error. I think I can finally call myself someone who understands Vulkan now. Anyway, now I just need to make tonemapping and the effects are done. After that I just make the simplest pipeline to composite it all, and I’m done with that as well."}, {"post_id": 32861, "ts": "2026-07-22T09:31:30Z", "hours": 1.115, "text": "VIGNETTE (post-processing effect 3) This was the most fun I’ve had debugging something in a while. This was really simple to implement, although I can’t really say a lot. The initialization was the same as the other effects, the actual logic of it was pretty similar, the shader wasn’t too hard to write too, but added up it was pretty bad. I accidentally left one of the push parameters in the CPU code as an int, but a float in the GPU code, which I ended up having to chase down for quite a while. The values are over the top on the image so it is more visible on the screenshot, of course I won’t leave it like this. Also, I have written a new readme for the github page. I just kinda wanted to, and thought why not?"}, {"post_id": 32630, "ts": "2026-07-21T22:19:26Z", "hours": 0.3061, "text": "BOX BLUR (post-processing effect 2) I have to admit this took 11 minutes, and the rest of the 5 minutes I spent in this devlog was spent debugging 3 new errors so I can make a post about this feature too. It took so little because I literally just copied and renamed the gaussian blur functions, and changed the shader. Still, I have box blur now. Actually, I think there’s quite a difference between the 2 blurs, quite cool. Of course now that I look at it it’s barely visible in the screenshots."}, {"post_id": 32617, "ts": "2026-07-21T22:00:32Z", "hours": 2.6194, "text": "I thought how cool would it be if I just did a whole 10 hour devlog with all of post-processing in it, but I realized I wouldn’t be able to show off all the cool stuff I’ve done, so I’m doing it effect by effect, anyway, GAUSSIAN BLUR (post-processing effect 1) But not just that, I also brainstormed out the entire post-processing system, and “implemented” it. I didn’t exactly implement it yet because I need to finish all the effects, but it’s there. I still have to finish: box blur, bloom, vignette and tonemapping, and I also want support for custom effects, but that’ll be really easy so I don’t want to put it on this list."}, {"post_id": 31844, "ts": "2026-07-20T15:30:20Z", "hours": 1.5975, "text": "MSAA !! (multisampled anti-aliasing) I always viewed anit-aliasing as this mystery thing that can make a game prettier and I’ll never know how it works. I have to say, it was way easier than expected, most of the time I spent was on some stupid bug or general debugging. You can’t really see the difference in a small screenshot, but it looks so much better full screen."}, {"post_id": 31201, "ts": "2026-07-19T12:30:48Z", "hours": 1.5542, "text": "2D rendering!! I have to admit this feature was kinda easy to implement, because most of it was ripped straight from my last game engine, but I did it because I think it is a really smart way of handling how a 2D system should work. Everything relies on the Surface class, the display itself is a surface, the loaded images are surfaces, and those surfaces can draw to eachother. This makes the system easy yet expandable. I think it’s kinda cool. It was heavily inspired by how pygame does things."}, {"post_id": 31151, "ts": "2026-07-19T10:09:03Z", "hours": 1.3306, "text": "And just like that, the next major update is done! SKYBOXES and other stuff Here’s a full changelog of the last 3 posts: I wrote a really handy shader compiler to make this update just a bit easier, now it automatically compiles all shaders at once I don’t have to do it one by one I wrote a compositing pass that finally has a use case as can be seen right now. The skybox and the objects are rendered separately to separate images, and the compositing pass combines them and renders them to the screen together. Might sound simple, and I have to admit if I understood Vulkan a bit better it might have been, but right now I’m really proud of myself for it. Of course the headline feature: The Skybox . This is most of what I’ve been working on for the last 3 updates. It’s done in a really cool way. The easy way of doing a skybox is to just render it as one giant object and then bim bam boom there it is, but sadly that has a couple of issues, the largest one is that this is extremely lazy. So I did it the smart way instead. What you’re seeing there is actually a cube a tad bit smaller than the monkey, but it’s rendered in a separate pass, where I only pass the camera’s rotation and not the position, and I also have the depth disabled. I render that to a separate image, use the compositing pass, and there it is, a beautiful, very stable and very professional skybox. I’m almost done rambling, but I have to mention one last feature, and that is the loading of equirectangular images into a cubemap texture . I think I’ve typed enough for this update, but if you care you can look it up or like look at my code. Next up: 2D rendering and I have some great ideas for it"}, {"post_id": 30908, "ts": "2026-07-18T21:44:56Z", "hours": 1.5617, "text": "This is a change that cannot be seen, but the composite pipeline is actually working. This is a really simple step in the graphics pipeline, it takes the images from the previous passes and combines them to make a final image. This will be useful for future features like 2D drawing, post-processing, the skybox, etc., so I’m happy I managed to implement it. It’s not something that can be seen with the naked eye, but believe me it’s there and I’m so happy for it. As always, this is next to some generic bugfixes here and there, but it’s been 1.5 hours and I forgot what I fixed and what I didn’t so if you happen to care enough this is open-source just like go and check it out."}, {"post_id": 30852, "ts": "2026-07-18T19:58:13Z", "hours": 1.6753, "text": "I’m well into developing the skybox system along with a compositing pass , but since I needed to write so many new shaders for this update, I decided to write a simple shader compiler, and I kinda want to show it off so it’s not lost in the giant list of changes that I’ll push in the next update. That’s it, I rest my case, that’s all I wanted until I’m finished."}, {"post_id": 30103, "ts": "2026-07-17T12:46:44Z", "hours": 2.2475, "text": "I finished frustum culling . You’ve probably seen this feature in some random wannabe gamedevs post on tiktok saying how they “optimized their game” by enabling frustum culling in Unity and calling themselves genius for it. Well it wasn’t as easy as pressing a button for me. Here’s how it works: I generate an axis aligned bounding box (AABB) for every object when loaded. Every frame I generate 6 planes for each boundary of the camera, the left, right, bottom, top, near and far planes. Then I also calculate the world position AABB for each mesh, which is just translating the original AABB and making a new one from that’s boundary. From that, it’s actually quite simple to compare if the box is inside the frustum or not, though it’s kinda hard to explain so I won’t bother, but I just run that for every object, and it basically makes it, so if an object is not inside the camera’s vision, then it just makes it disappear. I attached the majority of the logic in the second picture"}, {"post_id": 28824, "ts": "2026-07-15T10:08:18Z", "hours": 1.3122, "text": "I implemented a fully automatic mipmap system . Basically, mipmaps are lower quality version of a texture that appear the farther you are from an object. The image I attached is of mipmap layer 4, but of course this was set manually to showcase the system working."}, {"post_id": 28331, "ts": "2026-07-14T14:52:20Z", "hours": 0.6636, "text": "I wrote a giant list on what I want to implement into this framework to make it full. I’m pretty sure I’ll be done in about 150-200 hours, which I have about 40 days to make before the end of this thing. That is 3.75 - 5 hours of coding in a day. Anyway, here’s the list: TODO FEATURES: lighting gpu scene data uniform buffer ambient light directional light point lights tangent space normal mapping tangent generation in gltf loading dynamic light count proper api for handling lights shadow mapping depth-only pipeline shadow map image light space matrix shadow pass shadow sampling in the main fragment shader shadow bias PCF filtering material system material struct (albedo map, normal map, roughness map, emissive map, scalar tint and roughness and metallic factors) material descriptor layout default fallback textures gltf material loading draw_mesh api update emissive support in the shader creating and destroying materials for user-made materials mipmaps and proper sample management mipmap generation on upload mip count calculation sampler cache (so samplers are never duplicated) instanced rendering per-instance storage buffer draw_mesh_instanced api write the shader for it (gl_InstanceIndex) move to indirect drawing so the instance count can eventually come from gpu-side data skybox cubemap loading skybox pipeline skybox shader set_skybox api frustrum culling bounding sphere per mesh (compute in loading by finding the disctance from the centroid across all vectires) frustrum plane extraction (gribb-hartmann) cull loop stats readout post processing stack fullscreen traingle pipeline HDR tonemapping guassian blur api box blur api bloom vignette 2D rendering quad mesh 2D pipeline surface class default surface draw_surface draw_rect font atlas rendering MSAA multisample draw image resolve attachment configurable sample count"}, {"post_id": 28166, "ts": "2026-07-14T09:18:04Z", "hours": 0.8786, "text": "I have to admit I’m pretty proud of myself. I implemented the proper transformations for objects , I also made a good camera system , and I fixed the uv mapping of objects, so textures finally work properly. All this in under an hour."}, {"post_id": 19382, "ts": "2026-06-30T17:57:36Z", "hours": 1.0408, "text": "To be completely honest this engine development thing is going a lot more awful than I could have imagined. I tried to implement a proper camera and transformations for the objects, and now nothing works, yet I barely changed anything. I have been staring at this function for like 2 hours, I’ve tried everything, and nothing works."}, {"post_id": 18575, "ts": "2026-06-29T19:26:50Z", "hours": 1.0686, "text": "I finally finished the guide (🥳🥳🥳), so it’s time to make the game engine I want . My first improvement was adding a render queue, which basically means I can render anything in a much user friendly way."}, {"post_id": 18301, "ts": "2026-06-29T12:31:38Z", "hours": 0.8061, "text": "Thanks to the major rework of the allocation of descriptors, I have managed to implemented textures into rendering. I haven’t made texture loading yet, since it’ll be an engine feature and I’m working on project setup , so enjoy the beautiful pink-black checkerboard pattern on Suzie, the Blender monkey."}, {"post_id": 18277, "ts": "2026-06-29T11:27:47Z", "hours": 1.3389, "text": "I did a giant rework of the descriptor allocation to make handling textures a breeze in the future. Yes, I did mostly just follow the guide, but I am really getting the hang of how Vulkan works, and the guide is ending in a couple of chapers, and the rest will be up to me, and I already feel ready for the task. I have to admit it doesn’t exactly work yet, but I’m working on patching it, it shouldn’t be an issue."}, {"post_id": 18218, "ts": "2026-06-29T09:03:15Z", "hours": 0.4683, "text": "Mesh rendering is finally complete. This is a full 3D scene with a little monkey, rendered perfectly in perspective."}, {"post_id": 17917, "ts": "2026-06-28T21:08:40Z", "hours": 1.0294, "text": "So mesh loading and rendering works, although I haven’t gotten the world matrix and transformations quite right😅 The guide I’m using to learn Vulkan actually recommended I use fastgltf, but I ended up going with tinygltf, as I’ve used it before and have a bunch more experience in it, and oh boy is having experience a good feeling when learning VK"}, {"post_id": 17836, "ts": "2026-06-28T19:36:25Z", "hours": 1.1339, "text": "I’m progressing rather fast, I implemented a full mesh buffer system, which allows me to render any mesh. Mesh loading will be the next task, but until then the best display I can do is a colorful rectangle, I hope that’s good enough"}, {"post_id": 16164, "ts": "2026-06-26T08:40:00Z", "hours": 0.4853, "text": "Implementing a proper graphics pipeline was surprisingly easy. I remembered a lot from my experience in sokol and of course the guide was quite helpful as well. Got from a compute shader to a proper pipeline very fast. I’m still very eager to get back to the fun part of graphics programming and not be stuck in setup hell, but the next step is writing the mesh buffers, so that’ll be fun."}, {"post_id": 11826, "ts": "2026-06-19T08:22:42Z", "hours": 1.095, "text": "I finished the ImGui implementation, I’m starting to get the hang of things. I’m still following vkguide.dev for the setup phase, but I’m eager to finally get to the fun part of graphics programming"}, {"post_id": 4078, "ts": "2026-06-07T08:34:21Z", "hours": 1.0794, "text": "After about an hour of following vkguide.dev, I managed to make a little compute shader that display 16×16 cells of a gradient. Great start!"}]...
|
Title: GVK renderer by @gungu | Stardance
Devlogs Title: GVK renderer by @gungu | Stardance
Devlogs:
After about an hour of following vkguide.dev, I managed to make a little compute shader that display 16×16 cells of a gradient. Great start!
I finished the ImGui implementation, I’m starting to get the hang of things. I’m still following vkguide.dev for the setup phase, but I’m eager to finally get to the fun part of graphics programming
Implementing a proper graphics pipeline was surprisingly easy. I remembered a lot from my experience in sokol and of course the guide was quite helpful as well. Got from a compute shader to a proper pipeline very fast. I’m still very eager to get back to the fun part of graphics programming and not be stuck in setup hell, but the next step is writing the mesh buffers, so that’ll be fun.
I’m progressing rather fast, I implemented a full mesh buffer system, which allows me to render any mesh. Mesh loading will be the next task, but until then the best display I can do is a colorful rectangle, I hope that’s good enough
So mesh loading and rendering works, although I haven’t gotten the world matrix and transformations quite right😅 The guide I’m using to learn Vulkan actually recommended I use fastgltf, but I ended up going with tinygltf, as I’ve used it before and have a bunch more experience in it, and oh boy is having experience a good feeling when learning VK
Mesh rendering is finally complete. This is a full 3D scene with a little monkey, rendered perfectly in perspective.
I did a giant rework of the descriptor allocation to make handling textures a breeze in the future. Yes, I did mostly just follow the guide, but I am really getting the hang of how Vulkan works, and the guide is ending in a couple of chapers, and the rest will be up to me, and I already feel ready for the task. I have to admit it doesn’t exactly work yet, but I’m working on patching it, it shouldn’t be an issue.
Thanks to the major rework of the allocation of descriptors, I have managed to implemented textures into rendering. I haven’t made texture loading yet, since it’ll be an engine feature and I’m working on project setup , so enjoy the beautiful pink-black checkerboard pattern on Suzie, the Blender monkey.
I finally finished the guide (🥳🥳🥳), so it’s time to make the game engine I want . My first improvement was adding a render queue, which basically means I can render anything in a much user friendly way.
To be completely honest this engine development thing is going a lot more awful than I could have imagined. I tried to implement a proper camera and transformations for the objects, and now nothing works, yet I barely changed anything. I have been staring at this function for like 2 hours, I’ve tried everything, and nothing works.
I have to admit I’m pretty proud of myself. I implemented the proper transformations for objects , I also made a good camera system , and I fixed the uv mapping of objects, so textures finally work properly. All this in under an hour.
I wrote a giant list on what I want to implement into this framework to make it full. I’m pretty sure I’ll be done in about 150-200 hours, which I have about 40 days to make before the end of this thing. That is 3.75 - 5 hours of coding in a day. Anyway, here’s the list: TODO FEATURES: lighting gpu scene data uniform buffer ambient light directional light point lights tangent space normal mapping tangent generation in gltf loading dynamic light count proper api for handling lights shadow mapping depth-only pipeline shadow map image light space matrix shadow pass shadow sampling in the main fragment shader shadow bias PCF filtering material system material struct (albedo map, normal map, roughness map, emissive map, scalar tint and roughness and metallic factors) material descriptor layout default fallback textures gltf material loading draw_mesh api update emissive support in the shader creating and destroying materials for user-made materials mipmaps and proper sample management mipmap generation on upload mip count calculation sampler cache (so samplers are never duplicated) instanced rendering per-instance storage buffer draw_mesh_instanced api write the shader for it (gl_InstanceIndex) move to indirect drawing so the instance count can eventually come from gpu-side data skybox cubemap loading skybox pipeline skybox shader set_skybox api frustrum culling bounding sphere per mesh (compute in loading by finding the disctance from the centroid across all vectires) frustrum plane extraction (gribb-hartmann) cull loop stats readout post processing stack fullscreen traingle pipeline HDR tonemapping guassian blur api box blur api bloom vignette 2D rendering quad mesh 2D pipeline surface class default surface draw_surface draw_rect font atlas rendering MSAA multisample draw image resolve attachment configurable sample count
I implemented a fully automatic mipmap system . Basically, mipmaps are lower quality version of a texture that appear the farther you are from an object. The image I attached is of mipmap layer 4, but of course this was set manually to showcase the system working.
I finished frustum culling . You’ve probably seen this feature in some random wannabe gamedevs post on tiktok saying how they “optimized their game” by enabling frustum culling in Unity and calling themselves genius for it. Well it wasn’t as easy as pressing a button for me. Here’s how it works: I generate an axis aligned bounding box (AABB) for every object when loaded. Every frame I generate 6 planes for each boundary of the camera, the left, right, bottom, top, near and far planes. Then I also calculate the world position AABB for each mesh, which is just translating the original AABB and making a new one from that’s boundary. From that, it’s actually quite simple to compare if the box is inside the frustum or not, though it’s kinda hard to explain so I won’t bother, but I just run that for every object, and it basically makes it, so if an object is not inside the camera’s vision, then it just makes it disappear. I attached the majority of the logic in the second picture
I’m well into developing the skybox system along with a compositing pass , but since I needed to write so many new shaders for this update, I decided to write a simple shader compiler, and I kinda want to show it off so it’s not lost in the giant list of changes that I’ll push in the next update. That’s it, I rest my case, that’s all I wanted until I’m finished.
This is a change that cannot be seen, but the composite pipeline is actually working. This is a really simple step in the graphics pipeline, it takes the images from the previous passes and combines them to make a final image. This will be useful for future features like 2D drawing, post-processing, the skybox, etc., so I’m happy I managed to implement it. It’s not something that can be seen with the naked eye, but believe me it’s there and I’m so happy for it. As always, this is next to some generic bugfixes here and there, but it’s been 1.5 hours and I forgot what I fixed and what I didn’t so if you happen to care enough this is open-source just like go and check it out.
And just like that, the next major update is done! SKYBOXES and other stuff Here’s a full changelog of the last 3 posts: I wrote a really handy shader compiler to make this update just a bit easier, now it automatically compiles all shaders at once I don’t have to do it one by one I wrote a compositing pass that finally has a use case as can be seen right now. The skybox and the objects are rendered separately to separate images, and the compositing pass combines them and renders them to the screen together. Might sound simple, and I have to admit if I understood Vulkan a bit better it might have been, but right now I’m really proud of myself for it. Of course the headline feature: The Skybox . This is most of what I’ve been working on for the last 3 updates. It’s done in a really cool way. The easy way of doing a skybox is to just render it as one giant object and then bim bam boom there it is, but sadly that has a couple of issues, the largest one is that this is extremely lazy. So I did it the smart way instead. What you’re seeing there is actually a cube a tad bit smaller than the monkey, but it’s rendered in a separate pass, where I only pass the camera’s rotation and not the position, and I also have the depth disabled. I render that to a separate image, use the compositing pass, and there it is, a beautiful, very stable and very professional skybox. I’m almost done rambling, but I have to mention one last feature, and that is the loading of equirectangular images into a cubemap texture . I think I’ve typed enough for this update, but if you care you can look it up or like look at my code. Next up: 2D rendering and I have some great ideas for it
2D rendering!! I have to admit this feature was kinda easy to implement, because most of it was ripped straight from my last game engine, but I did it because I think it is a really smart way of handling how a 2D system should work. Everything relies on the Surface class, the display itself is a surface, the loaded images are surfaces, and those surfaces can draw to eachother. This makes the system easy yet expandable. I think it’s kinda cool. It was heavily inspired by how pygame does things.
MSAA !! (multisampled anti-aliasing) I always viewed anit-aliasing as this mystery thing that can make a game prettier and I’ll never know how it works. I have to say, it was way easier than expected, most of the time I spent was on some stupid bug or general debugging. You can’t really see the difference in a small screenshot, but it looks so much better full screen.
I thought how cool would it be if I just did a whole 10 hour devlog with all of post-processing in it, but I realized I wouldn’t be able to show off all the cool stuff I’ve done, so I’m doing it effect by effect, anyway, GAUSSIAN BLUR (post-processing effect 1) But not just that, I also brainstormed out the entire post-processing system, and “implemented” it. I didn’t exactly implement it yet because I need to finish all the effects, but it’s there. I still have to finish: box blur, bloom, vignette and tonemapping, and I also want support for custom effects, but that’ll be really easy so I don’t want to put it on this list.
BOX BLUR (post-processing effect 2) I have to admit this took 11 minutes, and the rest of the 5 minutes I spent in this devlog was spent debugging 3 new errors so I can make a post about this feature too. It took so little because I literally just copied and renamed the gaussian blur functions, and changed the shader. Still, I have box blur now. Actually, I think there’s quite a difference between the 2 blurs, quite cool. Of course now that I look at it it’s barely visible in the screenshots.
VIGNETTE (post-processing effect 3) This was the most fun I’ve had debugging something in a while. This was really simple to implement, although I can’t really say a lot. The initialization was the same as the other effects, the actual logic of it was pretty similar, the shader wasn’t too hard to write too, but added up it was pretty bad. I accidentally left one of the push parameters in the CPU code as an int, but a float in the GPU code, which I ended up having to chase down for quite a while. The values are over the top on the image so it is more visible on the screenshot, of course I won’t leave it like this. Also, I have written a new readme for the github page. I just kinda wanted to, and thought why not?
BLOOM (post-processing effect 4) I’m almost done with all of the post-processing effects, and this was by far the hardest of them all, but I managed to push through it. I’m really proud of myself, because I finally understand Vulkan enough where I didn’t need any sort of outside help to debug this error. I think I can finally call myself someone who understands Vulkan now. Anyway, now I just need to make tonemapping and the effects are done. After that I just make the simplest pipeline to composite it all, and I’m done with that as well.
POST-PROCESSING STACK and TONEMAPPING AND COLOR GRADING (post-processing effect 5) This is the finale to the post-processing saga, because I’m finished with tonemapping and color grading. Everything will look really good and cool and whatever, altough I’ve been coding non-stop for 2.5 hours and I don’t really care to give an explanation right now. I don’t have to explain color grading everybody knows that, and tonemapping is the process of mapping the internal HDR render image to an LDR (whatever it’s called) image that the display can actually render properly, how cool is that?
This change is not too visible, but it is a major change in the engine MATERIALS Everybody who worked in any sort of 3D graphics knows what materials are. And those know, that the next step after this is lighting . This wouldn’t have been that big of a change, if not for one issue. I’m developing on linux, and every once in a while my GPU drivers uninstall themselves, so everything runs on the integrated graphics on the CPU. That happened, but I thought that the code just became really slow because I was sending 6 images to the GPU every single frame, and that slowed it down. So I spent a bunch of time making a large optimization, and it worked like a charm, it ran fast. Then I found out the entire thing was running on the CPU and I didn’t even need to optimize it. Anyway, it’s here, and it’s fast
Well well well. Seems like it’s time for LIGHTING !!!!! finally This was not easy, and yet it was made so much easier by having done it once before last year. I added support for a directional light, a dynamic (aka not hard coded, so pratically infinite amount of) point lights and spot lights, full PBR, and I also made a little handy editor to go along with it, although that’s not in the renderer that’s just in the demo project. Anyways, this was an incredible journey filled with pain and suffering like never before, and I’m so glad I’m through it all. And if this was a challenge, I don’t know what shadow mapping is going to be, which is going to be basically right now, I’m going to add tanget space normal mapping and then it’s already shadowmapping. I’m mentally unprepared, but oh well what can I do?
NORMAL MAPPING This was a really easy update, most of the shader changes were copied from my last engine, and the CPU side of things was just changing the vertex struct and loading the tangents in the mesh loading function. But it works.
SCENE LOADING I just got home from a 10 day adventure that’ll never forget, but I’m back to code, and to get myself to shake into the civilized world again I decided to do a simpler feature before shadow mapping, so I implemented full scene loading from GLTF files. It’s a pretty handy feature, I’m glad I finally made it. It doesn’t just load models and materials, but lights too, pretty handy.
SSAO (screen-space ambient occlusion) This one was fun. Ambient occlusion is just a little shadow thing at the edge of meshes. It is not realistic, technically it doesn’t exist in the real world, yet it looks so much better with it. To be honest I don’t have a lot to say about this, it’s done and it’s cool.
SKINNED MESHES AND RIGGING This renderer is extremely close to done. I just implemented fully rigged (and animated) meshes. They render properly and animations work too (although I didn’t really care enough to show an example) . Now I am at the last 2 steps of this renderer before starting work on a game in it: 1. Polish up all the API commands so it’s more user friendly. For example, skinned meshes work and can be adjusted manually, but I have a really good idea on how to make working with them super easy, so I’ll definitely implement that. 2. Make a demo that includes basically everything I have developed so far. This step also includes writing compatibility for multiple platforms, because so far I’ve only tested this project on Ubuntu, and I’ll definitely need a windows version too. And that’s it, the project is nearing it’s completion. I can’t wait to make a proper game in this, which I’ll document too of course. Also, I’m writing all of this while I’m on a vacation in Denmark, so I’m quite far from home, but I’m extremely motivated to get this project finished.
FINAL POLISHING (mostly just making the API more useable) I genuinely can’t believe this. I did it, I made a renderer in Vulkan. Well, I’m still not in the clear yet, but I’m very close. Changelog: updated Skin struct to include find_joint() function added Pose struct updated existing skinned mesh systems to work with the Pose struct added sample_clip(), compute_global_pose(), compute_skinning_matrices(), pose_set_local(), pose_set_global(), pose_get_global(), update_animation() and finalize_pose(). These are all helpers for making work with animated and rigged meshes a whole lot easier moved all of the functions inside the gvk namespace, because they were about half and half before.
THE GREAT DEBUGGING and A DEMO This is the final post as long as everything works as intended. I fixed every single validation layer error that occurred, so this project is officially bug-free (to my knowledge) . I also made a really simple demo that I uploaded to the github releases tab of the repository. I also finalized the readme, so now the project also has a fancy description. What a ride, I love this project.
I worked for over 4 hours, but the renderer finally works as a library!!! That’s right, just put it in as a git submodule, add it as a subdirectory to you CMake project, and there it is! Way too much work for something so simple, but I had to debug a whole lot of stuff. I also edited the README to feature usage instructions and also how to try the demo.
I also rewrote the single mesh gltf loading function to return the material and the entire vertex and index arrays, because I’ll need them for a feature I’m making for my game. This is slowly turning into engine development and I kinda like it.
I rewrote the texture loading part of GLTF loading because it just plain didn’t work since I updated everything to tinygltf3, but I didn’t notice because it was after the demo. Anyways, it’s done now.
I completely rewrote the text rendering in the Surface class and also fixed a giant memory leak. Here’s a picture of it in use in my game.
I wasted so much time trying to optimize it all to lead to nothing, but I did end up optimizing some stuff with the Surfaces along with actual color blending for them. I did end up optimizing a bit, but that’s basically nothing. This is the most worthless update to this entire project, I don’t even know how I spent 3 hours on this.
PIXELATION POST-PROCESSING EFFECT I made my post-processing system so expandable that I just casually added it ‘cause I thought it would look cool in the game I’m making. I’m sure as hell right about that.
I also fixed a bug where the bloom’s blur looped around to the other side of the screen.
Screen-space reflections would be a major challenge, but I learned that making the skybox reflect off of shiny surfaces is a pretty good way of making your renders seem much better. As of right now it technically works, but I came to realize that I wasn’t loading materials right, so if an image didn’t have a roughness texture, then it would end up completely shiny. I’m almost done fixing it.
SKYBOX REFLECTIONS This is a neat trick I did in my last engine and I’m happy I recreated it in this one. Every single shiny surface reflects the skybox, which while isn’t realistic, it does make those surfaces look a lot more shiny than what they actually are, which is a great hack. It sure as hell looks better in the environments I make.
FOG Yeah I just added fog because I thought it would look cool in my game. Obviously it’s over the top in this image because I wanted to show it off. I’m pretty proud of this one, definitely one of the more complex post-processing effects.
I made a few minor tweaks to fog, and I started debugging it, which I ended up completely stopping and reverting, as it was useless. To be honest I’m really trying to find where this 27 minutes appeared, because I don’t remember spending half an hour on this
Ships:
GVK (short for gungu vulkan) is my new renderer using the Vulkan graphics api. I miss the times when I worked with pygame and love2d, where rendering an image was one simple line of code, and I intend to bring that experience to 3D with this project. Everything is designed to be as easy as possible for the user, just writes gvk::draw_mesh(mesh, material, position, scale, rotation); and the mesh is where you want it, how you want it. But don’t mistake it, this is not a game engine, this is just a renderer, something you can build an engine on. I made this tool so my later projects have a graphical backbone to build on, but every game needs a different engine. Ever since I started graphics programming last year I knew that one day I had to learn Vulkan if I ever wanted a job in it in the future, and I’m very proud of myself for finally doing it. This was the greatest challenge I faced in programming so far, and I so glad I tackled it. And as for AI: This project was created so I could learn Vulkan, and if I copied everything from AI that would have been to effective. My goal was to use as little AI as I could, while still leaving out the headaches of development. Here’s what I used AI for: Many of the shaders were written by AI, but that’s because I haven’t learned any of the maths I need to know to write any of these shader. When a shader wasn’t too math heavy I wrote it myself, but there weren’t too many of those. The GLTF loading isn’t hard, it’s just a headache to work with it. GLTF is a horrible format for developers, but it’s the best I knew, so I rather had AI write the GLTF loading functions. I’m still glad I did that, I would have learned nothing from writing those.
This is the second ship of my game engine / renderer. To explain, I like calling it a renderer instead of a game engine, because I purposefully tried to add as few bloat features as I could, I just wanted something that made rendering something that I don’t have to think about. More info on the README page, this isn’t a good explanation. Just know that it isn’t just a basic Vulkan abstraction, this is a full renderer with PBR, many post-processing effects, skyboxes, dynamic lights, rigged and animated meshes and frustrum culling ’cause why not. Changelog since the last ship: 2 new post-processing effects, pixelation and fog I also added a feature where shiny objects reflect the skybox, which isn’t realistic, but it adds a great sense of realism and I like to add it to my games. one of the largest changes was turning this into an actual library instead of an executable, which allows easier integration. I also made a full game as a tech demo for this project, it can be found at this link or under my stardance profile. It turned out really well, and I’m proud of myself, because I finally made a game engine that I can actually make games in easily. (The screenshot is from the game) and the smallest but still a significant change was rewriting the entire 2D rendering part of the engine for 2 reasons: it had a memory leak, and text rendering was buggy, slow and barely worked....
|
1793307550.9845407
|
1793393950.9845407
|