Martin Sawtell's website

Car Paint

Whenever I try a new renderer, I always seem to make carpaint. It's fun and it looks good- there's a lot going on with metallic paints to do with the angle of the surface to the camera (facing ratio). So why not give it a go with Renderman?

Here's what I came up with (rendered via Houdini):

Here's some previous attempts in other renderers (In various states of completion)




I had a look around the net for papers on making car shaders, and I found a great one here, titled "Photo-realistic Rendering of Metallic Car Paint from Image-Based Measurements". While I don't need to go as far as measuring the reflectance properties of real world car paint they got some great results:

Looking at this kind of paint we can see a few different things going on:

There are two basic layers of paint- the top coat which gives a sharp reflection, and the bottom metallic paint chips which give a glossy/diffuse reflection. The top coat's reflection is less visible when the surface is at a normal to the camera ray, and more visible when the surface is parallel to the camera ray. The bottom coat "glows", often with a slightly different hue at a normal to the camera ray, and becomes darker as the surface angles away. When viewed up close little paint flecks/chips become visible which give it that metallic lustre. So recreating these basic features would be the first step towards making car paint.

The basic model:

Lets start simple and work our way up;

Top layer: Standard raytraced reflection, multiplied with the (inverted?) facing ratio.
Underlying paint: Input the basic colour, offset it to a new copy, use the facing ratio to blend between the two thus creating the "glow" look. Add noise in somewhere to give it the paint fleck look. Also give it a specular component with a colour derived from the paint colour.

The complex model:

Top layer: Same as before except with added controls over how much of a reflection there is, and how it fades on/off over the facing ratio.
Underlying paint: Input a base colour, input a hilight colour. Input a "glowiness" factor. This time instead of the "glow" being a function of the incident camera ray and the surface normal it will be a more real-world representation- a function of the viewing angle (I), the surface normal (N) and the light direction (L).
Essentially what we have now is a basic BRDF function where spechilite = f(I, N, L) except if you look at the paper's render you will notice something interesting happening:

The model is inside an HDRI environment, and instead of being illuminated and generating it's specular highlights from point source lights, it's using glossy highlights via raytracing (as illustrated by the blue paint glowing over the same angle/place where the corresponding bright HDRI reflection happens to be). We could therefore use the gather() function to implement this however we'd compromise render times. As mentioned before, in the paper they actually measured the reflectance properties of real carpaint to generate the BRDF profile which isn't really necessary for aesthetic purposes, however we could use the graph in the paper as a reference for intensity as a function of viewing angle.

Next up is the paint chips:

In Real Life (TM), the paint has depth to it and has a bunch of aluminium flakes/spheres/whatever that reflect the light from different depths. If we REALLY wanted to this could be physically modeled with actual geometry, however that would be insane. Not that people haven't done that before: Simulation of sparkling and depth effect in paints.

One option is to use a bunch of noise functions to get the sparkle since really, the sparkles are in essence a probability function of the viewing angle, normal and incident light.

Another interesting option would be to represent the sparkles using a displacement map, a great example of this can be found on Fundza:

The advantage of this is that no raytracing is required! All we'd have to do is do the fake displacement, and the BRDF profile efficiently gives us an averaged specular sheen to it, way faster than tracing glossy rays. HOWEVER, it would only work with point/spot lights- ie it doesn't derive it's illumination by tracing reflections. Even if we were to do this with a simple 1 ray per hit reflection model, the result would be very noisy and unusable.

Lets do this:

Time to make the basic version...

I made a quick RIB test scene with 3 lights. If you follow the code below, this is how it went:
First I got the mundane normals setup out of the way and added a diffuse calculation, I also gave it a quick inputcolor. Then I set up the facing ratio which will for the basis for determining the reflectivity of the top layer.

I looked up the environment command and added that, however the vector maths is wrong so it's rotated 90deg, nevermind that for now. I noticed that even after multiplying the environment reflection with the inverted facing ratio, I was still getting a very strong hilight on the facing polys, this is due to the env map being a 32bit HDR tif, so it had very high values in places. The solution to this was to get the environment reflection colour, and then clamp it back to 0-1 space. However sometimes it's nice to have the flexibility to go above 1, so there's an upperClamp value added in the input list. In the final Ci calc the shellac colour was added, however sometimes the value was negative so was darkening the shader. I solved this with another clamp.

