A Guide on Overwatch materials to Source (Sfm)

Upon request to do so I’ve written up a guide on how my pseudo-pbr materials for Overwatch are done.
It will most likely be outdated in a matter of days given how often my workflow changes but that is to be expected. It’s far from perfect but at the time of writing nets me the best results.
As such don’t hold the contents of this tutorial as an ironclad set of steps that must be followed. I certainly won’t.

Still. Gets you pretty results if that’s what you’re after.


The term method in the context of this tutorial deserves a short explanation.
A method would be a general series of steps followed to get a desired result. The number afterwards represents a complete revision of the previous method that achieved more than marginally better results.
It does not increase if the results were equal, worse, or only a slight improvement. This contrived notation exist only because it was no longer feasible to refer to the number of times I’ve entirely redone individual models.
The current record for that is 114 if you’re curious.

This guide covers method 21b which is specifically for sfm. Materials for garrysmod will have their own separate tutorial at some point in time.

While method 21b is specifically for Overwatch the general workflow and principles can be applied to other conventional pbr assets. Conversions of Unreal 4 stuff for example has modelling and vmt differences but is pretty similar otherwise.
Special mention goes to this method cannot be used for Dark Souls 3 stuff. That one is something of an oddity.

In an attempt to make this guide easier to follow I’ve culled nearly all of the ‘why you do something.’ While interesting to some it lead to an increase in perceived complexity.
Doesn’t mean I will not eventually include that information in some form but it’s best I keep it separate from the guide itself.

Now for the guide itself. If you want the TL;DR here you go. Try and keep up.

I will not cover extraction. Nor the bulk of the modelling process, what little of that there is.
With the multitude of modelling programs available, as well as the ongoing updates constantly wrecking havoc with the extraction tools, it would be best to consult the dedicated Overwatch model thread for that.

That said the modelling stage still matters a great deal to us.
First order of business is to make sure you’ve retained the original vertex normals. If that is not the case you can remake the bulk of them with scripts for weighted vertex normals.
How you go about doing that depends on your tools of choice. As a max user a script exist to do it for me.
Even with the script a few verts here and there usually need manual adjustment. If you’re unsure how to adjust them properly it may be best to just use what the script gave you.

Unweighted --> Weighted

Now this is about the point shit gets weird. In the interest of full disclosure I do not know if this works for programs outside of 3ds max.

We’re going to export our model as smd (with explicit normals ticked) and then re-import it for two reasons. 1. It raises the rate of success of the next few sets and 2. we need to check if our exporter rotated all the vertex normals by 90°.
It happens. If it did happen fix it by, well, rotating them back 90°.

I would separate your model by material at this point just to make the next few steps easier. Because this is where the fun starts.
Isolate the portions of the model that are not transparent decal overlays, glass, and presumably most bits of flesh given how this will severely fuck with flexes should you include the head. Basically opaque non-fleshy bits.
Split the isolated portion further should parts of it reference a different texture than the rest (D.va’s mech has separate textures for the body and gun for example) just to slightly accelerate this next step.

Do not separate further by what parts are metals and non-metals.

For each of your diffuse for the isolated bits make 2 copies. Give them whatever suffix you want but do try and keep track of them as they are applied in a specific order.
The order goes dielectric (non metals), metallics (conductors), and then CH. That last one gets explained later.

I personally use no suffix for dielectrics, _metal for metals, and _CH for CH.

Apply your first material(s) for dielectrics and then export just your isolated bits as smd.

Apply your second material(s) for metallics and export just your isolated bits again as a different smd.

Apply your third material(s) and then export everything as yet another smd.

Since we imported from smd make sure to export with explicit normals.
We have effectively just given ourselves 3 materials for every triangle of the isolated model.

Our qc is next. It is no different save for the inclusion of multiple $model lines.

$modelname "overwatch/Characters/Mercy/caduceus_primary.mdl"

$model "Totally" "staff_ui.smd"
$model "not a" "staff1.smd"
$model "medigun" "staff2.smd"
$model "ripoff" "staff3.smd"

$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Celestial\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Mist\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Orchid\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Vendant\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Amber\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Cobalt\"
$cdmaterials "\models\overwatch\Characters\Mercy\Primary\Caduceus\Eidgenossin\"

