Procedural skin using TexturingXYZ maps in Mari

Gaël Kerchenbaum, currently working as a Creature Supervisor at Dneg, is pleased to share his complete workflow to create procedural skin.

Texturing skin used to be a tedious and difficult process. However, TexturingXYZ has opened up the doors to many new workflows. Texturing can now be achieved in a lot of different manners. 

As Surfacing Artists, we need to find ways to become more production efficient. Doing a lot of sculpting and texturing, I found myself always reproducing the same workflow when it comes to creating Skin maps. During the last two years, I created a proper setup that I could use and replicate for most of my different creatures.  

End of last year, Foundry released a new version of Mari, version 4.5, in which they added a Material node. This node allows you to quickly texture several assets, without the need of re-creating your complete nodes-tree. The latest release, version 4.6, has taken that even further and can now treat Material as Smart-Materials, by using Geo-channels. 

For this reason, I decided to transform my Skin template into a Mari Skin Material. The principle of this Material is to be fully based on Masks. In a real-life scenario, the color and quality of the skin are hugely influenced by the surface information. You want the creases, skin folds, pores, and all the macro to the micro aspect of the skin to have an influence on the color, specularity, and roughness of it. 

This tutorial is an in-depth breakdown of how I have created my own Skin template Material. It covers most of the texturing aspect and will help you to understand how to make most of your model and TexturingXYZ displacement in Mari. 

I - How to set up your Mari viewport

In order to paint our displacement in the Viewport, we want to customize a bit of our lighting scenario. You’ll need to use a shader that can properly show the volumes of your model, and put some emphasis on the surface displacement/skin pores that we will project through our texturingXYZ files. 


This tutorial can be followed within different color pipelines. I’ve decided to make it ACEScg friendly instead of using a more Legacy Linear color management. Why use ACEScg instead of Linear then? 

ACEScg allows for a better range of colors and values. It is also used in a lot of different pipelines from some of the biggest VFX houses. Putting your channels in ACEScg will mean that you have a lot of values to play with. The Gamut (range of available hue information) and the Gamma (range of available value information) are wider than in the Linear OCIO. The curves of both the Gamma are also safer to work on. This means that you’ll be less likely to burn your pixel data if you need to bring them close to one with various Adjustment nodes. The same applies to the dark values of your maps which need to be close to 0.  

In the following explanations, I will be setting my OCIO (color management) in ACEScg in order to get a better range for my grading. This tutorial can also be followed using Legacy color management. If you decide to do so, you’ll simply have to put all of your channels in Linear instead of ACEScg.

Here’s how I’m setting up my viewport: 

  • Create a new project and import model in Mari. Do not use any shading template. We will create them ourselves. 
  • Create custom lighting:
    • In lighting, start by hiding all of the lights. 
    • Activate the light_1. 
    • Switch the mode to Camera. 
    • This light is supposed to be your front light. Place it like so using the 3d manipulator.
    • Once done, switch this light to off temporarily.
    • Repeat the same process for the light_2 and light_3. They need to rim both sides of your model.
    • You can add a slight tint to the lights. Do not saturate it too much.

  • Setup Arnold surface material
    • In your main nodegraph, create an Arnold Surface Shader
    • Here are the properties of my Shader:
      • Diffuse: 1
      • Spec: 0.8 - 1
      • SpecR: 0.5 - 0.55
      • SpecIOR: 1.4
      • Coat: 0.2 - 0.4
      • CoatR: 0.2 - 0.3
      • CoatIOR: 1.4
    • These specs are meant to mimic the basic settings for a dielectric skin like material. The coat layer gives you a secondary wetter spec. 

We are now good to go with our basic viewport lighting and shader. The next step is to start populating the different channels one by one. 

II - How to paint and set up the Surface displacement

When you create a Skin material, it is important to start by creating an accurate surface. The fineDisplacement, surfaceDisplacement, tertiary or micro layer, whatever name you want to use to speak about the skin pores, is most of the time the first channel to work on. It is an extension of the sculpting. The more accurate it looks, the better your shader will behave during the surfacing stage. 

As you already know, texturingXYZ provides full-face displacement and micro packs in a 3 channel fashion. So we will also need to be able to individually control the R (height), G (bump) and B (micro) channels. 

