Jump to content
The Dark Mod Forums

Messin with shaders


Recommended Posts

v 0.69 .. (nice)

  • added cap to ambient rimlight, so brighter/lighter surfaces (eg white sheet-covered furniture, status/busts, etc) don't stick out like a sore thumb in the dark
  • added direct light boost option to compensate if ambient light darkening is on
  • everything has fake spec enhancing light dot to act as base layer of specular; real spec maps get layered on top as extra spec enhancement for things that have it.
  • in "interactions.funcs.glsl", added #define AMBIENT_HALF_LAMBERT to swap ambient light dot between doing valve-style half-lambert vs. full NdotL

Rimlighting part is self-explanatory. Was messing with Blackgrove Manor with ambient light reduction, and light/white textures had gaudy rimlighting. So, had to set a limit cap.

Direct Light Boost

Direct light boost option compensates when ambient light darkening is on.. Basically, it does about 1/3 of the opposite of the ambient reduction.

EG: if you reduce ambient light to 25%, then direct light will boost

  • 1.0 + (1.0 - 0.25) * 0.33
  • 1.0 + 0.75  * 0.33
  • 1.0 + 0.2475
  • ~1.25 (125% boost)

This is because ambient light doesn't contribute to total light as much as direct light does. So, just b/c we reduce ambient light to 25% doesn't mean we should boost direct light up 75% to compensate.

Spec Maps

Got tired of "if real spec map use it, else use fake" ... decided to experiment and just put fake spec map light dot enhancement on everything, then layer real speculars on top for things that have it. Turned out pretty nice after tweaking some values. Gets rid of some branching, too. I'll mess with some levels with it, and clean up the code more if I decide to stick with it, b/c that part of the code is a hot mess of experiments right now.

Half-Lambert vs Full-Lambert Light Dot

When objects multiply by the NdotL light dot for diffuse (color) lighting, if they have a surface angle 90 degrees or more to the light source, then they get an NdotL of 0. This would make the sides of boxes pitch black, ceilings pitch black, etc.. b/c ambient light source direction is coming down from directly overhead.

The valve-style half-lambert acts like an all-in-one solution to this, because it takes the 0 to 1 NdotL range, and shifts it to 0.7 to 1.0 (if you use sqrt(lightdot*0.5+05)). So, it prevents pitch-black surfaces by automatically creating a constant minimal light value.. but does so on the light dot. Since it tightens the dot range, it also evens out the lighting more.

But, the NdotL provides a greater range, and thus, greater contrast in lighting. In using it, we have to apply a final ambient light constant value to keep things from being pitch black. So, I just use the diffuse lighting a second time... in following fashion...

  • diffuse light = diffuse light * diffuse texture * diffuse light dot (NdotL)
  • ambient light = diffuse light * diffuse texture * "ambient light dot"
  • total ambient light = diffuse light + ambient light

That faux "ambient light dot" is just a constant value to determine how much ambient light to add on to keep things from being pitch-black. Even though this method isn't using valve half-lambert, it still calculates the ratios for that, because it uses them to determine that "ambient light dot". I did this, because TDM has a variable to set a minimum level, so I check if that exists and use it.. if it doesn't exist, then I use a different default value.. 0.5 in this case, b/c it works well for both half-lambert and the NdotL methods. 0.25 as default looks more like default TDM shaders, but the added speculars in my shaders make the light gem boost a bit more. So, shifting the default to 0.5 darkens the level a bit but also makes the light gem work well again.

Anyways.. there's a bunch of TMI

Hopefully this doesn't muck up Bloom again.




Link to comment
Share on other sites

Here's a gif of the NdotL vs. Half-Lambert, so you get an idea of the difference. The NdotL seems stark around the bottom of boxes, but when you have SSAO on, it blends them into the ground. NdotL is more true to math / scene, but half-lambert is a crutch technique to help with blending. Again, this is only done on ambient lighting shader. Direct/Common lighting shader does NdotL to provide a stark contrast between light and "not light" (IE: opposite sides of light where AO from lightdot being 0 sets in)


Link to comment
Share on other sites

Is there a way to tell the light sources apart?

