About Me

Hi. I'm Josh Ols. Lead Graphics Developer for RUST LTD.


View Joshua Ols's profile on LinkedIn


Unity, Consoles, Shaders...


Also it seems like it would be a good idea to test in OpenGL mode on an AMD/Intel GPU. Their GLSL compilers seem especially strict, so you're likely to catch the most bugs on those platforms.



Apparently you also need to assume some of the strict assignment and swizzling rules of GLSL. Certain things like accidentally assigning a four component vector input to a three component variable will pass for Direct3D, but blow up once you hit OpenGL.



To all the people authoring shaders for Unity with the goal of having them work on all of Unity's supported platforms I say the following:

  1. The shaders are HLSL! Unity still calls them Cg for legacy reasons, but they've all but abandoned that platform. So when looking up how to do something in the shaders, look for HLSL tips!
  2. Read the header "HLSLSupport.cginc" and burn its contents into your memory. Inside you will find all the crazy preprocessor juggling that Unity has to do to hide platform and language API differences. 

Today I learned this lesson the hard way with Alloy's parallax occlusion mapping code. Basically, in Cg if you want to apply texture coordinate derivatives then you use an overloaded version of tex2D(). In HLSL, you have to use an explicit intrinsic called tex2Dgrad().

Guess which one is platform-safe in Unity's shaders? To find the answer, follow tip #2. :p


Alloy 3.1 - Asset Store Madness

It's that time again! The Unity Asset Store is having another madness sale. For this week only, you can pick up Alloy 3.1 half off at just $62.50! If you've been hesitating due to price, now's the time to jump in and grab it! XD

Alloy - Unity Asset Store


TeamColor refinements 


Not long ago, I realized that Alloy's Team Color formulation was grossly inflexible and not super intuitive. The primary reason being that if you had more than one color mask overlapping a given pixel, the top one would dominate. This presented a problem if you wanted to mix multiple colors and precisely control their contributions to the final combined color.

It looked a little something like this:

half3 tint = lerp(half3(1,1,1), aTint, masks.a);
tint = lerp(tint, rTint, masks.r);
tint = lerp(tint, gTint, masks.g);
baseColor *= lerp(tint, bTint, masks.b);


As you can see, it was layer order-dependent and made it basically impossible to have all the colors influence one fragment in a clean way. The only problem it really solved was avoiding black zones between masked colors by ensuring that the starting color was white.


New Approach

I needed something that would still prevent the black zone problem, but also allow easy blending between all the masked colors covering a given pixel. Plus, when the total weight of the masks is above 1, it needs to renormalize all the mask weights so that they total 1. Finally, when the total weight is below 1, it needs to add the color white by the remaining weight. 

So I switched to something more like this:

half weight = dot(masks, half4(1,1,1,1));
masks /= max(1, weight);
baseColor *= rTint * masks.r 
            + gTint * masks.g 
            + bTint * masks.b 
            + aTint * masks.a 
            + (1 - min(1, weight)).rrr;


This new approach does everything I need without being overly complex and/or costly.


Alloy 3 is up on the asset store!

After months and months of work it is finally here! See the Unity forum thread for details:

Alloy - Unity Asset Store


Parallax/POM + Detail Mapping Gotchas

So I'm going to make a bold assertion. We as an industry are doing texture transforms wrong! >:(

...I suppose you want context for that statement. Well then...



Let's start from the most common way to transform textures:

Texcoord = UV * Tiling + Offset;

This approach has a very serious problem in that Offset and Tiling are both implicitly coupled together. By that I mean that when the Tiling rate increases/descrease then the Offset's translation effect is decreased/increased. To compensate for this limitation, artists typically have to manually adjust the Offset value by dividing by the previous Tiling amount and then multiplying by the new amount. 

It would be far more preferable to have the two properties be decoupled, so that Offset always has a consistent translation effect. Thankfully it is pitifully easy to do this in code via:

Texcoord = (UV * Tiling) + (Offset * Tiling);

or just:

Texcoord = (UV + Offset) * Tiling;

Sadly we can't do much with this as the content creation tools do it the wrong way, so the engines also have to do it that way to be consistent. So what does knowing this really gain us?

I'll tell you...


Parallax Mapping

For those who don't know, Parallax Mapping of most varieties work by using a height map to generate a per-pixel texture coordinate offset to create the illusion of depth on flat surfaces. Now what you may not know is that this offset is implicitly multiplied by the Tiling amount of the texture coordinates used by said heightmap. So assuming your base texture uses the same texture coordinates, you can apply the parallax offset by doing the following:

Texcoord = UV * Tiling + ParallaxOffset;

Simple! Convenient! But...


Parallax + Detail Mapping

Detail mapping is a very common way to increase surface detail by combining a second set of textures into the base textures, usually at a much higher Tiling frequency. As you might imagine, combining parallax + detail mapping is a great way to make visually interesting surfaces. However, it comes with a nasty gotcha as the equation from above doesn't work for detail mapping!

The detail textures' Tiling rate will almost always be different from the base maps', meaning that the Parallax Offset will almost always translate the texture coordinates either too much or too little. This results in the rather unsightly effect of detail maps swimming across the surface as the camera changes position relative to it. So how do we fix this?

It turns out that the same fix applies as with any other offset. You just need to remove the base Tiling by dividing it out of the Parallax Offset. The resulting normalized offsets can be applied the correct way and benefit from the detail Tiling amount without any texture swimming. That looks something like this:

DetailTexcoord = (UV + (ParallaxOffset / BaseTiling)) * DetailTiling;

Any questions?