Here’s is my workflow:

  • Create a new layer. I’m usually calling it mDisp_LR for mariDisplacement_Layer. 
  • Here are the properties of this layer:
    • Size: 4k or 8k (4k will work most of the time. Choose 8k only if you need really high res and have a good machine to work on).
    • ACEScg
    • Value: 50 
    • 16 bits
    • Scalar: On
  • Project your XYZ file on this layer using the Paint Through tool. 
  • You can use Mari’s symmetry option to project on both sides.
  • Plug your mDisp_LR into the Bump slot of your shader. Set the value of your Bump to 1.000 or above.

  • In order to extract and control R,G and B, here’s my setup:
    • Create a group, call it XYZunpack_GRP.
    • Create an input node in this group. Call it mDisp_IN. At the nodegraph top-level, plug your mDisp_LR into the mDisp_IN of this group.
    • In the XYZunpack_GRP, create a constant color, in scalar, with a mid-grey value. 
    • Create a copyChannel. You’ll need one per texturingXYZ channel that will make it to the displacement. The first one needs to extract the Red channel, the next one for the Green and the last one for the Blue. 
    • Merge all of the results on top of the midGrey_CLR node separately. 

- Now that all the channels have been isolated, we need to overlay them so they can end up in just one greyscale map. In order to do it, here’re the steps:

  • Create a Merge between the extract R and the extract G. Put the MRG in overlay mode. Disable the Colorspace management. 
  • Plug the extract R in the base and the extract G in Over. Rename your MRG into combineBump_MRG
  • Create a Merge between the combineBump_MRG and extract B; rename it combineMicro_MRG. Put this MRG in overlay mode. Disable the Colorspace management. 
  • Plug the result of combineBump_MRG in the base and the extract B in the Over of combineMicro_MRG. 
  • Plug the result of combineMicro_MRG into the output of your XYZunpack_GRP. Then, plug this group into the Bump entry of your shader.
  • To adjust the weight of the G and B channels, simply play with the Amount values of the combineBump and combineMicro nodes. 

In order to keep the important settings of your nodes available without having to deeply and consistently dive-in your groups, Mari has introduced a promote function. It can be incredibly helpful and time-saving. I’ll be doing this for every group that I’m creating, so the workflow will stay the same whatever shader slot you’re working on. 

Here’s how I’m doing it:

  • Make sure that all of your nodes are renamed properly. These names will be replicated during the promotion of the settings.
  • In order of importance, select the first node with the value you want to expose. 
  • Localize the value, and click on Promote. 
  • Do the same for the next values. 
  • Make sure to promote the values in a logical and hierarchical order. You can still go back and edit the order later using the Knobs editor if needed.
  • Rename values if needed for better clarity. 

III - Masks creation

This next step is pretty easy but really useful to control the full aspect of your albedo and spec maps. All this information will be controlled thanks to Masks. This is what Paul H. Paulino calls the “mise-en-place”. This step can be done in Mari, Substance Painter or even more easily in ZBrush. What you need to have is a black and white mask for every important area of your model. You can make them as generic as possible, using a convention that is always similar to whatever character/creature you’ll need to work on. 

These masks can be simply imported into your nodegraph as Layers, ACEScg, 8-16 bits, scalar on. The size varies between 1k to 8k depending on the amount of resolution needed. 

Here’s a nonexhaustive list of masks I’m putting in my network:


  • Curvature (Foundry has introduced a realtime node to create quick curvature. However, use it as a temp thing. You’ll then need to replace it with a raytraced baked curvature map to get an accurate result).
  • AO (can be generated automatically in Mari: Objects, select yours, right-click and generate Ambient Occlusion. You need to repeat the process for every version).
  • Thickness (not always needed)
  • EdgeDetect (not always needed)
  • Zdisp (I’m grading this one into Nuke to get a 0.5 mid-value and high pass if needed)


  • ISO_lips
  • ISO_gum
  • ISO_nose
  • ISO_nostril
  • ISO_eyeLids
  • ISO_tongue
  • ISO_ears
  • ...


  • ISO_color1
  • ISO_color2
  • ISO_color3

In order to keep this skin material as generic as possible, you can name the color-based masks by ID (color1, color2…) or you can name them with their own colors (pink, yellow, red, blue, green…). It’s up to you!