I was looking over the default TDM shaders again (now that I have a better understanding of what's doing what), and there's a variable called "R2f" in interaction.common.fs that shapes light to make it seem like lamps cast more light downwards than upwards (b/c their glass / metal hoods block some light). But, it looks odd on torches, candles, sconces, etc that would shoot more light upwards and less down due to their implement blocking light downwards.

I can use modify the R2f function to make it shoot upwards or downwards.. that was easy enough. But, if I can't build logic into the shader to differentiate light sources, I don't know which should get upwards & which should get downwards....

Here's some picks as example from Coercion...

A lamp looks better with light shaped downwards...


... not upwards ...



But, a candle looks better with light shaped upwards...


... not downwards ...



However, without being able to tell the light sources apart, the shader can't tell how to shape the light to benefit.

Original shaders used R2f as-is, and it blended well enough I guess to not be noticeable. But, I'd really like to leverage it for nicer lighting.

Do light objects have some flag that could get piped through as a uniform to the shaders?


I should also mention... fireplaces look better without any R2f shaping.

So, an ideal variable would be able to tell me if light should get shaped up, down, or not at all.

Is there something like that?

Edited by totallytubular
extra info
Link to comment
Share on other sites

11 hours ago, totallytubular said:

I was looking over the default TDM shaders again (now that I have a better understanding of what's doing what), and there's a variable called "R2f" in interaction.common.fs that shapes light to make it seem like lamps cast more light downwards than upwards (b/c their glass / metal hoods block some light).

Admittedly I'm only a newb when it comes to shaders, but that seems very odd to me. Why should the global interaction shader make assumptions about the directionality of in-game light sources, rather than having this controlled by the falloff textures which can vary from light to light?

If you can have a per-light interaction shader I suppose this would make sense, but if there is such a mechanism in game I certainly don't know how it would be configured in DarkRadiant (I suppose you could have a "shader" spawnarg on the light entity itself).

Link to comment
Share on other sites

1 hour ago, OrbWeaver said:

Admittedly I'm only a newb when it comes to shaders, but that seems very odd to me. Why should the global interaction shader make assumptions about the directionality of in-game light sources, rather than having this controlled by the falloff textures which can vary from light to light?

If you can have a per-light interaction shader I suppose this would make sense, but if there is such a mechanism in game I certainly don't know how it would be configured in DarkRadiant (I suppose you could have a "shader" spawnarg on the light entity itself).


Interaction shaders were designed with alternate fallback methods in case certain things weren't set.

EG: if a surface is missing specular texture, then it would apply a minor amount as fallback. But, it was multiplying all speculars by specular light color params. Surfaces w/o specular had their specular light color set to 0 rgb (pitch-black), which negated any fallback specular amount being applied.

The fallback methods basically act as crutches in case something isn't set for whatever reason... a likely scenario is because it would take more effort to do it "right" than it's worth. EG: going through all textures and making sure they all had a proper specular texture.. that's a lot of work, and would possibly add a lot of memory bloat to things.. especially for inconsequential stuff that will just have a very minor amount of specular applied. It's cheaper to just have a catch-all fallback method to do it in the shader.

The R2f in default direct light shader is applied regardless, so doesn't look like it was done as a fallback method.

But, it could be shifted to being one.

I think the problem would be level designers can position lights in all kinds of weird ways. I've seen hooded lamps parked on the ground and shooting upwards to highlight statues. The R2f shapes the light by just boosting the light direction up or down. If a lamp was flagged to boost downwards, but level designer parked it to shoot upwards, it would shape those weird. Who knows.

I'm gonna keep the R2f disabled in the shaders I'm farting with.


Link to comment
Share on other sites

v 0.71

  • fake spec diffuse light enhancement boosted a bit to be more noticeable
  • added the direct light rimlight boost back in

Fake Spec Boost

Fake Specs enhance diffuse lighting overall, creating subtle specular shines on things that don't normally have specs, and enhancing stuff that does. When I'm working on them, I'm hyper-focused on them, and notice very subtle amounts. But, regular players are going to be blowing through areas, and won't notice anything that is barely noticeable by me when I'm hyper-focused on them. So, I boosted the amount a bit. The idea isn't for it to be "in your face", but just to add detail that the player might actually notice every now and then as it enhances the scene.

Direct Light Rimlight Boost

Default TDM shaders have a wonderful effect where the rimlighting in direct light would brighten the more direct line-of-sight the player had with the light source, and the closer the player's eye level was to the surface the light source lit up.

When tearing the shaders down and testing each variable to figure out what was doing what, I omitted this, b/c the two parts of it, when tested individually, don't seem like they do much. (one part super-dulls the shine while another part super-brightens). But, going back and looking at default TDM shaders to see what I've missed or did differently, I noticed that when those two var's combine, they create the great rimlight shine boost.

So, I added it back in.. but did so by treating rimlight like a two-step light as well. Base rimlight is done, then it's used to create a specular rimlight that gets added into base rimlight.. and then, that all gets added into specular lighting if the surface has a real specular texture map, or diffuse lighting if it doesn't (and shaped by the fake spec map before doing so).

So, the nice, bright edge is back on the direct rimlighting...


If you're wondering why my rimlighting wraps around more...

  1. The normal map intensity has been boosted, making detail pop out more.
  2. I don't know 😕

I know the normal map adjustment will create more detail to pop out, which means things stick out further and thus get hit by light more. But, other than that, I'm not sure why it wraps more. But, it acts like light bouncing off one pillar onto the backside of the other to extend the rimlight range. I like the effect, so keeping it.


  • Like 3
Link to comment
Share on other sites

15 hours ago, totallytubular said:

I'm gonna keep the R2f disabled in the shaders I'm farting with.

That sounds like a good idea to me. If content creators want lights to shine more strongly downwards than upwards, they can set the Z falloff texture to accomplish this (by having brighter grey values in the bottom half of the texture). It certainly shouldn't be done in the shader for all lights, regardless of light texture or positioning.

  • Like 2
Link to comment
Share on other sites

Just noticed some surfaces have specular light color set (params[var_DrawId].specularColor.rgb), but don't have a specular texture.

In default TDM shaders:

ambient does a specular texture pull regardless

	vec3 matSpecular = textureSpecular( var_TexSpecular ).rgb;

.. but, common checks if specular light color is pitch-black first.. if it's not, then it does the specular texture pull

	if (dot(params[var_DrawId].specularColor, params[var_DrawId].specularColor) > 0.0)
		specular = textureSpecular(var_TexSpecular).rgb;


While messing with specular, I noticed this one dark-reddish-brown door/gate (left-side of image) never had specular, excepte for the metal fittings. Didn't matter what I did, in my shader I couldn't get specular to apply to it.


So, I piped specular light color through to see if it was flagged as a specular texture surface. Yup.. it has specular light color param set...


With the specular light color set, the specular texture should pull. So, I pipe specular textures through to see what they impact...


Only the metal fittings have specular textures.


specular = specular texture * specular light color * specular dot (brightness)

But, if the specular light color isn't set, or the specular texture doesn't exist, specular is 0.

So, this dark red-brown door/gate is never getting specular unless there's some double-checking going on to fallback to an alternate method (like applying a generic amount of spec, or, what I've been doing, grayscaling the diffuse texture as a faux spec map).

Not sure if surfaces having specular color light set, but not having a specular texture, is by design or a bug.

I was going to work some error-checking into my shaders to test if specular texture is pitch-black. But, that's a "meh" prospect, since specular texture maps could literally have pitch-black areas to create shine-free surface details.

Not sure how to approach this.


I stared at the default TDM ambient / common shaders more.

  • Ambient doesn't use specular light color at all. It just pulls specular texture, then muls it by specdot & specpow in a way to adjust specular lighting. So, specular light color never comes into that equation.
  • Common does use specular light color, and uses it to decide if a surface has a specular map. Since the dark-brown door/gate is covered by specular light color, it does a specular texture pull, but it doesn't have a specular texture (or it's defaulted to pitch black). So, it ends up with no specular.