surface carpaint_basic( float Kd = 1; float shellac = 0.6; float shellacOffset = 0.01; color upperClamp = 1.2; color inputcolor = color (0.337,0.521,0.933);) { //set clamping values color minC = color(0,0,0); color maxC = color(1,1,1); //set up normals, vectors etc normal n = normalize(N); vector i = normalize(-I); normal nf = faceforward(n, I); float fr = abs(n.normalize(I)); //calc facing ratio color diffuseValue = Kd * diffuse(nf); //add in some diffuse vector rRay = reflect(i*N,N); //get reflected vector color calcEnvColor = environment("uffizi.tex", rRay); //use the env function to calc env reflection color envColor = color clamp(color calcEnvColor, minC, upperClamp); //clamp it color calcShellac = (shellac * envColor * (1-fr)) - shellacOffset; //shellac layer calc color finalShellac = color clamp(color calcShellac, minC, maxC); //clamp it Oi = Os; Ci = Oi * Cs * inputcolor * fr * diffuseValue + finalShellac; //final maths }

Next up lets use a specular highlight to add a glowy reaction to light. I've already multiplied the underlying layer by the facing ratio (see the Ci calc) however we can do more...

Here's what it looks like with a basic specular component, I've added a few more inputs for spec level and roughness.


Not too great, but we'll use this as the basis for starting to work with the pearlescent metallic layer. I guess we could leave this there with a relatively low intensity just to give a bit of a specular sheen that looks like it belongs to the shellac layer of paint.

How to get varying noise in the pearlescent hilight? An easy way is to plug noise into the roughness of a spec function:

roughness = noise(s*250,t*250);
color specColor = specular(nf, i, roughness / 3);

After adding the noise function and setting some more inputs, this is what we get!


Things still aren't right around the grazing angles of the sphere, you can see how we get somewhat bright and unseemly hilights there, but it will do for now. Playing with the inputs we get some variations:


Plugging this into Maya, using a model and some spots + a key, we get this:

Getting this to work at home with RPS 14.0, RMS 1.x and a floating license was... a journey. I had to get maya and RPS to play with Alfred and Maitre-d (the job scheduling and arbitration server/s), explanation to come...

Complex Version (or bits of it):

So, the basic version looks good but it doesn't actually reflect it's environment. Even a basic reflection would come in handy here, so let's give that a go. I added a basic reflection (using a guide on Fundza) and modified the RIB sphere scene to effectively test it:

Yeah it works.

Let's do a glossy one too via gather!


Turning the basic shading layers I had from before back on, we can see the combined effect:

It's not in a scene that tests how it will perform when reflecting an entire environment, so I made a constant shader and put it on a giant sphere that encompasses the entire scene. I also now made a second glossy relection layer- this time to begin simulating the sparkly frontal metallic sheen from the metal flakes. However performance immediately went downhill exponentially. Baking the information into a brickmap isn't an option since reflections are view dependant (well, we could use that method if we just wanted to do stills but that's somewhat limiting). I guessed that it's probably because it's doing a gather calc on one shader, and when those gather rays hit the next bit of geometry the same gather calc gets called- thus the render times increase exponentially. If we set the global trace depth to 1 it solves this problem, later I'll look at adding it as a local parameter to the individual gather function.

And so we have the basis for the metallic highlight layer working efficiently:

Adding them back together and colorizing the metallic flake style reflection:

It should be noted that the noisy metallic looking reflection gets that noise from it's sampling rate, not an inbuilt explicit noise function. When the surface animates this might end up looking like grainy boiling noise/mess, we shall see...

Adding everything back together and balancing out input parameters we get this!


There's still a long ways to go:

The metallic flake noise function on the basic shader layer isn't very sparkly. It gets washed out quickly too, looks like a clamping/upper level thing and the entire noise/sparkle concept needs to be worked on more, plus the 'glowiness' could be more pronounced. From a distance though the shader looks great, and it now reflects whatever is around it plus you still have the flexibility to add in the "fake" effects.