If you decide to paint them in Mari, make sure to fill them with black value. You’ll be able to paint them later when you’ll start to create your albedo primary colors. 

Once all of these ISO (isolation) masks have been loaded on a layer, connect them individually to a bakePoint node. Rename each BKP (bakePoint) with a clear name, usually with the same one as their corresponding layer. Do the same for the Geo-Channel slot of these BKP. This will allow you to instance these layers wherever you need them in your nodegraph. This really elegant way of referencing layers is available in Mari 4.6 and above. 

IV - Albedo creation - Primary colors

Let’s now jump on the Albedo creation. This map will find its base into one tileable marble texture. Then everything else will be driven by the former masks. 

  1. Marble tileable creation:
    1. -Create a procedural tileable. Call it albedo_TLD.
    2. Import your tileable marble texture. 
    3. Create a new layer, in ACEScg, scalar unchecked.
    4. Merge this layer on top of albedo_TLD.
    5. Use your imported marble texture to clean the seams. 
    6. Add an HSV after the MRG. Customize it to get a neutral but vibrant base. 
    7. Select the albedo_TLD, MRG and HSV nodes. Group them and promote settings. Rename the group albedoTileable_GRP. Keep the seams cleaning layer out of this group for the end of this tutorial.

  1. Create the primary colors

The albedoTileable_GRP will now become your input for all the different colors you want to add to your skin. In order to create the first pass, we will use the AO and the different colorID masks that have been set previously. Here’s my workflow:


  • Select your albedoTileable_GRP, add an MRG node. Connect this same group to an HSV, then connect this HSV to the Over of the MRG. Rename it darkAO_MRG.
  • Tweak the HSV to become slightly darker and more saturated.
  • On the darkAO_MRG, connect an Ambient Occlusion node to the mask input. Then, add a Brightness Lookup (BLU) between the procedural and the mask input. 
  • From now, you should be able to see the darker values happening on the external volumes of your model. We want to do the opposite. In order to do that, simply select the curve of your BLU, right-click, invert it. Then you can play a bit more with the contrast to make it happen only in the selected areas. 


  • Select your darkAO_MRG, add another MRG node after it. Connect the darkAO_MRG to an HSV, then connect this HSV to the Over of the new MRG. Rename it brightAO_MRG.
  • Tweak the HSV to become slightly brighter and less saturated.
  • On the brightAO_MRG, connect an Ambient Occlusion node to the mask input. Then, add a Brightness Lookup (BLU) between the procedural and the mask input. 
  • This time, the effect should be working already. You’ll simply need to tweak the curve of your BLU to make it happen only where you need it to. 
  • Remember to rename all of your nodes! 


  • Select your former result and connect it to a new MRG node. 
  • Create a procedural Color node. Connect it to the Over of your MRG node. Leave it as it is for now.
  • Connect your ISO_color1_LR to the Mask of your MRG node. Later, it will be replaced by an input node when we will group all the nodes apart from the ISO layer. 
  • Set your MRG node in Multiply mode. 
  • At the moment, nothing should happen. Start by selecting a value in your Color node. Make sure to keep it high, and simply change the Hue and Saturation. You can start with a reddish hue.
  • To start diffusing your color1, paint white on your ISO_color1_LR. .

  • Once done, create a new MRG node at the end of this hierarchy. Then, repeat the same process for all of your colors.
  • You can also create some temp colorID slots, to give you more control later. If the connected ISO_colorID_masks remain black, they should not impact your current albedo; however, they’ll give you extra control later on!

  • Create a group for your primary colors.
  • Share your albedo_tile channel directly into this group. You can do that by holding SHIFT + click and dragging your albedo_tile channel in your primary color group.
  • Adjust your color the way you want with some adjustment layers. I like to give more contrast with the level adjustment.
  • To create a tint, use a procedural color layer. Select the tint that you want. Put your layer on top of your shared channel in Multiply mode. The more you saturate it, the more your tileable will earn color.
  • Once satisfied, group all the nodes controlling the primary colors, except for the albedoTileable_GRP, and clean the connections and names. You can now promote the important settings. Just remember to promote them in a logical order.

Part 1 - Part 2