In my ambient shaders, I added in specular light color, and was using it (like in common) to check if a spec map pull needed to be done. Figured it was good to skip an unnecessary texture call.

I think in my shaders, I'll just check if both specular light color & specular texture are pitch-black (empty), and fallback to faux spec maps.

Edited by totallytubular
extra info
Link to comment
Share on other sites

v 0.75

  • cubic / non-cubic branching added back to common/direct lighting (guess I removed it when sorting out diffuse vs specular light color.. realized it was broken when playing a level and the tiffany-style desk lamps made the room super bright)
  • called rimlight boost "rimshine" (rimlight is the soft fresnel while rimshine is the bright, crisp edgy part)
  • added rimshine to ambient lighting, but there it enhances all not just following specular trail up wall (had to make it enhance more than just the specular trail, b/c the specular trail going straight up becomes too over-powered with specular + rimlight + rimshine)
  • In ambient, ditched the sqrt() part in the lightdot*X+Y linear algorithm. Valve's half-lambert style was meant for light dots in -1 to 1 range, so sqrt(dot*0.5+0.5) was boosting it to 0.7 to 1 range.
  • AMBIENT_HALF_LAMBERT #define lets you decide if you want to do NdotL*X+Y half-lambert, or full-lambert with NdotL. When doing full-lambert, it will create a final, constant ambient light to add to total light at the end (basically diffuse texture * diffuse light color * constant value to create a constant light not impacted by light direction.. this keeps NdotL = 0 surfaces from being pitch-black).
  • Changed linear lightdot*X+Y formula to minBrightness & mulBrightness ... minBrightness is the additive (Y) part, and is used to see if u_minLevel exists (which it will use as an override). mulBrightness is the multiplier (X), which gets calcualted as (1-minBrightness). (it's basically reverse of how I was doing it with p/q, b/c I realized u_minLevel needed to be the additive, not the multiplier .. d'oh).
  • surfaces in specular light color area that don't have a specular texture (it comes back pitch-black) get the fake specular diffuse brightness boost now. (which my previous post was discussing). So, that dark reddish-brown gate door is speculared, but via diffuse dot enhancement.
  • added a test if specular texture does exist after it's pulled, and that flag is used going forward to do real spec parts. This may or may not help performance much, but it's purpose is to skip more specular math on parts that don't have real specular textures.
  • ripped out phong specular from ambient... it's blinn-phong again, but still uses double-speculars to cover both floors & ceiliings and such. (Phong specular is just too hit or miss.. it looks good in some places, then awful the next. Blinn-phong has more consistent look and quality).
  • added #define to switch ambient light back to original TDM specular style.



  • Like 1
