Texture cracking shader

I need certain stone objects in my game to display physical damage. It’s a mobile game so I am trying to find the most efficient approach. I came up with an idea to overlay a cracked texture on top of the main texture using a shader. But the shader would include a feature similar to an alpha cutout shader… This way I could have a cracked texture file showing different levels of cracking at different opacity levels. Then as the alpha cutout value is changed, so will the degree of cracking on the object.

I was wondering if anyone could help with this? I’m not even sure if it’s possible to do.

I quite amazed myself and cooked up something that seems to be working… I appear to have lost specularity but I’m sure I can fix that. If any of the more seasoned shader experts wanted to drop in an give any feedback to this noob, that would be very appreciated.



Shader "Mobile/FX/Cracking Bumped Specular" {
Properties {
	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
	_MainTex ("Base", 2D) = "white" {}
	_BumpMap ("Normalmap", 2D) = "bump" {}
	_SecondTex ("Cracks (A)", 2D) = "white" {}
	_Color ("Cracks Color", Color) = (0.5, 0.5, 0.5, 1)
	_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
}

SubShader {
	Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
	LOD 400
	
	CGPROGRAM
	#pragma surface surf BlinnPhong
	
	sampler2D _MainTex;
	sampler2D _BumpMap;
	half _Shininess;
	
	struct Input {
		float2 uv_MainTex;
		float2 uv_BumpMap;
	};
	
	void surf (Input IN, inout SurfaceOutput o) {
		fixed4 mainTex = tex2D(_MainTex, IN.uv_MainTex);
		o.Albedo = mainTex.rgb;
		o.Gloss = mainTex.a;
		o.Specular = _Shininess;
		o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
	}
	ENDCG


	// Add SecondTex on top with alpha cutoff
	CGPROGRAM
	#pragma surface surf BlinnPhong alphatest:_Cutoff
	
	sampler2D _SecondTex;
	float3 _Color;
	
	struct Input {
		float2 uv_SecondTex;
	};
	
	void surf (Input IN, inout SurfaceOutput o) {
		fixed4 c = tex2D(_SecondTex, IN.uv_SecondTex);
		o.Albedo = c.rgb * _Color;
		o.Alpha = c.a;
	}
	ENDCG
}

FallBack "Transparent/Cutout/VertexLit"
}

Nice one! There’s no need for two passes though… You can blend those textures manually inside a single pass like this:

o.Albedo = (c.a >= _Cutoff) ? c.rgb * _Color : mainTex.rgb;

Or even increase the quality and smoothen the transition practically for free:

float crackVisibility = saturate((c.a - _Cutoff) * _Sharpness);
o.Albedo = lerp(mainTex.rgb, c.rgb * _Color, crackVisibility);

where _Sharpness is in the (1, infinity) range. My guess is it would look nice at around 20.

Thanks! That seems to have improved it :slight_smile:

Shader "Mobile/FX/Cracking Bumped Specular" {
Properties {
	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
	_MainTex ("Base", 2D) = "white" {}
	_BumpMap ("Normalmap", 2D) = "bump" {}
	_SecondTex ("Cracks (A)", 2D) = "white" {}
	_Color ("Cracks Color", Color) = (0.5, 0.5, 0.5, 1)
	_Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5
}

SubShader {
	Tags {"RenderType"="Opaque" "IgnoreProjector"="True"}
	LOD 400
	
	CGPROGRAM
	#pragma surface surf BlinnPhong
	
	half _Shininess;
	sampler2D _MainTex;
	sampler2D _BumpMap;
	sampler2D _SecondTex;
	float3 _Color;
	half _Cutoff;
	
	struct Input {
		float2 uv_MainTex;
		float2 uv_SecondTex;
		float2 uv_BumpMap;
	};
	
	void surf (Input IN, inout SurfaceOutput o) {
		fixed4 mainTex = tex2D(_MainTex, IN.uv_MainTex);
		fixed4 secondTex = tex2D(_SecondTex, IN.uv_SecondTex);
		float crackVisibility = saturate((secondTex.a - _Cutoff) * 10);
		o.Albedo = lerp(mainTex.rgb, secondTex.rgb * _Color, crackVisibility);
		o.Alpha = secondTex.a;
		o.Gloss = mainTex.a;
		o.Specular = _Shininess;
		o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
	}
	ENDCG
}

FallBack "Transparent/Cutout/VertexLit"
}

