Rain effects, better lighting model, optimized code
Re-worked the FUEL shaders to include rain effects, better lighting model, enhanced shadows, better AO, optimized code by pushing pixel calc's to vertex shaders, etc.
v3.2 changes
v3.1 changes
v3.1 should be the final version for a while. I'm going to write articles on various things I learned while working on the shaders in case folks are interested in learning. But, other then that, I need to focus on other things right now.
Before I get messages saying "xyz is borked", here's known issues from the original game that I tested to make sure were already there, and not caused by the new shaders, and that (even though I tried) I can't seem to fix:
NOTE: if you're using FUEL: REFUELED with the option to skip shader recompile, you'll need to switch shader recompile back on to get the new shaders to compile. You'll need to keep recompile on as you mess with settings in the "__setup.h" file. When you finally have everything the way you like it, you can turn off the recompile option again.
Someone commented in v1.0 shaders that they adjusted something and shaders wouldn't compile. If you think you might have borked up the __setup.h file, then just unzip it again from the zip file. As long as you haven't changed any of the other shader files, then you can just overwrite it and be good to go. Open it and make changes. Don't uncomment things any more. Just flip the 0's and 1's like it says in the file.
If you didn't make a backup, but are using Steam, you can go into Steam and have it check the integrity of your game files. It will update the shaders to the originals. But, it will also update anything else altered. So, if you have FUEL: REFUELED installed or have messed with any other files, it'll overwrite all of that. Only restore using Steam file integrity check as a last resort.
I've touched pretty much every part of the code.. that's the only way to find out what works, what doesn't and what can get leveraged to do more.
Things get wet in rain.. pretty self-explanatory, but when you start driving into a rain storm you'll really appreciate it. Found this little snippet in the ground shader file disabled. Spent hours messing with it just to find out it was a simple cubemap reflection that used a fresnel to determine how much reflection to overlay. Too much and everything goes silver. Too little, and you don't notice anything. Tie it to a rain variable (which code has), and you can ramp up the rain sheen when it rains more. The trick was figuring out it needed to apply to the lights and not the base colors. The haze around the goal posts kept looking like square glass panes until I figured that out.
Green highlights roads as dust gets thick (basically a cheat for races like Dark Dust where it can be near impossible to see). I did this in an hour on a whim after messing with Dark Dust one night. Turned out better then I expected.
Tweaked the lighting model so shadows use normal maps better, ambient occlusion only occurs in shadows, a base lighting amount is set instead of funkily calculated (asobo's original lighting code was a hot mess). Chrome & Clear-coat paints reflect properly in lighting now, and in some cases the underlying texture is finally showing through. (EG: on the Valkyrie motorycle, the muffler isn't actually chrome metal. It's black carbon fiber. But, the original chrome'ing code added too much chrome reflection to the muffler turning its clear-coat into a silver chrome.)
Even though direct light is blocked in shadows, in real life there's light bouncing all around and into the shadows (hence shadows are not pitch-black). This light can reflect off different objects to create shinies and variances. Simulated this in shadows by letting a little specular shine into shadows as well as applying a very minor amount of environmental reflection to shadows in order to add volume / depth to shadows.
I originally thought asobo coded a reflection function by hand, so replaced it with default HLSL function. What I realized after re-doing shaders again was that they had hand-coded a "sunny trail" reflection.. which makes the nice, long specular that leads towards the sun and highlights everything in it's path (water shinies, asphalt road highlights, etc). I added it to more of ground textures (non-asphalt, like dirt and grass) and to grass/plants.. so as you ride along grassy hills, you'll see the "sunny trail" follow you, which adds to the lighting detail. (this was especially tricky in getting the grass ground texture and grass plants to match up in shine so they'd blend together like they should, b/c each used a different lighting model).
Asobo tried to do a LOT with hardware of the time.. they have objects in the shaders that are pushing the limits of what DX9 could do back then. To save VRAM in gfx cards, they would pack multiple textures into one, then call certain values from the texture and calculate the others. For normal / bump maps, this is a Partial Derivitive method.. where you store the x & y value, then use them to calculate the z value instead of storing the z value. Asobo was using a funky euclidean distance formula to partial derivitive calculate the z (1 - sqrt(x*x - y*y)) ... in many cases this could get replaced with an alternate distance function no-problem.. so, I put a flag in that lets you swap out a manhattan distance (abs(x-y)) for some things (like vehicle normal / bump maps) with little to no visual impact. In other cases I baked it in, b/c it was so inconsequential and couldn't tell the difference (eg: dirt / water flung on screen).
AO under vehicles now properly affects plants, tire tracks & water as you drive over them. This is the kind of thing you only notice when sitting still, but it's still a nice detail. (Tiny details add up).
Water uses a more robust lighting model (taking lighting direction into account now) and, like other things, lets a bit of specular shine through in the shadows. Plus, shadows are softer in water, b/c light in water scatters all around which means more ambient light invades shadowed water areas and lightens them up. Ocean / lake water turns greener during storms due to algae bloom and foamy aeration of water. Water is affected by headlights so you can see it at night (looks like clear pool water, but better then the empty black masses that it was).
The day-night transition is still stark. But, you can tweak a value to make nights darker. Certain things will remain bright, like lightning, flares, tree fires, moon.
Optimized frame post-processing formulas. Added optional FXAA-style anti-aliasing. Tweaked motion blur so it impacts the bottom of the screen and not the top so much, so you can actually see where you're going. Tweaked Dynamic Eye Adaptation / Luminance / Brightness so it's halfway bearable when flying in and out of shadows. Bloom has been tweaked to enhance Dynamic Luminance in shadows without making cars glow like nuclear waste for no reason when just sitting out on a noon day. But, if you don't like all of that, there's flags in the __setup.h file to disable it as you please without having to dig through the shaders to edit those parts out.
I tried to optimize code where I could, either trimming the fat or replacing chunks with built-in HLSL functions. I also tried to move as much code as I could from pixel shaders to vertex shaders for pre-calculation. Doing a calculation in a vertex one time which impacts 20 pixels means you potentially saved 19 calculations. (20 pixel calcs replaced by 1 vertex calcs). I say "potentially", because the vertex will calculate, then the graphics engine might cull the vertex if it's blocked by other objects. But, it's still cheaper to pre-calc as much stuff in vertex shaders as possible. The tricky part is DX9 limits how much stuff you can load into a struct/object to pass from vertex to pixel shaders. And, some of the objects were already pretty loaded up with things from Asobo. So, there was a balancing act between a) could I offload the calc to the vetex shader (some texture manipulation calcs simply have to be done in the pixel shaders), b) was there enough room to squeeze the pre-calc'ed value into the struct transporting things from vertex to pixel shader? Different objects often had different things going on, so this was a balancing act on a several levels. Also, if we gained any performance, it might have been eaten up by the new features I added (like enhanced shadows). so... you may or may not see FPS improvement. (Depends on features turned on/off). FPS needs to cap out at 60, or else AI vehicles speed too much. But, the goal was to try to get FPS to max at 60 as much and as smooth as possible to avoid frame drops. I was testing on a GTX760, and got pretty consistent 60 FPS unless tons of AI vehicles and effects around. But, even then the dip wasn't too much or for too long.
This is just a side note.. I used FUEL's built-in shader compiler to compile the shaders, because the game engine had all the values to pipe into the shaders. This thing would blow up at syntax errors, and only say "stuff blew up, dude" without producing a real error message pointing out which shader and what line of code was exactly causing the problem. This meant I had to get really good at bug-checking my code.. any missing semi-colon at the end of a line, or declaring a variable twice or any other tiny thing could cause things to blow up. If I couldn't figure it out, I'd have to roll back to a prior version. So, I got good at debugging my code and making regular backups, noted what changed in case things screwed up and I could figure out what I might have done to cause it. The shader code base is a hodge-podge of mega-shaders impacting lots of things (eg: the shader that impacts vehicles also impacts buildings and such. So, if you want to mess with vehicles you could accidentally screw up other things). There was uncommented code doing things that I had to test to see what it impacted. In some cases the game engine seems to make multiple passes in the shaders to do things, so you had to figure out the chain of events. In other cases, seemingly useless shaders were doing very minor things I'd only catch after breaking. EG: Dark AO under player vehicle is handled by the vehicle mega-shader. But, the AO under AI vehicles and roaming trucks.. it's handled by a completely different shader file that also mixes in functions to deal with menu & radar screen shaders. (wut? Exactly.) The code mixed vertex-only and pixel-only functions into mega reference files that took a while to untangle (which I did well in v2.0, but not quite as well enough in v3.0). So, untangling what goes where was a nightmare. Accidentally offload some code and not follow the chain of precedence well enough, and you'd be chasing your tail for 30 minutes with shader compile blow up.
I learned a lot about C-style programming (using tokenization #defines and such), bug fixing, QA, etc. So, it was an interesting challenge. The code will never be perfect, and will never do everything I want it to, because that would require having access to the game engine code to rework things and make that better, too (pre-calcing more things before passing to shaders, figuring out why one thing is used but other code is not, figuring out why some code doesn't seem to do anything but shader compiling blows up without it, etc). But, it was an interesting coding challenge that honed my coding chops. Overall, I'm writing this part so you can get an idea of the mess I dealt with behind the scenes.
What took you so long?!
v1.0 ... (which was released on here) had borked up normal maps (tire treads were flat and smudgy, b/c normal maps weren't working right.) I only noticed this much later after it was released and I decided to play FUEL again.
v2.0 .. (never released to public) I had to start from scratch again, b/c couldn't figure out how to un-bork the v1.0 shaders to make normals work properly. I made a lot of changes and refactored / organized a lot of code. But, I had too heavy of a hand, and borked up things to where objects were flickering in rain between wet and unwet textures, AO shadow under trucks was missing, etc. I couldn't figure out how to un-bork it, so I got discouraged and gave up for a long while as I was finishing a masters program in college and didn't need the extra stress.
v3.0 ... (released 2020 Mar 05) ... I once again started from scratch, but used v2.0 as a prototype to quickly shove the new features in. I figured out what broke as I broke it again, but kept an eye on things this time. FUEL's shader code is just a very temperamental hot mess riding a fine line between genius and insanity. (EG: commented out a part of code in a struct/object that wasn't used any where in the shaders.. turns out that must be used some place in the game engine, b/c it's the reason why certain vehicles stopped doing rain on passenger-side wheels while doing rain on driver-side wheels... and other weird stuff. Completely bonkers.)
Anyways, I graduated college, and have been looking for a job. Decided to start the shaders once again, because it's one of my Moby Dick "great white whale" projects that keeps nagging at me to complete. So, I've been working on it as a side-project to keep me busy, and finally try to put it to rest once and for all.
I did what I could, but there was only so much I could do as I reached my level of incompetence when it came to physics, advanced graphics techniques (eg: horizon blue'ing / haze ... it's a crazy formula I still don't understand well), as well as being limited with what the game engine pipes in and how I could use it.
EG: chrome / clear-coats on vehicles use an environmental reflection to look mirrory.. but, they don't take headlight lighting into account. So, they look awful at night. So, I forced them to turn off at night, but that involved first finding a way to determine night vs. day (harder then it sounds). Once I did, I used the "night time" flag to disable the chrome / clear-coats at night. BUT.. this also disabled them in the menu screen (vehicle select screen). Why? Because apparently the game sets the "night flag" to night time when the menu is up. Why? I have no clue. But, I worked around this by taking the headlights lighting value and using it as a flag instead.. when it's daytime, headlights are off and have zero light. During night, they're on and have greater then zero light. So, I could use that to create a flag to determine day/night, and then turn chrome / clear-coats off at night while still keeping the on in the vehicle select screen.
*whew*
This is the kind of weird stuff I've had to deal with in these oddball shaders. Asobo was in the middle of building out the shaders and upgrading them to more modern features when Codemasters forced them to ship the game. So, it's like walking into a house that's half done.. some places are kind of nice, others barebones, and there's materials laying around you might be able to do something with.. maybe having to get creative. (EG: having to use the headlights lighting from the water refraction to fake some headlights lighting since there wasn't real headlights lighting baked into the water shaders). But, you're just a handyman. You're not an architect or engineer. And, you dont' have access to some parts of the house (the game engine and settings files) that greatly impact the rest of what you can do.
There was a lot of trial and error, tweaking, etc. I worked the code in Notepad++, and had to use the compiler built into FUEL.. which only gave an error message saying compilation blew up.. but didn't say WHAT blew up. So, I got good at backing up my work, making minor changes, testing, tweaking, bug-checking the code (did I declare a var twice? am I missing a semi-colon?)
v1.0 & v2.0 had most of the time sunk into it to figure most of this stuff out. So, v3.0 was cleaning things up and implementing features I already spent the time on and sorted out before.
I've done what I can with the shaders. I've hit my level of incompetence with some things, and simply need access to the game engine to do others (eg: adding headlights lighting to tire tracks.. need to have a game engine call setup to do that.) It was a fun side project to get into C-style languages before I hit Java in college, but it's time to put this to bed.
Why was the code so messy?
Writing this part, so you get an idea of what I (and Asobo, perhaps) were dealing with.
Asobo (game developer) was making FUEL for CodeMasters (publisher) to publish. It started as an open world terrain experiment (I think), and then before they knew it they added vehicles and some races. Asobo wanted to do more with the game, but CodeMasters pushed them to rush it out the door in 2009.
The shader code is reflective of this. When you start to stare at code, you just automatically start to do forensics analysis of it to see what story it tells while you're working on it.
The FUEL shader code tells a story of being rushed out the door halfway while being worked on. It's a tangled mess of really brilliant algorithms (eg: like the one doing horizon haze / scattering) and hodge-podge hot fixes and copy-pasta. You look at one part and go "wow, this is amazing, and I'm hitting my level of incompetence trying to wrap my head around their genius". Then hit other parts and go "what were they thinking?! Why declare a variable and do all those calculations just to not use it for the final result?"
Some of the code is written, formatted and refactored really well.. then other parts are just a garbled mess with no spacing or tabbing to help make it readable. So, one of the first things is to refactor the code for readability.
The code is drastically lacking comments to determine what's going on in many cases, and many comments are in French. A quick google translate can help with that, but, it's clear multiple people from an international or multi-lingual team were working on the shaders. (This assessment is underlined by comments in the code made by one shader dev telling another to "fix this" or "don't touch this".)
The shadows in FUEL use cheap bilinear Percent Closer Filtering (PCF) and more complex cubic / trilinear shadows. But, there's code in there for Exponential & Variance shadow techniques, more robust shadowing. I tried making them work, but the values didn't seem to get piped in from the game engine. So, it was clear they were in the middle of updating the shaders to be more robust when they were told to just wrap it up and ship the product. This is made more apparent by placeholder functions to do anti-aliasing in the post processing shader, but the functions either don't work, or do very simple things and not the complex action they're supposed to do. (Being able to delete them out without impact means they werne't implemented. Hard-wiring their code directly into the main post-processing function doesn't get the results anticipated, which means the game engine is doing the necessary prep work to make them work.) So, you can see the hopes and dreams in the shader code that Asobo was striving towards before being forced to move on.
Some of the shader looks like they were initially heavily invested in vertex lighting before moving on to nicer, more robust pixel shader lighting. There's massive code for vertex lighting.. and come to find out it only really affects the game's reset screen after crashing... and many of the options in the vertex lighting function are branched by what they impact in such a way that most of the code is skipped over.
In that same vein, a lot of the shader code uses dynamic C-style tokenization to determine what to compile and use when compiling the shaders. All of this is coming from the game engine and/or setup files that are encrypted in such a way that I can't figure out how it's piped in or what values are used. EG: you can't simply force shadows on for everything, including stuff that doesn't do them. There's a lot of moving parts, and "mega shaders" handling many objects will make one look good while the other will look awful. EG: the pixel shader that handles the blowing leaves in the wind.. that cuold use some shadowing so when you go into a mountain shadow the blowing leaves also shadow. The vertex code for the leaves has shadowing in it, but the game engine disables the shadowing flag when compiling them. You can force shadows on.. but the pixel shader that handles the blowing leaves.. also handles clouds in the sky, and they don't need shadows at all. So, you would be adding in a lot of unecessary shadow processing to clouds in the sky just to add a tiny shadowing detail to blowing leaves. Just oddball stuff like that, and lots of dynamic compilation flags coming from the game engine that make it hard to tell what the end result is going to be unless you can do something to test it.. like setting a textures Red, Green or Blue value to x10 to make it highlight in the game to see what's impacting and what path it's taking to do so.
Headlights are another thing. Deferred lighting was starting to become a thing when FUEL was coming out. So, they used deferred lighting for headlights. They made it work on ground, plants and vehicles/ buildings. But .. not water, or tire tracks, or dust kicked up from vehicles.. or reflective cubemaps used for chrome and clear-coats on vehicles. So, it looks like they were in teh middle of implementing that when they were told to ship the game. The deferred headlights code can't simply get implemented in other things, because the game engine itself has to call the functions to make them work. Without access to the game engine code, I can't make them work for other things.
I think Asobo used FUEL as a base for other games. They shipped FUEL, then continued to polish the shaders and could use them to make other games. They later did The Crew and The Crew 2, and there could be former FUEL shader code in those games, just fleshed out and finished.
It's interesting to think what FUEL could have been had Asobo had more time. They were pushing the limits of DX9 at the time, hitting the variable limits of stucts/objects, so they had to find creative ways to pack more variables into unused space, try to offload matrix Tangent, Binormal, Normal lighting calculations to vertex shaders to get better performance, do vertex vs. pixel lighting depending on performance, pre-calc'ing things in the game engine on the CPU before shoving them to the GPU for work (because GPU's are awful at logic branching, so they die pretty fast with if/then/else statements).
By working on this project I have a whole new found respect for video game and graphics development. I had to tweak and test over and over. I had to dig into white papers on lighting and vertex / vector models. I had to learn about normal / bump maps and how they work. I had to learn how reflective shine can be used in varying amounts to do things from rain sheen to clear coats on paint and muffler chrome. Lots and lots of moving parts and high level stuff going on, often hitting my level of incompetence (eg: advanced physics calculations). It takes a LONG time to develop this stuff. Researchers come up with new ways to do things, then game developers have to find ways to bake them into their games.
I also realized why games in the DX9 era were so hit or miss on performance. It's because DX99 was very limited in what it could do compared to the advanced graphics folks were trying to do with it. It had very simply functions like reflection, but lacked robust things like anti-aliasing. So, each company had to code up massively complex anti-aliasing and other things themselves. If they weren't experts on shaders, then the performance could be bad. So, one game with DX9 may run and look really great, but another game runs poorly. Microsoft was resting on their laurels back then, but finally got a fire lit under their rear to make DX more robust with DX10, DX11, and finally optimize it in DX12.. making more robust functions that do more standard things, and pushing those calculations closer to the metal instead of game devs having to hand-code it in the high-level where it ran less optimally.
It takes a lot of time and work to do all of this. I'm sitting here tweaking some shader code, but I'm standing on the shoulders of giants making subtle changes.. and even then the work I did took time and effort.
It's very easy for someone to look at something they didn't work on and just readily poo all over it if they don't like it, because they don't have a clue about the time or complexity that went into it.
So, I have a new found respect for video games. There's a reason they take a lot of hours and intelligence to make.
Anyways, TMI .. just wanted to give a side panel on things I learned while working on the shaders in case folks were interested in back-story.
Average
-0 votes submitted.
This comment is currently awaiting admin approval, join now to view.