Link to comment
Share on other sites

v 0.77

  • ambient light amount matches up to original TDM shader ambient light amount better now
  • ambient rimshine applies to all rimlighting as edge highlighting, and tweaked to hopefully not be too gaudy
  • direct lighting rimshine toned down a bit, b/c it was super-bright
  • fake spec math changed, b/c averaging fakespec + (1-fakespec) was just making 0.5 regardless (d'oh). Switched to using a percent frequency curve to reduce shine on both very bright/dark textures while boosting the mid-tones. This evens out the shine a bit while still giving color variation.
  • tweaked ambient spec floors/ceilings to actually dim them instead of whatever it was I thought they were doing when I wasn't thinking straight while high on vaccine
  • added #define AMBIENT_DARKEN_UNDER ... this slightly darkens light on the underside of objects / surfaces (opposite light direction) in ambient light to help them blend into shadow maps / ssao better

My ambient shader created darker shadows than TDM originals. I originally kept it, b/c switching to an ambient light that matched original TDM caused the light gem to brighten slightly. But, after I tested it for a bit on more levels, it was only in a few places and didn't really impact game play. So, I just switched ambient lighting over to look more like original TDM shaders. My goal is to enhance the shaders while still keeping towards the original artistic look & feel. Not force darker shadows on folks. (If you want darker shadows, there's a #define in the "interaction.funcs" file called AMBIENT_LIGHT_DARKER you can play with to do that in optional fashion.)

Ambient rim shine was multiplying by specular dot, which basically culled the rim shine too much and accentuated it too much in specular trails up walls. Decided to just use rim shine as a generic boost. It branches on whether things have a real vs fake spec map, so metallic objects and stuff will use their specular texture and specular light color to do the rim shine while other things will use the fake spec map to tone it down.

Rimlight in ambient shader didn't take light direction into account. So, we ended up with bright rimlight highlights on the underside of objects (like canvas packages) that looked odd. I created a reverse light direction hack to dim the rimlight on the underside of things. Then I figured to just apply it to all ambient lighting (diffuse, specular, rimlight) to dim the lighting on underside of objects & surfaces. By using the reverse light direction, we have better control over how much gets dimmed (finer control than a half-lambert). I reduced the light slighlly, because too dark and it has opposite effect of sticking out like sore thumb being too dark. So, I faded it slightly to help blend into shadows / shadow maps / ssao.


On the under-side of the canvas package, the ambient rimlight, et.al. create an unnatural shine. Shadow maps & SSAO don't get rid of it; there's still a slight unnatural seam of light. So, applying a hack with the reverse light direction, we can tone down the lighting on the underside of it (and other surfaces) to help it blend into shaows & ssao better.

This is just a brightness reducer, not an "anti-light" / black-light rgb light source (which I've seen used in some games to create fake shadows, and it always looks fugly.)


  • Like 1
Link to comment
Share on other sites

2 hours ago, ate0ate said:

What causes the light gem to brighten when the shadow maps/ssao are toggled on in the above gif?

That is probably a lantern, you would not see pronounced shadows in purely ambient lighting.

Please visit TDM's IndieDB site and help promote the mod:




(Yeah, shameless promotion... but traffic is traffic folks...)

Link to comment
Share on other sites

16 hours ago, ate0ate said:

What causes the light gem to brighten when the shadow maps/ssao are toggled on in the above gif?

Shadow Maps vs Stencil shadows

That particular spot on Training Mission is lit up when you have shadow maps turned on, but not when stencil shadows are on. Not sure why.


It's like there's an extra light source there that stencil shadows aren't taking into consideration. Not sure if it's supposed to act like the light coming from the window across the water, or ... ???

Level authors often use extra lights that don't seem to have a source to accentuate things or help make ambient light seem more dynamic. (EG: in Training Mission, along the brick corridor leading to the Builder warehouse for sneak training, there's a bright, white dual-line light towards the top of one building that acts like moonlight rays casting down).

Why stencil doesn't account for it, though.. not sure.

  • Like 1
Link to comment
Share on other sites

Thanks for the answer. Yeah, that doesn't seem like something that would be done intentionally. I wonder how many maps things like that happen on or if its a one off fluke. I always play with maps personally, but I never considered that the actual lighting (and therefore difficulty) could be affected by the decision. You always find some interesting things messing around with the shaders lol.

Link to comment
Share on other sites

v 0.79

  • minor tweak to direct fake spec to brighten it up a bit
  • made "darkerunder" only impact fresnel on ambient again, because it was darkening ceilings and such too much when it impacted diffuse & specular
  • specular BRDF added back in with a toggle (#define BRDF_SPECULAR 0/1) to let you switch it on/off

The "darker under" thing was culling light too much on ceilings when applied to all ambient light. It's because there's no way to tell what's an object (like a canvas package) vs a ceiling or such. So, I just have it culling rimlight again.

I started staring at PBR again. Trying to make sense of it is a learning curve, because sources talking about it either go into very high-level math / comp sci / academia / white paper discussions about it, or they discuss it piece-meal with code chunks without really showing how everything fits together.

But, it breaks down into a diffuse lighting model (eg: Lambertian) mashed up with a specular lighting mode (blinn/phong, schlick, ggx, etc).

I was staring at the learnOpenGL PBR page again, and followed the link to the Unreal team's paper talking about their work on PBR into UE4. They said they looked at Disney's stuff, and ignored certain things b/c the cost/benefit wasn't worth it. EG: they ignored using other diffuse lighting models (like Burley model) after doing some tests and realizing Lambertian was good enough.

The real benefit comes from the GGX, etc specular stuff.

So, I ditched the diffuse stuff in the "interactions.pbr" file, and trimmed down to just some basic specular functions for visibility (geometry), distribution & fresnel. (Mainly GGX, since that seems to be what most folks focus on).

I got stuff working, and have the fake spec map doing double-duty as a roughness map as well.

Ambient & Direct light do their own thing from there...

I forced Ambient light to use 1-fakespec as a roughness to prevent glossiness. For some reason the specular distribution term wants to barf white pixels everywhere when glossiness is high, and the glossiness on real spec mapped surfaces sticks out like a sore thumb. So, I forced it to do rough surfaces.

Direct lights... if it has a real spec map, I have it doing glossy. If it has fake spec map, it does rough. This allows shiny surfaces, like the pillars on Training Mission, to look like mirror-finish polished marble, reflecting the torch light more.

The results of all of this are ok. The specular seems to even out in ambient lighting better. Metal pipes and such have more body to them as they reflect more light. But, some surfaces look wet. Some surfaces look like they have a bunch of white pixels washing over the place like dust.

I think this is the micro-facet algorithm going full-steam with "every facet is reflective". To make it vary in color, it needs like a noise algorithm to go with it. I was reading some coding lab web page where folks were doing monte carlo sims to statistically vary their micro-facets (*cough*).. but they were doing rays and stuff.

I'm trying to get a noise function to work, b/c I'd like to use it to tone down the gloss on NPC faces and such. But, I can't seem to get GLSL's built-in noise() func's to work, nor ones I bolt in from other sources. I think I'm missing a random value generator to ensure the seed value changes? I'm not sure. I've hit a dead end with noise generation.

Anyways.. here's some screen shots...


The pillars have real spec maps, so the fake spec map acts as a gloss map instead of a rough map for them. The BRDF specular obstacle course creates reflections of the candle as if it's polished marble.


The metal stands out more with speculars, but in ambient light I use the fake spec map as (1-fakespec) for a roughness map. So, they don't have heavy reflection. But, if you turn on the lantern, you will see the lantern reflect light off the pipes more (like with the marble pillars). The spec brdf hugs the curves of the objects more, creating crisp lines.


Invasion of the white, speckly pixels. The spec distribution function loves to over-bright things. I had to hackishly tone down it's results in the glossy brdf branch. Even then... I toned down the specular on the ground, but the ground has a real spec map, so it's using the glossy brdf branch. It has a nice shine, but it's a bit over-done. And, the white speckly pixels start to look like static as you move sometimes.


The issue with the PBR is it feels like it needs to get tweaked per-level. On one level the specular shines are too much. On another, not enough. Etc. The idea of the BRDF is that it makes more decisions on things, like spec power, rather than having to fiddle with variables. But, still stuck fiddling with some variables.


  • Like 2
Link to comment
Share on other sites


The issue with the PBR is it feels like it needs to get tweaked per-level. On one level the specular shines are too much. On another, not enough. Etc. The idea of the BRDF is that it makes more decisions on things, like spec power, rather than having to fiddle with variables. But, still stuck fiddling with some variables.


I think the problem is not really with PBR itself but with how the game assets and lighting are currently done, imo trying to brute force PBR stuff into a render and assets optimized for non PBR, will never work totally as intended.

But I most say, it does look better when it works, so please continue your efforts. :)

  • Like 1
Link to comment
Share on other sites

1 hour ago, HMart said:


I think the problem is not really with PBR itself but with how the game assets and lighting are currently done, imo trying to brute force PBR stuff into a render and assets optimized for non PBR, will never work totally as intended.

But I most say, it does look better when it works, so please continue your efforts. :)