$texturegroup "not_flesh_probably"
	{ "STF_Main_D" "STF_Main_Metal_D" "STF_Main_CH_D" }
	{ "STF_CE_Main_D" "STF_CE_Main_Metal_D" "STF_CE_Main_CH_D" }
	{ "STF_M_Main_D" "STF_M_Main_Metal_D" "STF_M_Main_CH_D" }
	{ "STF_O_Main_D" "STF_O_Main_Metal_D" "STF_O_Main_CH_D" }
	{ "STF_V_Main_D" "STF_V_Main_Metal_D" "STF_V_Main_CH_D" }
	{ "STF_CO_Main_D" "STF_CO_Main_Metal_D" "STF_CO_Main_CH_D" }
	{ "STF_A_Main_D" "STF_A_Main_Metal_D" "STF_A_Main_CH_D" }
	{ "STF_E_Main_D" "STF_E_Main_Metal_D" "STF_E_Main_CH_D" }

$surfaceprop "metal"

$illumposition 0.000 4.169 0.000

$sequence ragdoll "staff1" ACT_DIERAGDOLL 1 fps 30.00

I will fully admit to not really knowing how the bug(?) we are in the process of abusing works but the qc setup does matter.
Our ordering of the multiple smd we exported is the same in the qc. Dielectric first (staff1.smd) and CH last (staff3.smd)
staff_ui.smd is at the top of the list if only so its materials load first. It otherwise holds no relevance.

With our model compiled we now have to make our masks and materials. I feel now would be a fitting time to say welcome to hell.
Shit is about to get messy. You are responsible for keeping track of where and what things are.

First, assuming you extracted everything correctly, you will have 3-6 textures for one part of your model.
The additional can be separate ao, transparency, and/or emissive.
-The ao just gets used the same way the ao usually found in the basealpha gets used.
-Transparency just means another dielectric material for those parts of the model.
-Emissives are just applying the basetexture over them set to multiply. We can’t use selfillum with our setup so we have to color them.

Save for those cases these are what we are looking for. Basetexture, Masks, and Normal respectively.

Open up all 3 because we’re going to be moving stuff between them.

If the ao isn’t a separate texture grab it from the alpha of the basetexture. If it is separate grab the ao from one of the individual color channels of the texture. Do not just copy the image itself.
With that channel copied past it as another layer over the basetexture; and then re-paste what you still have copied in all of its color channels.

You’ll have to do that process of pasting an image in each channel of itself everytime you make a new layer from a single channel so I can only hope you’re acquainted with ctrl+c ctrl+v.

From here we’re leaving the basetexture till later. Move on to the masks.
Looks like a fucked up teal and red texture but we only need two things from it. The red channel and the green channel.
Red channel is metalness. Green channel is roughness. Ignore the blue channel entirely.

Nab the red channel, make it is own layer, and posterize it to remove artifacts. 25, 23, and 16 are the most common values that will level everything out but it really depends what you’re using it on.
If there is anything black on the metalness mask you’ll need to make another copy right about now. We’ll call that the negative.

From here adjust the levels (ctrl+L) for the metalness with 128 as the first value in the boxes for input levels. Apply it and move on.
If you had a negative apply a levels to it as well but put 127 in the last box for input levels instead.

Go back to our original texture and nab the green channel. Make it is own layer. This is the roughness. Copy it till you have 4 of them.

Just to possibly avoid redoing some steps later; put a copy of your current roughness and metalness layers on your basetexture for later.

We’re going to turn two of our roughness into our speculars; so these two will be referred to as our specular from now on.
Copy and set a metalness mask as multiply over both of our specular. Invert one of the metalness mask. If you have a negative make a copy and set one to multiply over each specular as well. Don’t invert any of our negatives.

The specular under our inverted metalness mask is now our dielectric specular. The other is our metal specular
Keep track of them.

Adjust levels of the metal specular so the first of the output values is 180. Now go to the dielectric specular and apply a curves (ctrl+m) to it.
You may want to make a preset for the value of the three points I’m going to give you:

Output   Input
23       87
61       151
136      206

By now you should have something like this.

Go ahead and merge the metalness (and possibly negative) down on to their respective specular. I hope you aren’t lost because we still have a ways to go.