I’ve added specular back into it, but it’s highlighting the cracking too. It’s not a huge deal but is there a way to exclude the cracking from the gloss?

// Overlays a texture on top of another (e.g. for showing cracks in a material) using the Cutoff property
// Uses optimizations from the Simplified Bumped Specular shader. Those differences (from regular Bumped Specular) are:
// - specular lighting directions are approximated per vertex
// - writes zero to alpha channel
// - Normalmap uses Tiling/Offset of the Base texture
// - no Deferred Lighting support
// - no Lightmap support
// - supports ONLY 1 directional light. Other lights are completely ignored.


Shader "Mobile/FX/Cracking Bumped Specular" {
Properties {
	_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
	_Color ("Color", Color) = (1, 1, 1, 1)
	_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
	_BumpMap ("Normalmap", 2D) = "bump" {}
	_SecondTex ("Cracks (A)", 2D) = "white" {}
	_CrackColor ("Cracks Color", Color) = (0.5, 0.5, 0.5, 1)
	_Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5
}

SubShader {
	Tags { "RenderType"="Opaque" }
	LOD 250
	
	CGPROGRAM
	#pragma surface surf MobileBlinnPhong exclude_path:prepass nolightmap noforwardadd halfasview novertexlights

	inline fixed4 LightingMobileBlinnPhong (SurfaceOutput s, fixed3 lightDir, fixed3 halfDir, fixed atten)
	{
		fixed diff = max (0, dot (s.Normal, lightDir));
		fixed nh = max (0, dot (s.Normal, halfDir));
		fixed spec = pow (nh, s.Specular*128) * s.Gloss;
		
		fixed4 c;
		c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten*2);
		c.a = 0.0;
		return c;
	}
	
	half _Shininess;
	sampler2D _MainTex;
	sampler2D _BumpMap;
	sampler2D _SecondTex;
	float3 _Color;
	float3 _CrackColor;
	half _Cutoff;
	
	struct Input {
		float2 uv_MainTex;
		float2 uv_SecondTex;
		float2 uv_BumpMap;
	};
	
	void surf (Input IN, inout SurfaceOutput o) {
		fixed4 mainTex = tex2D(_MainTex, IN.uv_MainTex);
		fixed4 secondTex = tex2D(_SecondTex, IN.uv_SecondTex);
		float crackVisibility = saturate((secondTex.a - _Cutoff) * 10);
		o.Albedo = lerp(mainTex.rgb * _Color, secondTex.rgb * _CrackColor, crackVisibility);
		o.Gloss = mainTex.a;
		o.Specular = _Shininess;
		o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
	}
	ENDCG
}

FallBack "Mobile/VertexLit"
}

Not sure whether this will work, but could you not use the ‘crackVisibility’ to test against.

if (crackVisibility > 0)
{
    // If there is a crack, don't add specular
    o.Specular = 0;
}
else
{
    o.Specular = _Shininess;
}

Thanks!

 o.Specular = (crackVisibility > 0) ? 2 : _Shininess;

seemed to work great :slight_smile:

Now if only I had a bit more time I could’ve made the cracks a second bump map and used that for the cracks. Would’ve looked pretty cool.

Though if you have a smooth transition for the color, why not use it for the specular as well?
o.Specular = lerp(_Shininess, 2, crackVisibility);

Just out of curiosity, if I was going to change the crack layer to a normal map and blended that to the current normal map, would that make the shader a lot more expensive?

Not much, but of course it’s not free…
You’d just lerp it the same way you do everything else, but you also have to normalize() the result, which is not exactly the cheapest operation (equivalent to dot + rsqrt + mul)… Well… or not, but the transition areas might get a little darker / brighter as a result of the normal not being normalized.
o.Normal = normalize(lerp(modelNormal, crackNormal, crackVisibility));

Ok, that’s good to know for the future.

Thanks Dolkar. Shader success!