My thinking is level designers created their levels based on what they saw at the time. Slapping PBR-like stuff on top now, especially by hacking together a roughness map, isn't going to magically make everything look good.

I try to tune the stuff to a specific map (namely Training Mission), then go mess with a few other levels to see if anything looks odd. Inevitably, something will stand-out. So, I'll tone something down, but it gets to the point where things can get so toned down it's like "why bother wasting processing for such a minor result?"

But, the performance impact is a bit negligible. I'm still getting 20-30fps on a dinky little HD 4600 integrated graphics. And, my view on enhancement is it's a death of a thousand cuts; little things add up.

I''ll probably mess with it more later, but right now I need to re-double my efforts on my job hunt now that I'm vaccinated. 2 yrs w/o a job after graduation. My retirement account has been keeping me afloat, but student loans are coming up. And wife's been watching home reno shows, b/c she wants a house badly. I've been throwing myself into personal projects to keep busy, but job hunt has been depressing.

Link to comment
Share on other sites

The dreaded black square bloom thing is rearing it's ugly head once more. Though, to be fair, it is definitely not like it was before. Same squares but different conditions for appearance. They actually only appear in one specific spot that I have found thus far, in the Pagan cave area of the new Hidden Hands: The Anomaly FM. They don't seem to be appearing in the training mission nor in Tears of St. Lucia as they were before. Very strange little thing and certainly not a huge deal but I thought you should know nonetheless. I thank you for your work and wish you luck in the (shudder) job hunt.