Grab a channel of the metallic specular and toss it in the alpha of the texture you’re currently editing. That is for later.
Now for another weird step. Set the blue channel of your metal specular to a uniform value of 20 and then save the metal specular as a separate image file. This new yellow and slightly blue texture is our CH.

Now we need to grab a channel of our dielectric specular and put it in the alpha of the normalmap. While you’re there make sure the normalmap is not inverted.
Find something roundish on the normalmap. Red highlights should point right and green highlights should run clockwise to the red ones. If green runs counter-clockwise select the green channel and hit ctrl+i.
Save as tga. 32bit to keep the alpha and close the normalmap. We don’t need to edit it anymore.

Back to the masks image file go find one of the roughness you haven’t touched yet. We’ll refer to this one as the exponent. Apply a different curves preset to it.

Output   Input
7        93
31       147
125      212

Now go to the green channel of the exponent and give it a solid fill of 235. Duplicate your exponent so there is one layer atop the other.
Give the exponent on top a layer mask (have it selected and hit the little rectangle with a circle in it at the bottom of the layer list) and then copy our leftover metalness.

Select the top exponent again, go to channels, and paste the metalness in the layer mask. Now select your bottom exponent and fill the green channel of that one with 128.

You should have something like this. Yours is probably way more green but that doesn’t matter.

Save your exponent as 32bit tga like the normal. We need the alpha intact.

Back to the base texture finally. You should have 4 layers on it. The basetexture, the ao, the metalness, and the roughness.
If that is not the case solve it now.

Relatively simple set of steps. Invert the metalness and set it to multiply over your ao.
Select the roughness and give it levels with an output of 100 in the far right box. Invert your roughness and set it to multiply on top of your metal and ao.

Should have this

Another unusual step. Merge the metalness, roughness, and ao together. Grab one of the color channels that results and put it in the basealpha. Then invert that alpha.
This will be our paint mask. Clear the basetextrue of all but the background layer so aside from the alpha it looks like we didn’t edit it at all.
Save once again as 32 bit tga and close.

To recap. Our textures should now be a new basetexture, an exponent, a new normalmap, and a CH mask. Plus any additional textures like illum stuff.
We get one more as a result of saving to vtf.

Save your basetexture, exponent, and normalmap as BGRA8888. I mean you can save it in a dxt format if you want severe artifacting everywhere; but given how you’re evidently reading this for an increase in visual quality that would run counter to your goal.
Save your CH as BGR888.
Now save your exponent again as a different texture in the A8 format. This is our alternate specular so let us just refer to it as the alt.

So you know have a basetexture, exponent, normal, CH, alt, and possibly emissive vtf.
Rejoice for the rest of the work is pretty much done for you from this point on.

Remember your materials from earlier on? Your dielectric, metallic, and CH?
Make some vmt for each of them. Well here’s what you fill each of them with. Adjust filepaths as need be.


	"$basetexture" "*Basetexture*"
	"$bumpmap" "*Normal*"

	"$color2"	"[0 0 0]"
	"$blendTintByBaseAlpha"	"1"

	"$phongexponenttexture" "*Exponent*"

	"$phongalbedotint" 1
	"$phong" "1"
	"$phongboost"	"0.9986979"

	"$phongfresnelranges"	"[2 10 22.5]"

	"$envmap" "models\Overwatch\shared\painted_silver"
	"$normalmapalphaenvmapmask"	"1"
	"$envmaptint"	"[0.01 0.01 0.01]"
	"$envmapfresnel"	"1"

The envmap can be whatever you want. I just really like how ambiguous painted silver is in regards to dither.


	"$basetexture" "*Alt*"
	"$bumpmap" "*Normal*"

	"$detail" "*Basetexture*"

	"$detailblendfactor" "1"
 	"$detailblendmode" "0"
	"$detailscale" "1"

	"$color2"			"[0 0 0]"

	"$additive"	"1"

	"$phongexponenttexture" "*Exponent*"

	"$phongalbedotint" 1
	"$phong" "1"
	"$phongboost"	"2.465625"
	"$phongfresnelranges"	"[1 1.05 1.25]"

	"$BasemapAlphaPhongMask"	"1"

