This tutorial will walk you through skin shading using Arnold renderer for Maya. Scene files and texture can be downloaded here to be used as the starting point.The tutorial will also explore some of alSurface features by creating a skin shader and cover the basics linear workflow setup in Maya 2016 and MtoA.
We’ll also be covering displacement setup, use of maps for skin shader, how to linearize color maps, remapping specular maps to get our desired range and how subsurface scattering works in alSurface.
Open the file 00_start.ma from the scenes directory in the Maya project and click on preferences and turn on the Enable Color Management checkbox.
Go to the Arnold globals and set all the Gamma settings to 1. This tells Arnold that all your inputs and outputs are linear.
If you do a render now you’ll get the image below.
Notice that a default standard shader is applied, so go ahead and attach a new alSurface. One of the main features of alSurface is support for light groups, that allow users to put all the information for each light (or group of lights) into their own AOVs, up to a maximum of 8. This is equivalent to doing separate renders for each light, but getting them all in one render. Once done with the AOVs, users can rebalance them in comp to tweak the lighting to whatever they want.
First, select the shape node for each of the three lights in the scene using the outliner.
From the Maya menu, select Modify>Add attribute. Create an integer attribute called mtoa_constant_lightGroup. The mtoa_constant_ prefix tells MtoA to export this attribute with the geometry as user data so that the shader can pick it up. See image below.
Go to the AOVs tab in the globals and add the first three light groups AOVs to the active AOV list. See image below.
Click render and you will have access to the light groups in the render view AOV dropdown. Selecting them allows you to see the contribution of each light individually. We will use this later to help tune our subsurface scattering by examining each light in turn.
The displacement map is by far the most important part of any skin shader and thankfully the model we’re using comes with an excellent texture extracted from the scan. First, we need to convert our head mesh to a subdivision surface by editing the Arnold>Subdivision attributes on the shape node and setting Type to catclark and Iterations to 4. Next, add a File texture to the Displacement slot in the shading group for our shader and load disp.tx from the sourceimages directory. See image below.
By default the displacement will be a little too strong. See image below.
Set it to a more reasonable value like 0.1. When you’re setting up displacement for skin shading, you want the result without subsurface scattering to look like a plaster cast of a head, and the subsurface scattering we’ll add later will soften some of the hard detail we currently have.
Notice that the current fine detail seems to be at about the right level, but our guy is still looking a bit puffy. This is because the displacement map has values from zero to one, which means that the surface is always going to be pushed outward, whereas what we want is to make the low values displace inward so that the surface keeps the same overall volume. To do this, open the Arnold rollout on the displacement shader and set the Scalar zero value attribute to 0.5. This tells Arnold to consider any value less than 0.5 an inward displacement and any value greater than 0.5 an outward displacement.
Adding the Color Map
In adding a color map, just assign a regular Maya File texture to the Diffuse > Color parameter in the alSurface shader and point it to sourceimages/col.tx. If we do a render now, the result will look very washed out, like the image below:
The color is wrong because the texture is in sRGB. In the first part of the tutorial we set all Arnold’s gamma settings to 1, so Arnold will not automatically try and convert the texture for us. There are several ways to handle this. We’ll do a quick fix first and then do the correct way. The quick fix is just to convert the color space of the texture after reading it in the shading network. You can do this by dropping down an alColorSpace node after the texture and setting its Source space parameter to sRGB (the default). This will convert the texture result from sRGB to linear for us and the render will now look correct. See image below.
This works fine for our example but is in fact not correct. The reasons why are outside of the scope of this tutorial, but in short it’s because you have to do color space conversions before mip-map generation and filtering, not after.
To fix this, we’ll need to pre convert our texture to linear. We could do this in Nuke, but an easier way is to use the conversion option built into OIIO’s maketx utility. You can do it by using the shell:
but you can also do it by passing those same options to MtoA’s Tx Manager window. With the texture converted, we can now point our File texture to col_lin.tx and remove the alColorSpace node. See image below.
If you compare this image to the previous method, you can notice a very tiny difference between the two. While it’s small here, it can make a big difference in the general case, particularly when you’re rendering very high-resolution textures at a distance, so it’s a good habit to get into in general.
The specular qualities of a material are perhaps the most important thing to get right in order for the material you’re creating to match its real-world counterpart. As you can see, the default settings in alSurface don’t do a very good job for human skin, so let’s fix that. A good starting point is to set the following:
Specular 1 > Roughness value to 0.5
Set the Specular 1 > Advanced > Distribution parameter to GGX. The GGX distribution is new to Arnold 4.2.12 and generally matches real-world materials better than the default Beckmann distribution.
We’ll also set the Specular 1 > Fresnel > IOR parameter to 1.38 instead of the default of 1.4. Index of Refraction is probably the single most important parameter for accurately matching specular properties of a material, and we’ll cover them in more detail in another tutorial, but for now, 1.38 is a good baseline for skin.
This gives us a good base, for example, the area around the temples is working well, but the nose is feeling very dry. Next, we’ll add a map to drive the roughness differently in different areas.
Create a new File texture on our Specular 1 > Roughness attribute and point it to sourceimages/spec.tx. By default, Maya will connect the alpha of the texture to our attribute, which is not what we want (because the texture has no alpha). We can solve this by enabling the Alpha is luminance checkbox on the File node, but I prefer to plug the channel we want from the texture directly, so delete the connection from outAlpha and connect outColor.r to our roughness parameter instead to just take the red channel as our scalar value.
If we do a render now it will look totally wrong.
We can use the Debug mode on alSurface to figure out why. The debug feature of alSurface can be very handy for figuring out why your shader isn’t responding as expected by showing you exactly what values are being fed into it. Change the Advanced > Debug parameter to specular-1-roughness and the render will show you the value that is driving the roughness. As you can see, the nose, forehead, lips and cheekbones are brighter than the rest of the head because the map specifies the shininess of the surface rather than the roughness.
We want to invert the values in the map, and also to remap them to a different range: at the moment the values are between zero and one and we know that for skin we want our roughness to be somewhere around 0.5 on average. To do this we’ll use an alRemapFloat node, so create one and plug it in between the File texture and the surface shader. See image below.
We want to change the range of the map so that the black values correspond to our base roughness of 0.5 and the white values correspond to some shinier value, say around 0.3. To do that we use the Output min and Output max parameters of alRemapFloat. Output min says “A value of zero in the input becomes this value” and likewise Output max says “A value of one in the input becomes this value”. So we want our min to be 0.5 and our max to be 0.3, which will invert the map and remap to range so that all values lie between 0.5 and 0.3. See image below.
Now our values look like they should so switch the Debug mode back to off to see the difference it’s made to the surface.
The final touch to apply for our character is to add subsurface scattering. SSS is handled a little differently in alSurface to the standard shader so it’s worth going over the logic briefly.
In alSurface, SSS is considered a part of the diffuse layer of the shader. That’s because this is how it works in the real world: light enters a material the bounces around a little bit, changing color as it goes, then is bounced back out of the surface again towards the viewer. If the material is very dense, then the distance the light travels inside the material will be very small and can be modeled accurately with a regular diffuse layer. If the material is less dense, then the light can travel far enough that the material starts to look “squidy” and we need to use SSS to model the effect.
The Diffuse > SSS > Mix parameter allows you to control how much the SSS is “mixed in” to the diffuse layer. Generally, you want this to be either zero (for “hard diffuse only”) or one (for “sss only”). It can sometimes be useful to use values somewhere in between, but most of the time you just want one or the other. Go ahead and set it to one now.
Now our guy is made out of jelly. Since SSS depends on the world-space distance light travels through an object, it is heavily dependent on the size of that object.
We could use the Diffuse > SSS > Distance parameters to fix this, but it’s easier to change the Diffuse > SSS > Density scale instead. The density scale acts as a multiplier (well, divisor) on the distance parameters, so it’s useful for globally adapting the shader to the size of your object. If you change density scale to 10 instead of the default of 1, you’ll see we have a more appropriate SSS effect.
Arnold’s default SSS algorithm is known as cubic and is simple to control and is pretty fast. Unfortunately, it also softens out surface details a lot, which would be fine if we were trying to make a blob of jelly or something like that, but it’s not so great for skin. The new empirical mode does a better job, but we want the most detail possible so we’ll switch Diffuse > SSS > Mode to directional. The directional algorithm is slower than cubic but gives a much more detailed result as it takes the light direction into account.
If you compare the previous two images, you can see that the directional mode preserves surface detail much better, particularly in the forehead and the chin. If you look closely, you’ll also notice that there’s a tiny bit of red light between the lips in the cubic image that’s not present in the directional image – this is the light that has erroneously crossed the empty space between the lips, and directional gets rid of it automatically for us. You’ll also notice that the directional version is slightly brighter and less saturated than the cubic version, which is the single scattering component kicking in.
A good rule of thumb when tuning sss for skin is “if you can see it, it’s too strong.” Ours is currently in a pretty good place, but it could be a little stronger. We can use the light groups feature of alSurface to help tune the scattering. Switch the render view to the light group for the key light (light_group_2 in my scene), and play with the Diffuse > SSS > Distance 1 parameter until you’re happy with the amount of red light you see blurring the edges of the shadows. In this case, it’s around 1.7.
Now let’s do a high-resolution render and see what we’ve made. My final sample settings are 6/1/1/1/2 and I’ve also cranked up the subdivision iterations to 6 to get some more detail out of the displacement map.
This ends the skin shading tutorial. I hope you liked it and learned something from this. Till next time.