Link to comment
Share on other sites

I appreciate the effort to try to make PBR work with current materials as that's probably the only way it could be integrated in the game, but have you tried testing it with proper PBR materials? Either remaking some that are in-game by hand or just getting some "standard PBR" textures from the internet. One of the big advantages of the PBR workflow is that there's a lot of good quality assets with compatible licensing on the internet, like https://cc0textures.com/

I'm honestly wondering how big of a difference it could make. I don't have an idea because while I believe the PBR workflow to be visually simply superior, I don't have enough understanding of engine developoment to know how well it can be implemented into an older engine that wasn't created with PBR in mind. At the same time I like your work already and I'm sure using actual PBR would only make it better.

Link to comment
Share on other sites

v 0.82 .. the "I think I finally got PBR working right" update (knock on wood)

  • ambient surfaces with real speculars do more glossy PBRs now
  • capped the distribution term in specular PBR flow to keep it from barfing white specklies everywhere with glossy outputs, and to help control how bright glossy shines are in lighting
  • got rid of the "divide by Pi" parts in PBR flow, because we're not dividing diffuse color by Pi (instead, dividing by " 4 * NdotV * NdotL" which seems to give better results
  • corrected visibility specular PBR term to use NdotL like it should (not sure how I missed this before)
  • renamed BRDF_SPECULAR to PBR_LIGHTING (PBR is what we're doing, BRDF is how we're doing it)
  • added #define PBR_REALISTIC which (when set to 1) will keep the multipliers usually used to tweak outputs (like speculars) to 1.0 so pure PBR outputs are shown without anything tweaking them after-the-fact (the idea of this is to maybe tweak the PBR outputs in the future to "enhance" the lighting to be more immersive / game-like, but still have a way to quickly switch to pure PBR. PBR is supposed to be a realistic lighting model, so mucking with it after-the-fact can alter the realism).
  • added #define PBR_FAKESPEC_IS_REALSPEC to flip between fake specs acting like real spec lights of diffuse boost in PBR (in PBR, they do better acting like real specular lights, because PBR does direct rimlighting and rimshining w/o having to resort to extra functions. But, when you turn the fake specs into diffuse boosts, a lot of what PBR does disappears w/o much diffuse boost to show for it..unless you muck with the outputs post-PBR.. which defeats the purpose of PBR.. to have algorithms do it all w/o having to mess with too many extra variables).
  • finally understood the whole "conservation of energy" thing, and added ks/kd ratios to balance diffuse vs specular. It's basically taking the specular PBR fresnel term and using it to determine how much light is reflected (specular) vs refracted (diffuse) based on your viewing angle, surface, etc.
  • fake spec maps are used in PBR as speculars, but for roughness maps I went with grayscale diffuse textures. This lets them have full 0 to 1 range instead of the curved range that fake specs do.

Basically, I read Epic Game's white paper on how they put PBR into UE4, and they made decisions to just keep using Lambertian Diffuse like they had been doing, and just focus on keeping it simple and where the algorithms they chose do all the work.. as opposed to shader devs or artists having to micro-manage tons of variables to tweak everything.

This radically changed my thinking about PBR, and I realized it can be as simple or as complex as folks want it to be. So, I ignored a various diffuse lighting models and just kept what we had, but modified with energy conservation (kd).

