A nice shader for track materials

Discussion in 'Racer Physics and Technical' started by Mr Whippy, Mar 23, 2013.

  1. Mr Whippy

    Mr Whippy

    I've been increasingly frustrated with Racer over the last few years because all our materials seem to look a bit unrealistic.
    There is no argument that Racer can look good, and good textures are still half the battle, but now we have HDR and nice tone mapping, true realism is still missing a bit.

    I'd looked at using blurred envmaps on materials and reflecting them to give more realism, but technically this is throwing MORE energy into the material which is wrong. How would we balance that?

    I'd looked at ambient specular, which is just another way of saying 'diffuse light from over there specular' multiplied out a million times for all the subtle light from every direction in the sky etc. But again, how do you make sure you preserve energy?
    Ruud added energy conservation but it didn't feel like it'd work within our existing materials. You could still get wildly wrong looking materials.

    Then I kept reading a lot about modern game engines (which use full HDR, energy conserving systems) using gloss maps a lot, and this seemed to answer a lot of my problems with Racer track materials. Specular gets some nice looking results, but things like asphalt and kerbs and walls, everything really, just still looks a bit unrealistic.
    I've used lots of non realtime renderers over the years for all sorts of realistic stills/animation rendering, but since we just 'use' them rather than actually write the code for them to work I've never really appreciated how we get the real result from a mathematical/energy point of view, just been a keen observer of how things *should* look to be realistic.
    I'm still unsure how to write these shaders properly but I know what materials should respond like and so that is the goal with these changes. Get materials that look to respond right to the environment they are in!

    So I decided to add these lines to my standard_bump_f.cg shader, calling it standard_bump_glossy_f.cg:

      // Specular map
      float3  reflectiveNess=(1-baseCol.rgb)*baseCol.a;
    So basically what is happening here is:

    reflectiveness (the specular value) is the inverse of the diffuse texture (note that if you tint the texture with the rgb values in the shader then that won't be taken into account)
    We then multiply that result by the alpha channel of the diffuse texture... this just gives us more freedom to get the 'look' we want. I guess you could think of it as an occlusion map for specular, or maybe both, or just to balance the final appearance of the specular value... after all, inverting the diffuse texture isn't perfect, you might want to tweak it some more!

    Check here for the logic behind that change:

    My only concern here is that when checking the output of reflectiveNess it doesn't seem to appear like a negative of the diffuse (outColor.rgb=reflectiveNess;) until you turn the headlights of a car on for example, and the result where the headlights hit seems sensible... (guess maybe the reflectiveNess is missing out on being tone-mapped properly etc, so the value is quite low in rendering?!)


    The second change is the shininess (or glossiness) is able to be authored across our material. Much nicer for things like asphalt, kerbs, anything really that isn't a pure homogenous material. In this case we use the alpha channel of the normal map to define the glossiness.

    So we ask the shader to take the normal map alpha channel, normalise it from 0 > 1 (instead of 0 > 255), then multiply the shader gloss value by that map.
    This means we specify the glossiest value on our material in the shader (shininess=100 for example), and then the alpha map then scales the shininess from 100 > 0, using white > black on the texture.

    Energy conservation needs to be turned on, because otherwise the specular value won't intensify appropriately where the material is more glossy, and also the diffuse value won't be reduced to conserve the energy (or overall intensity) of the material.

    This is done in lighting.cg file by uncommenting the #define line.

    // Energy conserving diffuse/specular?
    The end results of this on asphalt roads look really nice (relatively quick textures here, if you spent real time you'd get a better look again)


    The current missing link in this is that ideally specular needs to respond with a fresnel effect. Currently energy isn't conserved very well using the currently system where power is equal at all viewing angles, so the road example which looks ok in the video above, starts to look too powerful when viewed from above in midday sun (like a road of little mirrors haha!), and then at dusk driving into the sunset it looks far too subtle and not powerful enough.

    Filmic games covers this subject a little bit here:


    I think many tracks might not look good if we suddenly upgrade Racer to use energy conservation, fresnel specular etc. So maybe it'd be good to write in the required support for new shaders that can take advantage of these realistic material responses.
    Ie, if I could just make energy conservation run on just my standard_bump_glossy_f.cg shader, and also turn on fresnel for the specular for just this shader, that would be really useful... that way legacy content can stay looking as it does but we can just use these new shaders where we want the upgraded features.

    The only gap in my understanding at this stage is how the ambient values are impacted. I'm guessing that fresnel/IOR already impact specular/diffuse properties in response to the gloss value, and so I also assume that ambient needs altering appropriately too. Ie, maybe at shallow viewing angles we will find the ambient value becomes dominant and gives the subtle 'ambient specular' response I was looking for before?

    Hopefully Ruud can help out with some of these issues, but if not I give a shout out to anyone who thinks they can get specular fresnel working with the default fresnel coefficients (or defined ones if they are provided). I had no luck doing this as I had no TEXCOORDs free or in an appropriate format for the fresnel function to work from fresnel.cg.

    Maybe this shader isn't needed everywhere, but for a lot of materials that we see a LOT of the time, up close and personal (like the asphalt we drive on), using this kind of shading really livens things up and gives us a lot more realism!

    Cheers for reading this far, and your comments/views on these kinds of changes would be much appreciated!


    • Like Like x 4
  2. Mr Whippy

    Mr Whippy

    Well I got some time tonight again to invest in a nice shader for tarmac.

    In the end I butched the dyn bump reflect shader as it combined an existing fresnel in the structure, and the envmap elements.

    I then threw in Stereo's envmap mipmap fix (makes the edges of the cube map soft), and it came out quite nicely.

    Notice mainly how we get a nice low-sun evening specular, that then subdues when we set the time to midday. We don't get a crazy powered specular at midday from any angle, especially top down.

    Also note how we get a nice ambient in-fill at steep angles, so the road appears to be lit by the colour of the sky/environment more.

    Downsides here are clear, straight-on to surface ambient lighting approaches zero, but since it's rare we see the road straight on from most angles this isn't a huge problem... but still not ideal as it's not technically done right as per real lighting effects.

    Also plus points are that reflection is set to 1, specular is set to 1 in the shader, everything just works without any fine tuning. Mainly the work is just in the textures and is logically done.

    So basically we have the same as before (last post/video), but combined into the shaders mentioned above we now have the following.

    First I took out what I thought was a bodgey kinda line
    // Ks*=saturate(1.0-length(eyePosW-IN.Position)*30.0/far);
    Not sure why it's in there or what it's trying to simulate, maybe the scattering of a sharp specular as distance increases (via mie/atmosphere scattering?!)

    Anyway I took it out and put this in the place.

    So here we have mixed between 0 and specular with fresnel values in the shader. I needed to move the fresnel define up to the start of the lighting structure in the shader too so I could call it earlier.

    I also used Stereo's environment driven ambient from the mipped envmap for the ambient lighting. This has it's pros and cons. Some cons work as pros at different times.
    Maybe the water reflect shader thing will be the perfect solution? Until it's functioning I can't test it out though.

    Also I used fresnel on the ambient. So at glancing angles you can see the 'ambient specular' as we'll call it, but at steep angles you get no ambient.
    lightAmbient = texCUBElod_soft(envMap,float4(N,9.0f));
    lightAmbient = FresnelMix(0,lightAmbient,1,fresnel);
    Technically this is wrong though. I just fudged it because generally you see diffuse when looking at surfaces straight on, so I pushed ambient to the glancing surfaces.
    The principle is sound enough ish for roads at road viewing type angles, most of the time... it's closer to right but still technically wrong hehe.

    All three terms need to be processed via fresnel, mixed based on viewer vs surface normal vs light source angles.

    Probably a big function but it's heavy on maths and I think Ruud would enjoy making this shader instead hehe ;) :D

    Using a dyn shader as a base is also a bad idea iirc since the vert transforms done are more costly etc etc.

    But hopefully this video and my thoughts will act as inspiration to those more capable than me.
    I really think Racer needs a shader like this for 'hero' materials... and if the surface you spend most of your time driving along and looking at isn't a surface that deserves some nice fancy and realism improving shading then I don't know what is :D

    Thanks for reading/watching!

  3. boomer541


    Nice work, Dave. For those of us that are prone to make code editing errors it would be helpful if you'd post the shaders as .txt file attachments so we can try them. Looks really good!

    I also like the floating track with the car casting a shadow under it, LOL.
  4. Mr Whippy

    Mr Whippy

    I'm gonna try sort the blending issue in the next few days. If I do or don't then I'll post where I get to.

    I'm learning a lot but generally at this point I'm WYSIWYG'ing it and just reading through the shaders a lot to work out what is going on.
    I know what look I want and what makes sense, but the syntax of shader generation is a real pain. Once I get the inputs and bits and bobs (where using an existing shader with the bits you need is useful), then I'm kinda good to go.
    But as said that is not ideal for this shader since it's for statics and right off the vert shader is for dynamic objects.

    It's far from my strong point, so it'd be really good if someone like Stereo could figure some of this stuff out, or Ruud, and give us all a super-powerful but properly made shader to do this kinda stuff.

    Lots of ideas, so little time hehe :D


  5. Mr Whippy

    Mr Whippy

    OK I've uploaded a small test course here.

    The main focus here is the asphalt and it's texture/shader. The track around it should be ok. Especially I think the TOD curves which are from IES values (although there is no tinting in ambient really, like they are white balanced, something Racer doesn't have!!!)

    It cycles ok from night to day, but day to night seems a bit off.

    I've also been logging sky intensity today, so I think the sun_intensity value is good too.

    Clouds don't work, so don't use them.



    Things these shaders do at this stage.

    Have energy conserve turned on (lighting.cg in renderer/shaders folder)

    The road specular uses fresnel, so at glancing angles it's intense, but at steep angles it reduces in power.
    Ambient is multiplied by the gloss map and then applied using fresnel too, this is 'ambient specular', though I'm not sure if this is the right way to do it. It makes sense but the energy conservation pipeline isn't clear so I just have to guess for now until others can clarify :)

    The tarmac aggregate details are passed in a 'detail' element (normals and gloss), and then diffuse textures are used for overall colouring and gloss (wear map) in alpha.

    The shader then balances out the textures to give something quite nice.

    In the middle of the track are two test balls. One is lit with ambient (from TOD curves), the other from the mipmapped envmap of the track.
    This is useful to balance out the TOD ambient values, to the sky colouring at different times of day etc.
    Note this is costly, ideally we'd have Racer internally take the average top-half intensity of the mipmap and use that automagically for the ambient value, but for now we have to set them manually which these balls help with!

    The intention is to make your diffuse/wear textures for your asphalt in fairly low resolution and then let detail textures add in the extra details.

    This means you can add a lot more variety into the diffuse/wear appearance because it's lower definition (so not using oodles of GPU VRAM)

    I plan to add white lines and markings and some drain covers etc as decals. This is the intended workflow with these shaders.

    Stereo is planning to help me out with streamlining these shaders etc, and also hopefully making the detail textures use global uv coords (so we can just scale the detail till it looks right, and then map diffuse however we like!)
    We also plan to add some interesting features so where wear maps are high (ie, glossier), the normals from the detail are faded away a bit to suggest worn down aggregates etc...

    There are also some fudge elements in there (like mixing the ambient specular over lit/shaded passes to try balance it out better... but I think that is probably quite normal unless you have a system with many cube map lookups!

    Hopefully we will get to a stage where the author just picks from 3 or 4 detail textures that cover most aggregate base types for tarmac (from highways to race courses to worn single lane back roads etc), and then the author only really needs to worry about the diffuse colours and wear map... and of course their decals :D

    So play away and tell us what you think.

    I've been driving a bit today and been looking through polarised lenses backwards and forwards into/out of the sun... I think this is getting there, much more than the standard shaders we have now at least.

    It's surprising how much asphalt changes it's appearance. We take it for granted but if you take a look around it's a very dynamic appearing surface based on sun position and viewing position!


  6. Stereo


    That part at least is easy; change line 85 in asphalt1_f.cg:
      float4 bumpColor=tex2D(normalMap,IN.Position.xz*scale);
    The scale in the shader also needs to be adjusted since the units are metres instead of texture coordinates (which seem to be about 4-5m on this track), so somewhere around scale=0.5.
  7. William Geuze

    William Geuze

    Doesn't work for me with RC5 sadly

    Fri May 31 20:56:35 (INFO ): [racer] Loading track 'asphalt' [rtrack.cpp:1285 / RTrack::Load]
    Fri May 31 20:56:36 (WARN ): [racer] DGPUShaderManager:MakeObject(asphalt1_v.cg): can't create CG vertex shader program [dgpushader.cpp:910 / DGPUShader::LoadAndCreateFromFile]
    Fri May 31 20:56:36 (WARN ): [racer] DGPUShader::LoadAndCreateFromFile[asphalt1_v.cg]: The file could not be read. [qerror.cpp:41 / QShowCGErrors]
    Fri May 31 20:56:41 (FATAL): [racer] DGPUShaderManager:MakeObject(asphalt1_v.cg): can't create CG vertex shader program
    The file could not be read. [%s]
    You didn't supply the folder name for the shader files in track.shd correctly. However, still no worky :p
    Fri May 31 20:59:51 (WARN ): [racer] DGPUShaderManager:MakeObject(data/tracks/asphalt/shaders/asphalt1_v.cg): can't create CG vertex shader program [dgpushader.cpp:910 / DGPUShader::LoadAndCreateFromFile]
    Fri May 31 20:59:51 (WARN ): [racer] DGPUShader::LoadAndCreateFromFile[data/tracks/asphalt/shaders/asphalt1_v.cg]: The compile returned an error. [qerror.cpp:41 / QShowCGErrors]
    Fri May 31 20:59:51 (WARN ): [racer]  data/tracks/asphalt/shaders/asphalt1_v.cg(56) : error C1008: undefined variable "CalculateAtmosphere"
    data/tracks/asphalt/shaders/asphalt1_v.cg(64) : error C1008: undefined variable "CalculateAtmosphereExtinction" [qerror.cpp:46 / QShowCGErrors]
    Fri May 31 20:59:57 (FATAL): [racer] DGPUShaderManager:MakeObject(data/tracks/asphalt/shaders/asphalt1_v.cg): can't create CG vertex shader program
    The compile returned an error.
    data/tracks/asphalt/shaders/asphalt1_v.cg(56) : error C1008: undefined variable "CalculateAtmosphere"
    data/tracks/asphalt/shaders/asphalt1_v.cg(64) : error C1008: undefined variable "CalculateAtmosphereExtinction" [%s]
  8. Alexander Knoll

    Alexander Knoll

    William, do you copy the files from the track directory/shaders to your racerdirectory/renderer/shaders? make sure the utils.cg is moved to /renderer/common...
    this is what i get...

    and rc7:

    ...booth works...
  9. Stereo


    Had a play with the shader, this is what I've come up with so far:
    The specular's not really 0 on the alpha=1.0 section, it's just very large and soft. I also painted that part lighter to be sure I could see it. The bumps also disappear on the alpha=0 section, or at least are quite worn down.

    In a larger context, you can see what the 'groove' looks like as a result:

    Had a bit of a play with the max shininess, and got it to where with alpha=0, it's near a mirror-reflection, basically as shiny as tarmac gets. Now it just needs the reflection layer mixed in with an appropriate mip level. In proper usage, this level of shiny should probably be reserved for the tar seams that get worn mirror smooth, it's the only place I've seen the specular get this sharp. But I feel like it's important to leave the options open.
    It's funny how the eye compensates and makes the median look darker inside the specular hotspot than on either side, just because of the brighter white on either side.
    • Like Like x 1
  10. Mr Whippy

    Mr Whippy

    Looking nice Stereo.

    I guess the issue here is, alpha on the diffuse map is in gamma space and so the 'roughness' map is in gamma too.

    Then we have the control 'detail' texture (normals + gloss of details) in linear space.

    So the mixing there might be confusing.

    I wonder if it'd be sensible to hard code in the shader to load the alpha of the layer0 texture in linear space (somehow)

    That way it might make tuning the maps more intuitive? Ie, we can blend them in PS to get an idea of what the final result might be looking like.

    I think whatever approach is used, as long as an author can basically achieve the final result they need then that is good.

    So far I'm thinking.

    Macro diffuse = constant, but we can multiply a bit of the detail gloss map against it to add some variance (currently done with 10% of detail gloss map addition)

    Macro gloss = added over the top of the detail gloss, a straight add. More white = more glossy surface. Also more white = less normal map influence linearly interpolating to a smooth surface from mesh normals.

    Detail normals = how the freshly laid aggregate and tar appear (or maybe even ok for concrete roads) so no wear is authored here.

    Detail gloss = again how the surface would look freshly laid. Different tarmac appears different here, so some aggregate may be very glossy with almost lambert tar (cold rolled high quality tarmac), wheras some hot rolled types where the aggregate is initially coated in tar may appear less glossy aggregate, but the rolled tar might increase gloss (so a more homogenous initial appearance)

    I think that makes sense, so the trick will mainly be in making convincing diffuse base textures (getting the base colour right), and then choosing the appropriate detail initial type (ie, hot rolled, cold rolled, aggregate mix (gap graded or a mix, density of aggregate with volume etc) (yes I studied a bit of tarmac at uni haha)), and then making a nice wear map!

    I think the only danger with having the shiny tar lines authored is that they won't have any bump map associated, and they will have to be very glossy to remove the underlying detail map... and they are not always very glossy... sometimes the tar lines can be painted on and quite dull if they are old and at the sides where no one drives on them.

    I think the glossy tar lines are still best as decals, though you could probably author them in here with higher res base diffuse/gloss maps... more so if they are just there for a bit of character and effect and work for that situation :)

    I still think it's good to look (these days with more GPU power and stuff) at the asphalt as the laid surface and texture it appropriately.
    Anything painted/laid on the road (white lines, tar seals, drain covers etc) should be a decal with a different shader... that way you also get the ability to add a unique surface behaviour for it too (ie, more slippy etc)

    So far my only other thought is the ambient reflection. High gloss = sharper reflection, so envmap input is useful here... but costly.
    I suppose you can turn it on for special road areas where you want high gloss (drag strip start line maybe?) or a wet track.

    The only sub-thought from that then is the flipping relationship between gloss/matte.

    It seems from the blog here:

    That on a rough surface we use ambient light intensity * base colour (diffuse map) for ambient influence (specular?) and then as we get more glossy we use ambient light colour * base colour intensity (the end result being a mirror reflection at the intensity of the base map (so darker than a mirror in the case of asphalt usually)

    If I find the time today I'll generate some polygons down the middle of the road and some at the sides so we can start to play with decals for white lines etc...

    If anyone has any requests for little things to add that might make tuning asphalt more interesting and accurate then please let me know (was thinking of some street lights maybe, or traffic lights to cast some colours into reflections (more so when highly glossy or on a night time)

  11. Mr Whippy

    Mr Whippy

    So thinking more today while driving around and looking at tarmac.

    Does it make sense to author the detail texture how we want the surface to look (detail wise) when fresh, and that the wear effect is then changing from that.

    So we make the alpha map on the detail look how we want the tarmac to look, then we add in the wear map to get the worn appearance.

    That means everything gets glossier and smoother, which is probably about right.

    But are there any special cases where that isn't the case? I couldn't think of any while driving so we can run with that for now.

    Stereo, what code have you used for the normal map > mesh normals blending? (ie, smoothing down worn surfaces?)

    Also have you implemented a system whereby the glossier surface reflects a lower mip level of the envmap via the 'ambient specular' system?

    Lastly, have you looked into the way ambient specular is done? Ie, use base colour and ambient light intensity for matte, and then use base intensity and ambient light colour for gloss and interpolate between?

    It seems once those few extras are ticked off things might be easier.

    I'm curious too if the apparent disparity between ambient value in TOD curves and what we see via the envmap illumination is due to the last mentioned behaviour above being missing?

    From what I can find/measure recently all values appear to be right in TOD curves/intensity values of sky colours etc, so I can only guess that it's that behaviour which is currently wrong in the shader?

    Any way, on to a road marking decal set...

    Does anyone have any preferences? I'm gonna do UK for now as I have lots of reference materials :D

  12. Stereo


    The actually-reflective one was a test of the mip level controls - which is why it looks like wet pavement (it's coded a lot more like reflections off of water than of asphalt - to display the reflections I just copied the Fresnel Mix out of dyn_reflect).

    To blend normal map to mesh normals, I basically have it interpolate from the bumpmap normal to (0,0,1,1) - which is pointing straight up at maximum gloss for the detail map. The interpolation control is a combination of baseMap.a and bumpMap.a, such that higher values on the bumpMap get flattened more.

    Haven't yet looked into the ambient specular, I expect that mixing the reflections into there is a better move once the gloss variable is in sensible terms.

    For the specular spot, I had to raise the linear map to a high power anyway - that is, it's using (10^bumpmap*shininess) instead of bumpmap*shininess, so the effects of gamma are just a factor in that equation.

    On one end of the scale there's gonna be maximum shininess, which corresponds to baseMap alpha being at its limit, and that can be taken from the shader. Where's the other end of the scale? What's the lowest shininess value that we want to produce? That determines the range we're working with. I imagine for a single set of pavement, the max shininess will be consistent across various basemaps, so the min should cover the same stuff over the whole range of possible states of that pavement.
    I find it easier to explain with a diagram; this is basically the way it behaves right now (note that the vertical axis is log_10 scale). So the question is what should the minimum be, and how much of the triangle clipped in the top corner, representing pavement polished as smooth as possible but not as flat as possible. Plus, how much of a shininess gap should there be from highest bumpmap to lowest bumpmap?
  13. Mr Whippy

    Mr Whippy

    I think shininess always needs to start at 0 (or just have a + 1 at the end, or something to always keep values just above 0?)

    The maximum shininess is then whatever we want it to be... I guess once the specular spot is as big as the sun then it's as shiny as it can/needs to be.

    I think that blog I linked to talks about the shininess range they use. I guess beyond a certain point you can't really tell the difference any way such is the amount of colour depth you have to describe the gloss level.

    In my shader the max value is that defined in the shader track.shd, and then the shader simply scales between 0 > track.shd value (iirc this is how the fancy car glass shader works), but with a +1 or similar to stop 0 shininess (with black shininess map) which caused NaN errors and spiking auto exposure :D )

    Since we have, in theory, 512 levels in two 8 bit alphas for the shininess (or maybe a bit less due to the clipping at the top of your diagram), I think that should cover all shininess values nicely.

    My initial thought really is that maybe the clip of shininess should be at white on the detail map alpha. Then when we add the 'wear' we simply gloss up the areas which were not white on the detail map (so already white areas on the detail map can't get any more glossy), and also reduce the normal map influence as you mentioned.

    That way we can, in theory, have glossy aggregates that remain just as glossy as the surface is worn (which some might do)...
    For those who want the aggregates to get more shiny, then in theory the detail map can be authored with the aggregate chips at values under white (allowing them to gain some gloss as they wear)

    That seems to give the author the ability to NOT have elements get glossier if they don't want them to... which makes the base detail maps (which are the hardest bit to make in a way) more flexible for a wider range of uses?

    Then again some clipping might make sense...

    I'm really open to anything as long as authors can do what they need to do. If the detail maps that are created are almost like an off the shelf piece you don't really need to tamper with too much, then you author in diffuse colour and wear and things react realistically and intuitively that is good.

    That might mean asphalt where the aggregates gloss up with wear can have off-white values in the detail map, but asphalt where the aggregates are already very shiny when rolled into the tar, can remain just as shiny even with a lot of wear (white to start with on the detail map)

    I suppose that part of the code is easy to fudge around with any way... it's just how you mix the two maps in the end...

    So what is the actual line of code for the normal influence blending? I have no idea how to achieve that effect in practice :D

    Also, I agree the ambient specular is in essence just blurred reflection... technically it should just be the same thing and probably done via the reflection route. The reason I went ambient specular initially, and that the 'reflection' features is even in this shader is more because it had to be to get the fresnel to work hehe :D

    There are probably two shaders here, one that works via reflection map (maybe even the new 'water reflect' shader... or cube maps if we can make them look right...

    And then one that uses the cheaper ambient light values (like the one I'm using here when you turn off the reflection map lookup for ambient) to fake the effects needed (generally good enough for dry asphalt with fixed TOD etc) and get more FPS...!

    I'd generally like the eye-candy do-everything reflection map based shader, but it is pretty costly and using the car envmap isn't always ideal.
    Maybe cube maps could work here ok? Or the new reflection shader when it works?

    But I'd still like to make a cheap shader that works ok with just the ambient light input.

    Hopefully when this shader is perfected that tweak will be easy to make... remove the refmap references, make it a non-dyn shader vertex transform etc etc... hopefully we can get 90% of the speed of the old bump shader but with 90% of the look of the eye-candy refmap fancy shader we are developing here :D

    So for now if you are happy to share the normals code (or the shader as it stands with you currently), then we can have a play more with authoring different tarmac textures and see if they offer the flexibility and intuitive behaviour we want.

    I also want to try write something semi-elegant to do the gloss > matte inverse mixing for base colour/ambient intensity > base intensity/ambient colour thingy... that seems quite important as I play more as I just feel to be getting too blue results for more matte roads... yet it looks natural with shiny roads as per your 'wet' look shot.
    I think that is a telling sign that this process is probably important :D

    Sorry for repeated long posts, but brain-dumping is necessary so I can remember what I was thinking. My brain turnover seems far too high, maybe brain RAM gets smaller as you get older haha :D
  14. KS95

    RACER Moderator


    After many years, we're used to it. All I can say is... tl;dr. :p
  15. Stereo


    Gonna clean up the shader a bit before sharing it, I had to move lines of code around to get information available in the right order (can't calculate tweaks to the normal vector until after the basemap is loaded for example) so I'm gonna try to organize it, delete the 'aborted attempts' lines of code, etc. and then post up the whole thing.

    The reflmap code, for now, sits entirely in the #ifdef for cubemap mipmaps -- if there aren't any mipmaps there's no point trying to add its influence. I suspect if your card's high-end enough to have this feature the extra processing is not a big deal so maybe it's a good way to have a fallback.

    In fact, utils.cg could be further modified to include a #define that's only present if someone's got the modified 'soft' mipmaps, to make this more bug-proof. Hmm.

    The nice thing about using 10^(texColor.a-1)*shininess is, 10^x is positive for all values of x, so it behaves nicely with the 'positive unbounded' nature of the shininess's specular shape. The -1 is just so that with an alpha of 1 it's at the value set in the shd.
  16. Stereo


    Ok, asphalt1_f.cg
    // Dynamic model, bump+reflection
    // In world space (more like the other shaders, and gives a chance to modify reflection vectors)
    // RvG, 19-11-2010
    #include "atmosphere.cg"
    #include "lighting.cg"
    #include "fresnel.cg"
    #include "shadowmapping.cg"
    #include "../common/utils.cg"
    // Vertex to pixel shader structure
    struct v2p
      float  extinction    : COLOR;
      float2 tc0            : TEXCOORD0;
      float3 Direction      : TEXCOORD1;
      float4 RayleighColor  : TEXCOORD2;
      float4 MieColor      : TEXCOORD3;
      float4 Position      : TEXCOORD4;    // For fragment shader
      float3 normal        : TEXCOORD5;
      // Fresnel in pixel shader
      float3 I              : TEXCOORD6;
      //float  fresnel        : TEXCOORD6;
      //float3 R              : TEXCOORD7;    // Reflection vector
      float4 tangent        : TEXCOORD7;
    void main(
      // In
      in v2p IN,
      // Out
      out float4 out0 : COLOR0,
    #ifdef CSM_MRT
      out float4 out1 : COLOR1,
      // Constants
      uniform sampler2D  baseMap  : TEXUNIT0,
      uniform sampler2D  normalMap : TEXUNIT1,
      uniform samplerCUBE envMap    : TEXUNIT2,
      uniform float3    lightDirection,
      uniform float3    lightColor,
      uniform float3    lightAmbient,
      uniform float3    ambientSpecular,
      uniform float3    eyePosW,
      uniform float    atmosRayleigh,
      uniform float    atmosMie,
      uniform float3    Ke,
      uniform float3    Ka,
      uniform float3    Kd,
      uniform float3    Ks,
      uniform float    Kr,
      uniform float    shininess,
      uniform float    far,
      uniform float    mipmap,        // Mipmap level of reflection map (blurry if >0)
      uniform float    sunny,
      uniform float    scale,
      uniform float    exposure,
    #ifdef CSM
    uniform sampler2D    shadowArray  : TEXUNIT7,
      uniform float4x4    smTexMatArray[SM_MAX_SPLITS],
      uniform float smSplits,
      uniform float    fresnelBias,
      uniform float    fresnelScale,
      uniform float    fresnelPower
      float3 skyColor;
      // Get texture map info
      float4 baseCol=tex2D(baseMap,IN.tc0);
      float4 bumpColor=tex2D(normalMap,IN.Position.xz*scale);
      // Texture space axes
      float4 tangent=IN.tangent;
      float3 N=normalize(IN.normal);
      float3 binormal=cross(tangent.xyz,N)*tangent.w;
      // Expand normal
      // Mix to 'flat and shiny' based on the interaction of the two values
      // Calculate new normal in world space
      float3 bumpNormal=N+(bumpColor.x*tangent+bumpColor.y*binormal);
      // Reflection vector
      float3 R=reflect(IN.I,N);
    #ifdef CSM
      // Output shadowing
      float lit = GetShadowFactor(IN.Position, IN.normal,shadowArray, smTexMatArray, smSplits, lightDirection)*sunny;
      const float lit=sunny;
    //lightAmbient = texCUBElod_soft(envMap,float4(R,12.0f));
      // Get sky gradient color
      // Specular map
      float3 reflectiveNess=1-(baseCol.rgb);
      float gloss = 0.5f*bumpColor.a+2.0f*baseCol.a-1.5f;
      // only load an envMap if we have soft mipmaps
      // note: the 7.5f corresponds to shininess and really should depend on it.
      float3 envColor=texCUBElod_soft(envMap,float4(R,7.5f-4.0f*gloss));
      // Lighting
      float3 ambient,diffuse,specular;
      //FresnelMix2 lets us mix using lerp vs fresnel, so we can go from black to white and vice versa, rather than FresnelMix which just does additive blend vs fresnel
      //define fresnel
      float fresnel=Fresnel(fresnelBias,fresnelScale,fresnelPower,normalize(IN.I),normalize(N));
      // fresnel on specular, yay!
      // ambient specular options
      // really the cubemap mipmap is a more accurate representation of the 'ambient' light.
        // the higher the gloss, the more it should use the reflective color.
      float3 litColor=(baseCol*(0.8f+0.2*bumpColor.a))*(diffuse)+specular*reflectiveNess+(ambientSpecular*0.6817);
      //float3 litColor=baseCol*diffuse+specular*reflectiveNess+(ambientSpecular*0.6817);
      float3 shadowColor=(baseCol*(ambient*+Ke))+ambientSpecular*0.3183;
    #ifdef CSM_MRT
      // Mix sky with texture color based on atmospheric influence
      // Blending
      // Mix sky with texture color based on atmospheric influence
      float3 finalColor=lerp(skyColor,shadowColor+litColor*lit,IN.extinction);
      // Need to clamp output - my nVidia GTX285 only handles colors upto 255, then makes them +Inf!
      // Blending
    http://nmurdoch.ca/cg/asphalt1_f_2013-06-02-0029.cg (timestamped for posterity)

    I moved the reflections into the ambient lighting. I know less about pavement than Dave but I would guess around 0.5 alpha (rough pavement) to 0.7 alpha (well worn groove) is where you want to be - it ranges from 1.0 alpha being fully flat and shiny to 0.0 alpha being completely rough. As far as I know, this means I haven't changed the energy conservation properties of the shader - only made the coefficients more dependent on the textures. What I did to figure out a good range of alpha is just set one of the sections to a test texture with a smooth gradient white to black in the alpha channel, and figure out which results were what I wanted.. You probably want to set 'shininess' higher too since it corresponds to a way smoother bit of tarmac. To get a specular spot the same size as the sun it needs to be over 1000.
  17. Mr Whippy

    Mr Whippy

    Cheers for the updates Stereo.

    It all looks fairly sensible to me.

    Just my initial thoughts.

    The litColor, is the 0.8f +0.2*bumpColor.a to be multiplied by, so the result is always around 1, so we just add contrast to the diffuse map a little bit, rather than do a simple add?

    That is nice visually, but is it still 'cheap' from a processing point of view? I guess alongside the other calculations going on it is pretty cheap, but just wondering :)

    Lerp against gloss to move from ambient light value > ambient envmap value seems ok for now... a quick and cheap solution to the observed behaviour.
    But how much does it deviate from the technically correct approach. I guess I need to do more testing here hehe :)

    ambientSpecular is result of ambient * bumpCol.a (which is what I had done in my code)... I wonder really if it should be ambient * gloss result?
    I think I had it set like that originally but it made the whole road look too blue. Now with the above lerp for gloss > matte perhaps using the gloss result to apply ambientSpecular to will look more natural?

    I'm still baffled by the gloss result maths... but if it works then I'm happy. Do we need the power function to make the curve non-linear, so we weight more of the texture information to the lower gloss values where banding might be more apparent (a bit like gamma)?

    I guess we can do some reading/testing here by having a 256px texture go from white to black and then look for banding in the gloss result.
    In practice I suppose if we go from 1 > X gloss and then have no shininess parameter to set in the track.shd then that is the most ideal situation... the same for specular too.

    Really we want to be able to combine/atlas all these road textures (sharing the same detail textures) so having settings the same across all shaders seems sensible.

    So assuming we have a 256 intensity alpha on layer0 and one on layer1, then we have 512 intensity values for gloss.
    Do we get any banding if we set shininess to 1000 and use white in both the alpha maps?

    Maybe we should just set these as absolutes and then authoring is totally consistent and appearance is all determined by the textures (which makes sharing etc of textures for certain 'looks' really easy between content developers?)

    Lastly, I'm afraid the shader isn't energy conserving for at least one reason. I forgot all about it but it's something I've mentioned a lot in the past.

    The sun in the envmap!

    We currently have a specular and in the high gloss areas we also have the envmap sun spot!

    I'm also thinking if this is why the ambient envmap mipmap seems too intense vs the TOD curve values we have!

    I was also watching a video of a Caterham R500 on a coastline course in Project Cars last night and noticed their sun. It just looks like a basic texture hanging in the sky with a high output. In theory a lens flare (single decal) graphic of a sun would suffice for the sun in Racer.

    Then we can move back to having the specular do all the work for us, and the sun spot can be replaced by specular with no issues with energy conservation.

    The only area I will miss the sun spot in envmap is on car shaders. Right now we get specular to do a nice metallic effect and use the sun spot to be the lacquer specular... but in the end we can just write a second specular spot and value for the car paint shaders so we can achieve the same effect I guess.

    So maybe the scope of this shader being accurate runs into issues because of Racer's overall shading logic...
    Maybe it's worth patching the atmosphere.cg shader (turn off the sun spot) and recommend a tweak to the racer.ini to turn on an appropriately made lens flare spot for the sun!? (lens flare isn't rendered into the envmap which is the reason I think that is the best approach for now?!)


  18. Stereo


    I get banding on the top end that I think is because a texture that's loaded with normal gamma mode gets its alpha channel compressed like crazy; it's made slightly less obvious by the bumpmap scattering the results.

    Essentially the argument for the power function to make it non-linear goes something like this.
    The specular spot's size is determined by another power - the normalized brightness is angle^shininess, where angle is 0 (perpendicular to the sun) to 1 (result vector points straight at the sun. Really, it's the dot product of the reflection vector and the sun's direction). So for shininess=100, you have angle^100 - these days I use Wolfram Alpha to visualize this stuff quickly.
    If you evaluate the angle where this reaches, say, 0.1, you can get an idea of how 'wide' the specular spot is. That is, solve for angle in angle^100 = 0.1 - again Wolfram Alpha can do this for you, the result is 0.977. Say you set the minimum shininess (via shininess*baseMap.a + 1) to 1. For angle^1 = 0.1, the result is just 0.1. So you go from a specular spot 0.023 wide to one 0.9 wide.
    What comes next is, what's halfway between those? Well, if you want a specular spot 0.46 wide, you stick that into the equation - (0.54)^shininess = 0.1. This comes out to a shininess of 3.7. So you're cutting the specular width in half by making the alpha channel only ~9 brighter on the scale of 0 to 255.
    How about if you look at it the other way - what's an alpha value of 0.5 correspond to? x^50 = 0.1 resolves to 0.955.

    Now, if you turn shininess into a power function of gloss, you get this equation: angle^shininess = angle^(10^gloss).
    For the same values as before, shininess = 1 and shininess = 100, you have gloss = 0 and gloss = 2, since 10^0 = 1, 10^2 = 100. That gives us the range - gloss should vary from 0 to 2 (the shininess from the .shd is implicit in this for now).
    This time, if you look for 0.54^(10^gloss) = 0.1, you get a gloss of 0.57. So it's not perfectly linear, but it does spread out the options quite a bit - if the gloss range of 0 to 2 corresponds to 0 to 255, this is a 73.
    Comparison plot - here the x axis is "baseMap alpha" and the y axis is "angle at which specular is 10% of peak brightness". It does flatten off at the top, but you have a lot more room to work with.

    On another tack, the mipmap level is already implicitly a power scale. With a value of 0, you have top level, or 1/512 of the texture. 1 gives you 1/256, twice as wide. 2 gives you 1/128, 4 times as wide. 3 gives you 1/64, 8 times as wide. And so on. If you think of the mipmaps as basically being equivalent to a bunch of specular spots, then you get that, say, shininess=1200 corresponds to mipmap=0, shininess=600 corresponds to mipmap=1, shininess=300 corresponds to mipmap=2, shininess=150 corresponds to mipmap=3, etc. (Again, Wolfram Alpha) So, if you want the two to match up, you would be looking at shininess = 150*(2^(3-mipmap)). That this is shininess ~ (2^mipmap) implies that if gloss ~ mipmap, shininess ~ 2^gloss. That this is 2 instead of 10 is compensated for in the shader by a constant - gloss ranges from 0 to 2, mipmap ranges from 2 to 10, or something along those lines.

    With the energy conservation specular, the spot blows up in intensity at the right rate as shininess increases, so it turns out to not shrink to 1 pixel worth of texture map - it ends up the same size as the sun spot is on the envmap, which is nice confirmation of the result.

    If we take user-control of shininess out of the shd it's not a problem, but as it stands I haven't yet set the mipmap to depend on that value. Really it's just a matter of putting a log_2(shininess) in the right spot.

    In terms of time consuming operations, I wouldn't worry about multiplications and additions. For one thing, these can get compiled into single instructions on many architectures - that is, you can do (a+b*c) in 1 clock cycle - and for another, they're not hitting the memory like a texture lookup does, so the effects are fairly minor. You can certainly do optimizations on order of operations but it's not going to contribute as much to runtime as the third texture map.

    In the absence of a set of real-world data that the shininess curve could be fitted to (what is specular size measured in, in units? The 'shininess' value is simply a matter of convenience, in that (angle^shininess) is what you calculate), I feel like doing it this way makes it more flexible in terms of letting the end-user get the effect they want.
  19. Mr Whippy

    Mr Whippy

    Hmmm, ok I guess that makes sense then hehe.

    I'll be honest that I don't really 'get' the pipeline at this stage, but that is as much because of the route specular takes all the way from lighting.cg, through the fragments of energy conservation, and then into this new shader with tweaks :D

    As long as the end result means specular that look right for given input values then I'm all good.

    So I suppose the shininess value set in track.shd now dictates the max shininess an object can have?
    Do you think we should hard-code that so it can't be set? That makes any author using a base detail texture get the same result whatever they do with it... which kinda makes sense considering the texture will be tuned with a specific look in mind to start with?

    That then frees up the shininess variable for something else perhaps...

    Anyway, I turned off the sun in atmosphere.cg and did some playing but the ambient appearance on a ball doesn't seem to change much... the 'up' block of the sphere I tested didn't change much, maybe 1-5% intensity, nowhere near enough to balance out the disparity in values I see from TOD ambient > envmap ambient differences in intensity.

    Lens flare works ok with a nice texture, doesn't show up in the envmap, but doesn't obey the clouds variable so a cloudy track doesn't work so well hehe.

    My 2p is that Ruud should dump the atmosphere.cg sun rendering, return to a flare type structure (ideally not hard coded, set it per track/camera type even) and then specular ALWAYS deals with the energy of the sun reflections.
    Having sun reflected in two systems is never going to be good for energy conservation or realism!


  20. Mr Whippy

    Mr Whippy

    Ok just having a play with your shader updates at last...

    It seems good but it's hard to compare directly with the one I had made because of the changes to other elements.

    So I'm having to tweak my base textures to see what happens.

    Initial thoughts are that the detail map alpha influence on the diffuse base seems more subtle? I'm not sure what the right solution is there but the way I had done it felt like I'd made a diffuse detail map almost...
    Is the result the same with your method? Maybe it's just because I've tweaked values now hehe... hard to compare :D

    Lastly I think the normals are buggy. I've had to rotate the bump map 90deg clockwise to get the lighting appearing correctly.

    But apart from that, adding a linear gradient across the texture for 'wear' does seem to give a pleasing range of results, and an intuitive texture painting process could be made with those controls :D

    In fact, you could probably create a slightly more aggressive detail normal map now since they represent newly laid surfaces, and then let the wear map soften them as it does!

    I'm also wondering if we hard-set the shininess to 1000, and specular to 1, then we might be able to use those input values to control some of these other things.

    Ie, use "specular" to control the influence of detail alpha on the diffuse base?

    Maybe use the shininess variable to do something else?

    A bit of a fudge on naming and intuitive use but this shader is getting some hard-set variables which are still in a sense author tweakable.

    Then again, that then deviates from the purpose that we can just use these textures anywhere and other users can copy them and they will look consistent because they have no control except the textures in the first place... hmmm...

    Also they will atlas up and batch optimise better with identical shader entries.

    PS, I'm testing here with the sun turned off in the envmap:

    I just hard set the sunny influence on mie to 0 in atmosphere.cg
    float miePhase=(sunny*0)*0.000079577471636878192976419142055225*atmosMie*((1-g)*(1-g))/pow((1+g2-2*g*cos),1.5);
    Also my 'flare' settings and texture:


    I guess when testing this shader it's a good idea to do this because otherwise with very shiny road surfaces you may be getting bad results as you are erring towards double specular response at fully glossy!