Friday, August 3, 2012

Vertex-painting with Bob Ross

RLR (Realtime Local Reflections). A fancy word for... reflections. Realtime.

Did some interesting graphical enhancements last weeks. Realtime G.I. finally works a bit... got to be careful with such statements, getting consistent results that always look good is hard to achieve with G.I… Furthermore, RLR (Realtime Local Reflections) have been added for pretty accurate reflections on complex surfaces. I'll post about this soon, when I have some nice pics to show with it. And last but not least, we did some finger-painting.

Some artists make money just by throwing a bucket of paint or virgin menstruation blood on a white canvas. Random splatters, abstract stuff dude, smoke enough and you'll see what it means. Normal people however tend to paint / plaster their walls as smooth and equal as they can. Yet for games the artist has to be careful with repeating the same boring texture over and over again. For two reasons. First, even high-res textures still lack detail to vary enough, In reality, no matter how hard you try, even a white boring wall has some inconsistencies. Little drill hole here, crack there, darker spot in the corner, slight bump here, old brown blood from a squished mosquito, et cetera. In reality, you get all those details for free. But if we had to paste tiny mosquito blood decals or mini-holes on the walls in a game, it would take ages to finish.

Second reason, true realistic graphics suck actually. Why else do you think they need a light-experts, smoke machines and tons of make-up on a movie set? We want dramatic scenes, not clean white plastered walls we see every day in our own house. That's why we overdo it a bit with larger decals, damaged spots. And that’s also why I implemented an artist-throwing-with-buckets feature.

See that green wall? When our new artist Diego(from Spain, of course) showed me that texture, my first thoughts were "....". The wall looked boring (in an empty room I must say). But then again, what else do you expect from a green plastered wall? If you look in a new empty house, you won't see huge damage decals, random cracks, yellow pee stains and Mickey Mouse holes either. So basically, there was nothing wrong with this texture. But how a more dramatic look then?

Of course, you could add a few random details on your texture. For example, let's place a larger crack in the center, and paste some zombie vomit in the upper right corner, just for fun. Well, that might look good from nearby, but if you apply the texture in a larger empty room, it becomes a bit odd that this zombie vomits the same splatter every 3 meters at the same height. In practice, you probably won't use such a specific detail in your textures. That's where we have decals for. But also that bigger crack will become noticeable soon. Mip-maps may help you hiding this repeating pattern after a couple of meters, as such effects become more blurry in lower mip-map levels. But still... The magic trick for texturing is to apply as much detail and variation in your image, but without making it too noticeable repeating itself.


Entropy
================
As said, specific details should be added afterwards with decals. Decals can be placed everywhere, anywhere. Plus they can be rotated and scaled so you can reuse the same crack / hole / splatter / or whatever detail multiple times without the viewer directly getting aware of your dirty tricks. However, decals aren't always perfect either. Either you'll have to draw a LOT of variants, or use them a bit careful so you don't see the same picture being stamped on the walls over and over again. And using many decals may also have an impact on your performance. Also when you need to apply variations on a larger scale, you either need multiple decals or very large textures.

Since half a year or so, Tower22 has Vertex-Painting tools. You can draw(override) pre-baked occlusion values with those, in case you want to make a corner darker for example. But it can also be used for "Entropy". For each material, additional textures can be defined so you can manually draw variations on your surfaces. For example:
metal > corrosion
Brick > painted parts / worn parts
Asphalt > holes / wet(water pool) parts
Pavement > Green moss between the tiles / displaced normals to make an uneven surface
Wallpaper > parts with paper peeled off / dirty parts

Pay attention class. Notice the dark wood tiles being repeated (the texture contains 4x4 tiles) in the background? Now look at the foreground. A different texture variant (a more pale looking one) was mixed in around the TV.

If you google “Entropy shader”, you’ll find some nice (UDK) movies. The basic technique is pretty simple, just mix(lerp) between textures based on the per-vertex weight values. It's similar to terrain rendering shaders that allow you to draw grass patches, sand or rocks. But to make it look more natural, smart tricks and masking textures can be used. Let's take a stone floor where we want to add moss. Where does that green stuff grow first? Exactly, in the gaps between the stones. So if you supply a heightMap somehow, you could fade in moss at the lower parts of the texture first. Here some pseudo code
// Fetch Stone textures (base layer)
stoneAlbedo = tex2D( stoneAlbedo, uv );
stoneSpecular = stoneAlbedo.a; // we stored specular in albedo alpha
stoneNormal = tex2D( stoneNormalAndHeightMap, uv );
stoneHeight = stoneNormal.a; // we stored height in the normalMap alpha
 
// Fetch Moss textures
mossAlbedo = tex2D( mossTexture  , uv * uvRepeatValue );
mossNormal = tex2D( mossNormalMap, uv * uvRepeatValue );
  
// Fade in moss. Intensity is stored in vertex.weight1.x
// Lower parts will get moss earlier
fadeInFactor = saturate( (1.f - stoneHeight + bias) * vertex.weight.x );
outputAlbedo = lerp( stoneAlbedo.rgb, mossAlbedo, fadeInFactor );
outputNormal = lerp( stoneNormal.rgb, stoneNormal.rgb + mossNormal, fadeInFactor );
outputNormal = normalize( 2.f * outputNormal -1.f );
// Reduce specular on parts with moss
outputSpecular = lerp( stoneSpecular, float3(0,0,0), fadeInFactor );
That's just one way to mix. You can also use the normal. If you want to add snow for example, surfaces facing upwards should carry more snow, while surfaces facing downwards shouldn't carry anything at all. Obviously, the way you mix depends a lot on the type of material you'll be adding.


Wall painting
================
For our green wall, we did yet another trick. The ideas was to have "repainted" spots. The kind owner of this room in Tower22 repainted some worn parts with a fresh layer of paint. The repainted parts should have a slight different(brighter) color, and the cracks should be less visible on those parts. As shown above, you could make a greenish "repainted" texture variant. But what if we want white or orange paint instead of greenish? Got to make yet another texture? Or how about customizing the color values manually with the vertex-paint tools?

If enabled by the surface shader, it's possible to adjust the Hue / Saturation / Brightness values locally. Again, by painting per vertex. The colors would get transformed from RGB to HSV, then we add/subtract to offset values given by the vertex weight values, and transform it back to RGB again. This allows to make the green wall darker, brighter, white, or pink for that matter.

Yet the initial "Hue drawing" results sucked a bit. By nature, weight values interpolate between 2 vertices. So if we painted the center vertex red in the pic above, it color would smoothly go from red to green, like a gradient. Unless you are Bob Ross, that's not how painting works. The transition from one color to another should be harsh. And if we did a quick & dirty paint job, we should see brush-streak patterns right? No worries, the GPU cooling fan is the limit.

To make more realistic transitions, you can make use of a mask texture. The values on this texture can be seen as an offset (or "height"). With a different vertex-weight, we paint this mask-texture (invisible) on the walls. The more intense, the higher the offset. --- offset = tex2D(mask).x - 1.f + vertex.weight.x ---
Then afterwards, we colorize it with the Hue painting tools. If the offset is 0 or below, nothing will happens. Once above 0, the color rapidly changes into its second variant, given by the custom Hue values.

No, this is not what you think it is. Just an artist throwing with buckets of virgin menstruation blood, that's all.

Pretty neat huh? With a relative cheap tricks (Hue manipulation & one extra read from a mask texture), we can customize this wall in many, many ways. And of course, this can be used much wider on a lot of different surfaces. All to "break the patterns". Preventing the eye from seeing the same happening twice in a scene is another step fowards into realism. Or at least eye-candy :)

No comments:

Post a Comment