I realized Torrence-Cook is the main PBR model most folks use, but they swap out various sub-algorithms in it. Once I wrapped my head around that, it made more sense. I switched to using the most common algorithms tuts used, and just kept it simple to get it working. I then figured out the whole "conservation of energy" thing, b/c the ks/kd thing was confusing me for a while. (LearnOpenGL's tutorial was confusing, b/c they kept swapping back-n-forth between code and math.. and I do better when I see just a cohesive chunk of code showing how things are done).

Anyways... I got it going.

The goal (in my mind was)

  • Get the damn thing working properly
  • If it's working right, we shouldn't have to use any post-PBR multipliers to tweak speculars and brightness and other junk. The PBR algorithms should handle all of that, so we don't waste time mucking around with that. (I kept the multipliers in the code, but they're all set to 1.0 for PBR paths.. so we see a pure PBR output experience.)

I had to make a few hackish decisions to get things to look decent, like clamping the distribution term (it was barfing white specks all over the place). Also, not dividing by Pi...

The Pi division was confusing the heck out of me...then I came across some forum post (can't remember where) that the person said in the Lambertian Lighting Model, you divide diffuse color by pi. But, it's just assumed that light colors in the game engine piped out to shaders are already divided by pi, so nobody divides diffuse color by pi in shaders.

Um.. okay? *head-scratching*

But, the PBR code I was looking at (eg: Urdo3D, LearnOpenGL) were dividing the specular PBR output by Pi (learn open gl had one of the spec sub-terms also divide by Pi). I realized they were doing this, b/c it would balance out diffuse color dividing by Pi.

But, we don't divide TDM diffuse by Pi. It's a bad assumption to make w/o knowing for sure. All I did was look at the results from the shaders.. if I divided diffuse color by Pi, it got really dark. So, I removed the divide by pi from the specular PBR output, too.

Instead, I switched it over to a " / 4 * NdotV * NdotL" that I noticed other folks doing. This worked well to tone the final spec output without having to do Pi.

(this is the big PITA about all of this.. it's like herding cats. Everyone's doing their own version of it that works for them, but they don't explain why they do certain things. No code comments or what-not in the code, and white-paper-like-discussions that hit my level of incompetence pretty fast. So, trying to mix-n-match code or using one project's code into ours can lead to funky results w/o knowing why.)




Chunky texture seams

some surfaces have an ugly, chunky texture situation going on, and not sure why. When I switch PBR off, it resolves itself. So, it's something with the PBR pipeline.


Something is acting like a cap or limiter or something to create the stark seams between parts. I tested various terms in the PBR, and these chunkies still occurred. Maybe it's my crappy HD 4600 doing something funky? I don't know. Only certain textures do it. If anyone can verify whether they're also having the issue, I'll be able to know if it's a code issue or gfx driver issue.

Dark Spot under Player

Ground specular was looking nice... before I got kD (diffuse refractive ratio) built into diffuse. Now there's a dark spot under the player.


I know it's the kD value, b/c when I remove that from diffuse lighting equation, the dark spot goes away and ground speculars look better. Not sure what's up with this. I'll have to experiment, or try other formulas or something. I don't know.

Speculars get Bright up near walls in Ambient


PBR helps ambient specs look better, but they exacerbate the brightness of speculars as you get closer to walls more-so than non-PBR spec. Ambient specular was always a balancing act trying to make it look good both indoors and outdoors, b/c it's controlled mainly by the light color the level designer uses for the room/area. So, if they want brighter specs outside, they use a brighter light color. Darker inside, use a darker color.

For PBR, I use the distribution clamp value to try to control how bright the shine is. I set it lower for ambient specs than direct specs. But, we still get cases like this, where looking up a wall in a dark area (Alberic's Curse, the inner church) has an out-of-place shine. The PBR has rim light & rim shine baked into its algorithms, so it does this super-brighting on its own. This would be a situation where spec & roughness maps would control it I guess, so maybe my PBR pipeline isn't using them well enough. Not sure.


Screenshot Compares

Here's some before & afters with non-PBR vs. PBR. It doesn't make a dramatic difference in scene-wide lighting. It mostly enhances things you're looking closely at by making the lighting act more realistically.


PBR does it's own rim lighting and rim shine for direct lighting. (not so much for ambient lighting, so kept that as it's own light in ambient). So, in direct lighting, when PBR is on, the rim light & rim shine turn off to let PBR handle it all. Surfaces with real speculars treat PBR more like glossiness while fake specs do more rough / hazy specular.



PBR makes light form around objects more realistically. The metal objects use real speculars, so they do more of a glossy shine instead of hazy/rough shine. The player lantern shines off the metals, reflecting light back. The falloff of the light adds more body/depth to the pipes & boiler. The window light also highlights the boiler more... PBR is handling all the direct rim light / rim shine here. So, no fiddling with variables to try to "get it right". The PBR algorithms have the smarts built into them.



PBR enhances the lighting along the camera view. The metal lamp post pops out, because it's using specular as more of a gloss. But, the overall lighting tone has changed. The lighting on the posts to the left follows the falloff of the light source better, so instead of both posts being evenly lit, one is a bit brighter than the other.



Mission 2, the weeping statue ... PBR helps the light follow the countours better.



The stairs stand-out here, b/c PBR works best on things with more contours. It adds better light falloff and specular shine. The flip back-n-forth is my non-PBR shader vs PBR shader. Since my non-PBR pipe adds fake specs to diffuse brightness, it punches up colors. Since my PBR pipeline is set to do fake specs like real spec lights intead of adding them to diffuse brightness, colors seem to have toned down a bit while lighting has punched up a bit.


I can post screenshots, but one of the things that makes PBR nice is how it looks when you move. The light just contours around things better. There are spots where you look straight up and go "why is that wood building super shiny?" But, that's b/c I'm doing the best I can with what I have right now.


  • Like 4
Link to comment
Share on other sites

v 0.82.b

  • fixed spazzy water

Water (eg: in Training Mission rope arrow canal area) was flickering like a disco ball again. The PBR fake specs acting like real specs weren't taking the diffuse color texture into account. So, the math changed from this:

  • diffuseLightColor.rgb * vec3( specularPBRfake )

... to this ...

  • diffuseMaterial.rgb * diffuseLightColor.rgb * vec3( specularPBRfake )

Crisis averted. :P



Link to comment
Share on other sites

v 0.82.c

  • optimized BRDF a bit by skipping over it if 0 value inputs would just generate 0 value output
  • roughness is now ( 1 - grayscale(diffuse texture) )
  • roughness is now used as a mix/lerp factor to better control the gloss/haze range
  • tweaked roughness to tone down glossy real specs while hazing up fake specs a bit so they don't look wet

Noticed some surfaces had too even of a shine, like covered in oil or clear-coat. Had this issue with fake specs when using too similar of a value to the diffuse color. So, I did like the one article said and inverted the diffuse grayscale. This let opposite areas of the diffuse stand-out with specular, creating more variation in the specular on the surface.

I standardized it all to do that.

It was still a pain using multipliers and other junk to try to coax the roughness into something that looked decent. So, I just switched it to being a lerp factor...

  • roughness = mix( 0.2, 0.7, roughness )

This gives better control of the gloss/haze specular.. so real specs I could make a tad more glossy while fake specs I could make more hazy with better precision. Too narrow of a range, and things get too even-shined again.. so tried to keep at least a 0.5 range between low & high values (eg: 0.2 to 0.7, 0.1 to 0.6, etc)

With BRDF going, my FPS has dropped a bit, but game is still playable. So, I looked at the functions, and noticed that if some key variables are 0, they'll just generate a 0 output. So, added some logic branching to skip BRDF and just default to 0 if those key variables are 0. Didn't really help with performance; not many things will be 0.

I've seen some BRDF's where folks use the dots (NdotV, NdotL, etc) to see if those are 0 and skip processing. But, the problem is with ambient light, NdotL will be 0 for walls. So, walls and windows and things won't get specular lighting. I pipe in the NdotL*0.5+0.5 half-lambert, and that helps, but then NdotL will never be 0, so no point in testing for that.

There's probably other ways to optimize this thing, but for now just trying to keep it simple.

(Now that I have a handle on it, I was going to look at Disney's / Burley's github repo again later.. dude has tons of BRDF's. Maybe I can find some interesting tweaks / optimizations to make this basic BRDF better. But, that's for later.)


  • I really want to get a noise generation routine going, because it could be used to tone down the "wet / gloss look" of speculars while preserving the brightness / shine. I can't seem to figure that out, though.


Link to comment
Share on other sites

8 minutes ago, totallytubular said:

With BRDF going, my FPS has dropped a bit, but game is still playable. So, I looked at the functions, and noticed that if some key variables are 0, they'll just generate a 0 output. So, added some logic branching to skip BRDF and just default to 0 if those key variables are 0. Didn't really help with performance; not many things will be 0.

It's worth noting that such conditionals do not necessarily have any positive impact on performance in a shader. They only do under very specific circumstances, the easiest of which is if the value you are testing is a uniform. If instead it somehow depends on the vertex input or a texture or similar, it almost certainly will not help. This is because fragment shader invocations are scheduled in groups, and unless a conditional can be definitively decided for the entire group, it will have to evaluate both paths for all fragments. So from my understanding, testing anything involving the normal should not help.

Link to comment
Share on other sites

Join the conversation

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

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

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

×   Your previous content has been restored.   Clear editor

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


  • Create New...