//     If you have an emissive include the following as well

	$emissiveBlendEnabled 1
	$emissiveBlendTexture	"dev/null"
	$emissiveBlendBaseTexture	"*Emissive*"
	$emissiveBlendFlowTexture	"dev/null"
	$emissiveBlendStrength 1
	$emissiveBlendScrollVector "[0 0]"

The $emissiveBlendTexture and $emissiveBlendFlowTexture are just blank white squares. They shouldn’t be anything else for what we’re using emissiveblend for.
Special mention goes to. $Emissiveblend has what I call ‘The detail lighting bug.’ What this means is the effect will apply multiple times directly proportional to the number of sfm lights touching the model +1.

Can be quite nasty. Watch for that.


	"$baseTexture"	"*Basetexture*"
	"$normalmap"	"*Normal*"

	"$maskmap1" 	"models\Overwatch\shared\masks1"
	"$maskmap2" 	"*CH*"

	"$nocull" "0"
	"$additive" "1"

	"$specularexponent"		"20"
	"$specularscale"		 	"0"
	"$specularcolor" 			"[.8 1 1]"
	"$rimlightcolor" 			"[.8 1 1]"

	"$rimlightscale" 			"0"
	"$ambientscale"			"0"

	"$envmap"	"models\Overwatch\shared\painted_silver"
	"$maskenvbymetalness" "0"

	"$envmapintensity"		"0.035"

Once again envmap can be whatever you want.

I’m not going to explain all but a little of what this shader and its commands are. For the way we’re using it you really don’t need to know.
$maskmap1 is just a 2 by 2 square with red 0, green 255, blue 255, and alpha 0. Alpha must be 0 or you have fucked up.

One is included later anyway so you can just steal that.

If you mess up with any of the vmt have fun trying to figure out which one has the error. Consider use of $no_draw 1 to try and figure that out.

Everything should now be set up to do the following:
This is the albedo just for reference. Our color information.

This is our dielectric material.

This is our metallic material.

This is our CH material.

And with all of them combined this is our final product.

Or at least it would be. The intensity of the CH envmap was set to default for showing purposes. This is our actual final product.
There’s a reason it looks black but that will be explained in a little bit.

So with that you’re pretty much done. If you can believe it this only takes 4-6 minutes.
Should still continue on a little bit so you understand just how to work with what you’ve made. It will have some quirks unique to models using a particular shader.

Since the model viewer is especially poor for previewing stuff that relies on the environment around it for illumination toss them on a map and throw in a few lights to test.

Now those are some nice looking models. Although we’re not quite done yet.

Allow me to do the introduction of the Customhero shader and then tell you the only things you’re going to need to know about it.
I’m going to hazard a guess that all save for a very small handful of you reading this even know what Customhero is. For the uninitiated; it is Dota2’s primary model shader next to the brush/model hybrid Globallitsimple.

Both are present in the sfm and both are broken beyond repair. Globallitsimple is trash and should never be used. Customhero for its conventional use will only operate on very specific maps and even then looks like garbage given how poorly implemented it is.
With that in mind we are not using Customhero conventionally. We are also not making these for garrysmod as Customhero does not exist in that engine branch.

You can get overwatch stuff to work with method 20b for garrysmod but that requires a separate but similar guide to this one.

In anycase we are using customhero exclusively for how it interacts with cubemaps as well as the sfm’s ambientocclusion. Advantages and disadvantages over Vertexlitgeneric are:

  • Interaction with albedotint. About god damn time.
  • Incompatible with sfm’s ambientocclusion. That much closer to obeying the laws of physics.
  • There is no express int or float envmaptint command.
  • ‘The detail lighting bug’ is present for envmaps.

That bug is something you must absolutely keep in mind. We’ve effectively lost control of how intense our envmap is; which doesn’t entirely help when we’re aiming for accuracy.
If you want to know why the envmap intensity was not set to 1 this is why:


Trust valve to somehow fuck up environmentmaps every time I guess.
As a fallback I set envmap intensity to 0.035 which works in dark environments to environments with 8-11 lights. Anymore and it will often be brighter than the environment around it.

With no sfm lights present or lighting disabled entirely the actual intensity of the cubemap shows up. Like so:


