1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
Dismiss Notice
Like RaceDepartment on Facebook.

The shader thread?

Discussion in 'Racer' started by Mr Whippy, Jun 17, 2010.

  1. I'm messing with shaders a bit right now.

    Right now I am looking specifically at adding control for specular to the existing reflect shader... on Deyan's Murcielago the unified wheel/tyre texturing poses some problems.

    I have to control reflection strength with an alpha map, so use the old trick of adding a syntax in the reflection shader so if we define reflection = -1, the baseCol.a is used to control reflection strength.

    ambientColor=FresnelMix(ambientColor,envColor.rgb,(Kr>0?Kr:-Kr*baseCol.a),fresnel);


    The problem now is that I have no spare texture slot for specular/shininess control.

    So I have added this little fragment of code (from the bump speca shader)

    Ks=baseCol*Ks;

    This now allows the colouring of the outside of the tyre in the diffuse (rgb) channels, to do some work.



    However, because the tyre is still dark in the diffuse texture (near black as it is a tyre), it doesn't have much effect on it's own.

    I need to somehow increase the brightness of the texture so Racer pumps up the specular we see.

    So I added a *5 multiplier to the Ks as you can see in the image attached.

    [​IMG]



    Now what I don't understand is why the two images look different.

    First case:
    Ks in car.shd is 8, while Ks = baseCol * Ks * 5 essentially gives us Ks = baseCol * 40

    Second case:
    Ks in car.shd is 40, while Ks = baseCol * Ks essentially gives us Ks = baseCol * 40


    Now, I don't get why they look different. Ks is meant to control the size of the specular too, but in either case they both calculate out to a peak shininess of 40, because the baseCol lookup isn't changing.
    The specular settings remain the same.

    It appears that multiplying by 5 in the shader means the specular level is pumped up 5x, while the shininess (size of the specular) is retained at 8 in the car.shd...

    That is the effect I wanted to achieve, pumping up the actual output of the specular blob, while keeping it soft at a value of 8, but it seems un-intuitive/wrong because the value that really needs bumping up is the specular value, not shininess? (though the specular being multiplied by 5x (2.5 2.5 2.5) looked awful)

    Am I missing something obvious here?




    Ideally I'd like to control reflection amount, specular AND shininess using one shader here...

    Probably using rgb.a for diffuse.reflection strength, and then another 3 channel texture for spec/shine and leaving another slot free for something else (perhaps transparrency?)

    Specular/shininess maps add so much to these kinds of materials, so it is a shame that they haven't really been considered in the default shaders very much. I think they should almost be defaults in all materials that err away from being generally reflective (ie, using the envmap)


    Bodging in the code I have for the Lambo works quite nicely in this case, but it's obviously not ideal to use the diffuse channel to control specular in this way.



    Hmmmm

    Dave
     
  2. Oookkkaaayyyy...

    Found the problem... I think.

    Ks = specular property, so in the 5x example I was actually boosting the specular to 1.25 1.25 1.25

    shininess = the sharpness of the specular, and is defined separately (using "shininess" in the shader files from what I can tell)


    Ks = how bright the specular should be
    shininess = how sharp the specular should be


    Can't see the wood for the trees!


    I still would like control of each separately though, so a Ks and shininess map would seem nice to have...


    What do people think to the channels needed for each? Would people like rgb control of specular, and then an 8 bit grey-scale to control shininess?

    I think I could almost get away with baseCol.r for specular, baseCol.g for shininess, baseCol.b for transparrency, and baseCol.a for reflectivity perhaps (using the old -1, 0....1 system for reflectivity?)

    That said, bump is also a concern if you want a normal map... hmmmm...



    Just how much can we load up a shader with texture info without hitting speed too much?

    Dave
     
  3. I am here too. Do you know how fix the so blue rayleigh on sky?
     
  4. Texture load depends on your GPU's RAM. But for specular maps etc. you're probably ok to have lower res textures.
    I still think that we can eliminate a large % of texture load by the way we create cars. These days you really don't need to bake in lighting, esp. with TOD coming into play, things just go south quickly. If you create a material for the car paint then we could potentially customise colour in-game/menu. I did a Proof Of Concept ages ago back when RSC was our home.
    Anyway, back on topic.
    The problem with having a control map for spec and shininess is that in the end you'll still need to have a multiplier out in front to get the correct value. A control map can only go from 0...1 but you can see that both Ks and shininess are >1, though it's quite easy to chuck in a texture lookup. Just depends on how many texture lookups each card can handle.
     
  5. Stereo

    Stereo
    Premium Member

    I don't know if it's generally helpful but I added a bunch of texture lookups (texCube) to a shader, and something in the range of 40 lookups were only losing about 1% fps relative to the normal 3 lookups (base, bump, reflect). That's slightly different from adding extra texture maps, and I don't know if there's a cap on number of textures in a shader...

    In general though you can just add additional layers in the shd, add texture x to the cg, and Racer will use them. Can't post an example right now cause I'm away from my desktop where I develop stuff.

    The tricky part of custom shaders from my perspective is getting them into the Racer package. Ruud mentioned in the 0.8.9 thread that custom shaders are discouraged, which is sensible, they tend to break with every version update (keeping my glass and chrome shaders going is the main source of crashes when I start using a new version). So we need to find some usable standard set that everybody can accept and work with, providing all the flexibility we need.


    While I agree with camsinny that baked textures aren't hugely helpful anymore (esp. with CSM and possibly proper occlusion at some point) liveries will still need that base texture, as well as any other textured details.

    I guess we need to work up a decent proposal for what information should be in the shader (stuff that can be a single value or a texture needs work, using negative values to tell it to look at the alpha is not very clear), what texture maps are available etc.

    Just having the Fresnel values adjustable from the shader, instead of constants in the cg file would remove need for my chrome shader for example. Really I think as much as possible should be defined by car.shd, thus removing need for custom cg scripts.

    Hmm...
    Code:
    shader_texture~dyn_reflect
    {
      reflect
      {
        value=1.0 ; Kr
        map=body.tga ; use body texture
        channel=a ; take alpha channel from body
      }
      Fresnel
      {
        value=0.2 0.8 2.0 ; Fresnel values
        ; no map of distinct fresnel vals
      }
      emission
      {
        value=0.0 0.0 0.0 ; Ke
        ;map=glow.tga
      }
      diffuse
      {
        value=1.0 ; Kd
        map=livery.tga ; paint colour
      }
      ambient
      {
        value=0.8 ; Ka
        map=occlusion.tga ; burnt occlusion
      specular
      {
        shininess=40
        ; no map for constant spec
      }
      bump
      {
      ...
    }
    Maybe this kind of structure makes more sense than the layer0, layer1, etc. approach. If a map= is not present just use the single value.

    In practice it would be simplified by defining most of these in the vf_reflect{} and just linking them, so a basic shader is just
    Code:
    shader_body~vf_reflect
    {
      diffuse
      {
        map=body.tga
      }
      ambient
      {
        map=body.tga
      }
    }
    Then to use the shader on another car, you copy vf_reflect{} over, and insert the relevant textures.
     
  6. Some cool ideas there.

    I agree with Cam on the ability to abandon full-car maps, but then we do lose livery capability... hmmmm...

    As for the spec/shine maps, surely we can scale these in the code? For example, shininess seems to range from about 0 to 100 in most applications in Racer, so in theory a single 8 bit channel can cover 255 tones, enough to have pretty fine control over the 0 to 100 range? (do we need to go higher than 100 in most 'normal' cases? Have a special case if in rare instances we do?)
    As for specular, again, should we ever really go above 0...1 anyway? How would a surface bounce back more energy than the incident light on that surface (ie, the sun reflection)? Again, for most normal materials could we not simply have 0...255 >> 0...1 for specular in the rgb channels?

    A spec map could consist of rgb.a, with "rgb" specular (0...255 >> 0...1), and "a" shininess (0...255 >> 0...100)?





    I like the layout above Stereo, but could it make for an elegant shader in the renderer folder?

    If it can, then it looks like a good idea.

    I also like the idea of defining more in car.shd... of course, we don't want to do TOO much there and make it complicated, but what you have shown would be intuitive. It removes any coding or understanding needs and simply presents more options in a clear nature.

    And as you say we can also nest variables at the head of the file, so when we define each material it may only be a texture or two input each.




    Right now I'm managing to do a whole wheel/tyre (two quite different materials) with one shader, dyn_standard_alpha_reflect_v/f...

    The diffuse channels alpha map controls the Kr, so the wheel has reflection, the tyre doesn't, then with specular map controls as said, I can turn off specular on the wheel to a certain extent, and simply add full-control specular to the tyre via the maps.


    So if simply adding one more rgb.a map can give me control over all car paints and rubber type materials that is pretty good.

    The ONE thing I don't have is bump mapping, but I don't think it works nicely without a full-range of specular control anyway... would having a THIRD map that controls bump cost that much more?
    Also, transparrency is not available here, but I think for transparrency, you could lose the spec map "a" for shininess (and use a single value for that), and use that map for transparrency?


    To me that makes 3 shaders to do most stuff for dynamics

    diffusey, reflecty, shiney

    diffusey, reflectly, almost shiney (cos of transpparency)

    Then either of those with bump added on, if needed.


    So thats four :D

    The the brake heat one and other specials... oh, and the fresnel definitions into car.shd etc too, then we can do chromes and so on more easily.


    Dave
     
  7. Ruud

    Ruud
    RACER Developer

    For the first issue, Ks is indeed just the specular color, shininess is used in pow(), so an exponent. Quite different. Shininess in regular OpenGL was 1..128 (perhaps also 0). I'd just use it directly without scaling. In your image, you can see that with low shininess, the specular shine is spread out more. With shininess=40, you get more a point. With shininess=128, you get a really small speck of specularity.

    Why do wheel (reflective) & tyre (non-reflective) with 1 shader? The cubemap lookups are still done, so more expensive than a simpler single-texture (perhaps plus a specular map) texture lookup. Texture lookups do matter, esp when anisotropic=16 (compare by setting it to 1 or 0).

    I appreciate the best would be to have 1 ubershader which does it all, but adding cubemap lookups and then throw them away is quite performance unfriendly.

    You can count on semi-transparent materials to need their own shaders quickly. Ordering stuff, re-using alpha, shadowmapping issues... They're quickly a problem and then need their specific shader.

    Note that the (Kr>0?Kr:-Kr*a) is an 'if' in disguise, and may have performance impacts, but I'm not sure if the latest superduper cards have too much trouble anymore with branching (which makes pipelining a problem). Seems GPU's are beginning to look like CPU's.

    As for specular maps, for opaque shaders I'd just use the alpha channel for strength (Ks=Ks*baseCol.a) and keep Ks (specular color) uniform (same for each pixel). For transparent shaders, there you go, you want the alpha to indicate transparency.

    You can ofcourse add a 2nd layer (texture); all cards readily allow 4 texture units to be used at the same time. For the 2nd texture, you get for example:

    col=tex2D(specMap,uv);
    Ks=col.rgb;
    shininess=col.a*128.0;

    Note that in Cg the .rgb is already in 0..1, even if the source texture was 8 bits. I'm more than willing to put a diffuse+specular shader in v0.9.

    I've seen the textures of a Grid track; 223Mb of DDS files (so quite the same as what goes to the gfxcard, since it's all raw data). 1700 textures in all, most albedo (diffuse) maps, bunch of specular maps and a series of lightmaps (squeezed into big textures). Would be nice to have lightmaps, but generating them is a pain (GT5 did it through a Swedish company). Just to give an idea of a modern game.

    I don't really agree on the shader 'diffuse', 'ambient' etc naming scheme for .shd files; for performance' sake you want to stuff lots of info into textures, using channels where available. Using lots of maps would push towards texture wastage.
    I do agree on the passing of uniform fresnel parameters though.

    For bump mapping, I don't think the extra texture lookup is that bad, it's more that everything gets transformed so you work in texture space instead of world space, which can be tricky to program. It definitely helps in the visuals, whereas parallax mapping was a bit of a disappointment.

    On ambient occlusion then, I think it's quite ok for the time being to bake in ambient occlusion in your diffuse textures. SSAO was very slow when we tried it, although Mitch has seen a method which was simpler (thus faster). Quite a few issues with it though.
     
  8. Ruud, the reason I am using reflection on the tyres is because the tyre AND wheel model are one part on the Lambo currently, with a curved texture running between the polys for the rim of the alloy and the tyre. There is no elegant way to split it without it looking a bit ugly (or me doing more mesh work)
    Happy to do either really, I've already revamped them a bit, but the cost didn't seem too high here, and it was elegant. If you think it is costly to do and should generally be avoided for the bigger picture point of view, then I can split the materials!





    Well I'm more than happy with the standards that exist now, but I think they might be named better so it's clearer what they do and don't do (and on Racer.nl a list of them and example of how they work) and we need more of them to give more flexibility for needs. There are not really that many.

    A little list here, mainly saying what texture features they have.

    standard(1tex): just diffuse
    standard, reflect(1tex): diffuse and reflect
    standard, reflect.a(1tex): diffuse, with reflect using alpha map
    standard, spec.a(1tex): diffuse, with specular(Ks) controlled by alpha map (scales the car.shd values, so tinted colours will scale in intensity)
    standard, reflect.a, spec.rgb, shininess.a(2tex): diffuse, reflection(Kr) using alpha, specular(Ks) colour/intensity by rgb, shininess by alpha
    standard, reflect.a, bump(2tex): diffuse, reflection on alpha, bump on rgb

    That kinda thing looks nice.

    For most car paints I want a diffuse with reflection control on it's alpha map, even with a bump map present... for newer cars that may not be needed so much, but the Lambo's panel line gaps need to have 0 reflection, even with the bump there! For high-res cars with panels modelled and bevels, then probably something as simple as standard with fixed value reflect is enough for starters (possibly with an AO map for ambient?)

    For tyres and plastics I want diffuse with spec.a control, so again I have control over the specularity there. That means I can throw all my rubbery/plastic items on one texture and define them all in one shader called rubber, rather than loads of shaders.

    For glass etc, diffuse, transparrency on alpha, and then probably another texture for specular rgb and shininess on alpha if you want extra control (I find I do mainly because of tints and embedded elements in older cars, a bit like the tyre/wheel merge in the old Lambo) Again this means all glass type materials can be thrown in one texture and use one shader.


    In my mind, a car should have maybe 4 main texture sets... solid(main car), glass(decals/transparents), effects(lights/glows etc), wheels/brakes(obvious), with each one having maybe one or two textures for diffuse/bump etc etc... And then maybe another texture set for a nice interior.
    Really, a car shouldn't need a big shader or complex shader file, even if the selection of shaders you can choose from (list above) is fairly large.


    The old days of having a texture for each object is just making shader writing/updating/optimising impossible... Stecki had it right back in the early days and it still works well today!


    Dave
     
  9. Also, one last thing.

    Can we run a blur on the envmap somehow?

    It would be nice for softer reflective materials that have some reflectiveness but it's not a mirror finish and not soft enough to be driven by speculars.

    Dave
     
  10. Even more last but not least.

    I can't get any kinds of transparencies to work for decals on cars.

    How do I actually use a transparency with cg?

    Do I use a blendfunc in the car.shd file still, or should the cg shader file handle what happens with different parts?

    Using the dyn_standard_reflect_window_v/f.cg files doesn't seem to do anything for my decals on the Lambo.


    I am also very confused as to what all the old values like cull, z-depth, alphafunc and all those things actually do, which are still in force, which are now outdated or not needed, and just generally what they all do?
    There are docs spread all over racer.nl but in each case they refer to both new and old gfx systems and there are very few working examples (ie, the Lambo we get with Racer doesn't have working glass for instance, or decals, and the baja has none, so I have no idea if things are broken or I'm just not doing them right)

    Dave
     
  11. Stereo

    Stereo
    Premium Member

    I think you use dyn_standard_reflect_v/f.cg for transparent textures that shouldn't reflect (they return the basemap's alpha unchanged, whereas reflect_window increases opacity on reflections). Or just dyn_standard, since the badges on the Lambo are small enough that it's not important that they actually reflect or not. You do need the blendfunc or other type of cull in the shader so that it will display it transparent (follow the examples from Carlswood trees if you want them to be able to cast shadows), otherwise the cg shader returns rgba and the shader ignores the alpha component.
     
  12. Ummmm...

    So what is the blenfunc :D

    Blend or one src_alpha?

    Or can I use an inverted alpha map and use one one_minus_src_alpha?


    Will play some more.

    What would be nice for the Lambo rear badge at least is a normal map with reflection like chrome (if only we could use falloff coeffs per shader, rather than forced to use fresnel only), and then an alpha map to trim the outline... then it would look really really nice :D

    Just I don't have that selection from the shader defaults, or an example of how to do it if we did :(


    I know what I want, just no idea how to code these shaders THAT well :) I can do some tinkering but that is far as I can go.


    I'll try write a definitive and well explained list of what I think I'd want to make great shaders, then others can pick it apart and then we will hopefully get a list, can make them, test them, and then get them in the v09 final!

    Dave
     
  13. OK, just working on some grass now (like Swiss Stroll), using the new tree shader... (up normals)

    I copied from Carlswood to start with, then tried a few other things.

    vf_tree
    {
    vertex_shader
    {
    file=standard_tree_v.cg
    }
    fragment_shader
    {
    file=standard_tree_f.cg
    }
    }

    shader_grass_01~vf_tree
    {
    cull=none
    layer0
    {
    map=grass_01.tga
    wrap_t=clamp
    alpha_to_coverage=1

    ;depthwrite=1 (doesn't seem to do much)
    ;alphafunc=gequal 128 (doesn't seem to be needed or do much, I can see grass through car windows and it occludes other grasses elegantly)
    }
    }


    Everything seems to work nicely there.

    Issues are, that for grass I don't think shadow casting looks nice. Is there any way to easily turn off shadow casting for this shader? It probably needs to receive shadows, just not cast them onto the floor around them as it looks really tatty.

    Anyone any clues?


    Many thanks

    Dave
     
  14. Was thinking a few minutes ago...
    Why couldn't we introduce graphics presets. This could then let people have the awesome looking graphics with CSM and the shaders with all the texture lookups and 3/4 shaders that do everything with 20 texture lookups while people on lower settings could use the other shaders that optimise lookups/shader computes etc.

    This would make a bit more work for artists but would be worth it IMO.
    Then low settings could use the old shader system?
    Not sure if that's possible...Ruud, is it possible to switch renderers at runtime? Could possibly include a launcher then with a cg and non-cg exe?

    Just thoughts on progress/the future/compatability.
     
  15. Ruud

    Ruud
    RACER Developer

    On presets, I think a track.shd and track_nocg.shd option (same for cars) might be good to keep non-Cg alive. I'm dropping LDR, also because if you have CSM support, you're bound to have 16-bit support (HDR).

    On the shaders, I'll mail Mr Whippy more directly to get a short line to make a few of those specular shaders. In general, the gfx pipeline should be understood to understand blending and such:

    - vertex shaders just modify vertex locations and attributes for each vertex (color/normal/texcoords)
    - fragment shaders can only modify RGBA values for a single pixel (and in CSM it does so for 2 output buffers, halfway towards deferred rendering, where the postprocess filter does the actual shadowing, combining diffuse and ambient lighting depending on the shadow state)
    - blending is separate; this is how the fragment shader's output is combined with the framebuffer (FBO)

    On blending: blending is a pain really for CSM. I tend to move blended objects to alpha_to_coverage materials using alphafunc (gequal 128). For decals, I do the same and then set depthwrite=0. v089 sorts shaders so depthwrite=0 shaders get drawn later than depthwrite=1. We needed that to put trackmarks on top of the road in our tracks for example. The trackmarks would jitter on/off until we determined that the Z values are mostly the same; therefore, you don't need to write Z for trackmarks.

    Culling: that is just early in the triangle drawing phase where the normal is checked. If it's pointing towards the viewer, it is accepted. If not, it is rejected (invisible). Culling=none is useful for fences and single polygon lines. Note that for collision objects, you do want to add volume! Newton has trouble with boxes less thick than 10 cm; had some discussion with Julio Jerez about that (he maintains Newton). So for guardrails, it's really better to have a boxy thing of 10cm thickness rather than use cull=none and have a flat polygon strip. Also, lighting is bad on the 'wrong' side of those objects!

    Z-depth: reading and writing exist: depthwrite and depthtest. For some materials you don't need/want Z-writing (decals for example, or transparent objects). The beauty of alpha_to_coverage is that it dithers and thus you can Z-write again and STILL have transparency, sort of. Most materials will want to read/test Z though.

    alphafunc: if a pixel's texture alpha fails this, the pixel will not be drawn at all (won't go into the fragment shader). Useful to cutout trees and fences.

    Most though, blending between layers is done now inside Cg, and you just specify a bunch of maps and a Cg shader. Blended materials like windows are still a bit unclear in CSM here as well...
     
  16. *respect*
     
  17. That makes sense.

    Old LDR/non-CSM and then new HDR and CSM...

    Most of the general techniques are the same, just the final touches...

    If we could move towards a single flag that just toggles the shader folders basically, so in Racer.ini it's just a switch from old to new gfx, or vice versa.



    I'm still a little confused about glass and so on.

    Alphafunc sounds nice but it leaves a sharp edge on decals, rather than a smooth gradient as alpha changes.

    alpha_to_coverage just seems to make everything a bit bitty and nasty, again, not so nice for anything except maybe grass and trees. Anything that needs to be sharp and smooth on a car it looks pretty bad.

    I can't get any nice results on the Lambo really, for decals at least. Glass seems to half work using a blendfunc = one one_minus_src_alpha on the Lambo, but using the same logic for the decals just fails (no idea why)


    Isn't there anything that is basically a good old "blend", where alpha is used to make a smooth gradient of visibility?

    Do we need to do that via the shader file itself, or just add blendfunc in the cars car.shd?



    I look forward to emailing with you Ruud, and hopefully showing you what I and others are looking at wanting (and the Lambo specifically for now), and how we should best achieve that!


    Dave
     
  18. Stereo

    Stereo
    Premium Member

    I can't really speak on how it works with the CSM engine (still didn't buy NVidia card), but on the Lamborghini I'm using these cg shaders and glass and decals look fine to me.

    Code:
    shader_glass~vf_glass
    {
      sort_offset=1
      reflect=0.8
      layer0
        {
          map=glass.tga
          blendfunc=blend
        }
      layer1
        {
          map=$trackenvmap
        }
    }
    
    shader_decal~vf_standard
    {
      layer0
      {
        map=glass.tga
        shininess=0
        specular=0 0 0 1
        blendfunc=blend
      }
    }
    Might actually have extra values now, I haven't messed with it in 0.8.9. I did edit the glass.tga though, to reverse the transparency of the windows. I can't remember why older cars have 1=transparent, 0=solid, but with cg shaders and blend it makes more sense to have 1 = solid, and write shaders with that in mind. Otherwise you have to mess with src_alpha one_minus_src_alpha type functions.
     
  19. Hmm, but your decal shader has no 'shine' to it, either specular or reflection. In theory that should be easily possible.

    The transparrency is reversed mainly because 'blend' was buggy, as was one src_alpha, so one one_minus_src_alpha was used instead. That meant that alpha's were inverted...
    It can also be better to do this if you want to use the alpha information for reflection strength too, or vice versa... just depending on what material you want you flip it to get the result desired from the reflection strength...


    I just don't get why we can't have a simple alpha blend that is crisp and sharp. These were used all over the place in the past without hardly any fps hit. Stecki's MINI for example uses poly strips with a mapped on line/alpha map for his MINI panel gaps. Almost all on-car details/decals were added this way.


    Ie, you can use a quad poly on the Lambo back badge, alpha map to cut it out neatly, diffuse map can be replaced with a normal map, then a 'chrome' material used (100% reflect, no fresnel falloff/falloff)...
    That way you get a lovely badge that reflects the environment and reacts to it really well, is cut out wonderfully with an alpha channel, and just looks ace, with no more or less texture data than it has now!

    We really are limiting ourselves to some rather poor standard shaders if we don't get our own good ones thrown in as standards to do cool stuff!

    How many cars are out there like the Lambo, with the mapping/alpha maps there etc, that can just be 'tweaked' and upgraded easily to look much better with just better shaders?

    Loads I would imagine!

    Dave
     
  20. Stereo

    Stereo
    Premium Member

    Right now the normal map doesn't affect material reflections so there's no real way to make it look better by reflecting (looks like a flat poly because that's what it is) the specular looks acceptable but it's fairly noisy. It's easy to add the normal map, trickier to edit the shader so reflections are calculated with the bumpmapping in mind (and to get a smooth enough bumpmap to handle reflections nicely). I did it once a few versions ago by shuffling a bunch of the graphics calculations out of vertex into the fragment shader (pretty much everything from the tbn matrix on down) so the normal texture would be used to calculate reflections. But it sort of messes with the sequence of the graphics pipeline doing that. Tiberius may have done a better job of it, I'm not sure.