A workaround is either editing the vmt directly or using override materials to just change the intensity on the fly. Customhero has its own command for envmapintensity (it’s $envmapintensity, shocker I know) so it’s only an issue assuming you don’t know what you’re working with.

So that’s method 21. As a reward for making it this far I’ve supplied the caduceus shown (as well as gun) for reference, proof of concept, as well as something to play around with.
Grab it here. Sfm only just fwi.

If you have any questions, such as “Jesus christ what the fuck,” feel free to ask.

Jesus christ what the fuck

Before I start to disable lights again in the gif, there are 9 actively affecting the model. Might the alpha channel of CH Mask 1 be used for selfillum instead of emissiveblend? It doesn’t seem to carry the same issue with multiplying intensity. To avoid adding the color onto itself, I figure one could add the glowmask onto the alpha channel of the diffuse, resulting in it being blacked out from the basetexture.

It’s a viable option. Both emissiveblend and customhero selfillum ignore ao. The tradeoff is now you have no control over intensity. Fix one issue cause another.
Customhero selfillum can’t really get bright. If we were using customhero’s specular it would actually darken the selfillum rather than add to it.
Whichever you prefer honestly. I prefer bloom 4 dayz.

As to your point on avoiding adding the color onto itself with illumination that would sort of violate the laws of physics. A surface that glows still has diffuse from external sources. The glow IS diffuse just from a difference source.

Thanks for putting this guide together - I’ve got to try this! Have you developed a system for characters’ hair?

Sort of but it consumed too much memory to be viable; and only works from certain angles. Anisotopic mask are probably one of the few things source cannot fake.

“$color2” “[0 0 0]”
“$blendTintByBaseAlpha” “1”
Makes my dielectric material unnaturally dark.

Why are we using these lines? For AO? Yeah, um… it’s perhaps a bit too zealous.

did you invert it beforehand

im pretty sure…

[editline]5th July 2017[/editline]

there is my diffuse vtf

without $color2

with $color2

You did not split the metallic at 128 onward by the looks of it.
Any mistakes during the texture conversion phase are usually irreversible and you must start over.

BTW I spend the last couple hours automating this entire process in photoshop so now it completes in about 8 seconds. I’ll share this tomorrow.
I believe it follows your instructions 100%. It works amazingly well after a little bit of setup that makes sure it starts properly.

[editline]6th July 2017[/editline]

I think that perhaps AO maps should be included differently, at the moment they seem to be over zealous so I turn up the $color2 values to compensate.

This will be a godsend if it works as intended.

Here is the macro for Photoshop, it’s just an action script.

Here is a video tutorial on how to use it.

Now if you want to use negatives, they were made and can be found in the work document (the first one that’s left over). You can manually multiply them into what you’d need and work up from there, but most the time you don’t need to worry about those. I’ll make a version of the macro that does that part later.

Let me know about bugs.

If you want to add this to the OP, that might be helpful to others going through it.

[editline]6th July 2017[/editline]

By the way, you can download the latest version of Photoshop and install the VTF plugin, so long as you grab the 32 bit version. You’ll also need to make sure the Nvidia tools are installed so you can manipulate dds files.


For textures smaller than 2048x2048 you need to make sure they are that size exept the met roughness which needs to be 1024x1024. ill fix this quirk later

Will there be a Gmod version, or can you do the same thing?

I really hate to do this but you may have to end up remaking that. Multiple times probably.

What you automated is 21b. Is almost a year old. It’s also specifically for overwatch so automating it prevents you from using it for anything outside of that like unreal4.
It works. Just not as well as anything that came after it. Currently the most recent is 29.

I don’t up the version when I do some slight refinement of values. I do it when I revise everything to work around some glaring issue.

For example. These two shots of the model viewer are using literally identical parameters and textures between them. The only difference is the order I layered them.
Because source.


We want the one on the right (28). 27Δ on the left, despite technically being the same, is not as physically accurate. 21b gives us a worse version of 27Δ.

I was planning on updating or outright re-writing the guide once I finished up 30 so it also included a separate guide for specular-pbr to source. I just haven’t gotten there yet.

I can update it when I can read an updated guide.

[editline]6th July 2017[/editline]

I’d like to know how that is done :o

-snip, don’t mind me, I’ll just ask BlueFlytrap myself-