Surface Shader: Brick Texture problem

Hi guys. I’ve been following along with the book Texturing & Modelling: A Procedural Approach and doing the exercises in unity. I’m doing the shaders in the first chapter. First is a shader that procedurally generates a brick wall texture. The 2nd modifies this shader to add in some procedurally generated bump mapping. Anyway, the code that generates the brick pattern works fine, but the shading is broken. First, here is the code from the book for this shader:

Brick Texture (Bump mapped)

The diffuse lighting calculation at the bottom of that code is a bit confusing at first, but I found some info on what some of the variables are that I didn’t know yet (such as Ci and Oi).

Variables Info

Anyway, this code uses the Renderman shading language, but is similar enough I’ve been able to get it mostly working in Unity aside from this very stubborn shading problem. Some functions built into Shaderman are implemented in an include file so my shader can work in spite of the fact that it isn’t using Renderman. Here is my code for this surface shader.

Shader "Custom/Ch01/Brick (Bump Mapped)" 
{
	Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0

		
		_BrickWidth ("Brick Width", Range(0.0, 1.0)) = 0.25
		_BrickHeight ("Brick Height", Range(0.0, 1.0)) = 0.08
		_MortarThickness ("Mortar Thickness", Range(0.0, 1.0)) = 0.01

		_BrickColor ("Brick Color", Color) = (0.5, 0.15, 0.14)
		_MortarColor ("Mortar Color", Color) = (0.5, 0.5, 0.5)
		
	}
	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM

		// Include our Renderman Functions. Renderman is a rendering language created and used by PIXAR Studios.
		#include "Assets/RenderManFuncs.cginc"

		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input 
		{
			float3 posWorld;
			float3 normal;
			float2 uv_MainTex;
		};

		uniform half _Glossiness;
		uniform half _Metallic;
		uniform fixed4 _Color;
		
		uniform fixed _BrickWidth;
		uniform fixed _BrickHeight;
		uniform fixed _MortarThickness;
		uniform fixed4 _BrickColor;
		uniform fixed4 _MortarColor;
		

		void vert (inout appdata_full v, out Input data)
		{
			UNITY_INITIALIZE_OUTPUT(Input, data);
			data.posWorld = mul(UNITY_MATRIX_MVP, v.vertex);
			data.normal = normalize(mul(v.normal, _Object2World));
		}

		void surf (Input IN, inout SurfaceOutputStandard o) 
		{
		
			// Calculate height and width of a single brick, including half the mortar on each side of the brick.
			half bmWidth = _BrickWidth + _MortarThickness;
			half bmHeight = _BrickHeight + _MortarThickness;

			// Calculate the thickness of half the morter on each side of the brick. This single brick is our repeating patterin in this shader that is used to texture a surface.
			half mortarWidth = _MortarThickness * 0.5 / bmWidth;
			half mortarHeight = _MortarThickness * 0.5 / bmHeight;
			
			float ss, tt, sbrick, tbrick, sbump, tbump, stbump, w, h;
			float sCoord = IN.uv_MainTex.x;
			float tCoord = IN.uv_MainTex.y;

			ss = sCoord / bmWidth;
			tt = tCoord / bmHeight;
			
			if (mod(tt * 0.5, 1) > 0.5)
				ss += 0.5; // Shift every other course of bricks by 0.5 so the bricks will look like normal staggered bricks.

			// Figure out which brick we are on.
			sbrick = floor(ss);
			tbrick = floor(tt);
			ss -= sbrick;
			tt -= tbrick;

			w = step(mortarWidth, ss) - step(1 - mortarWidth, ss);
			h = step(mortarHeight, tt) - step(1 - mortarHeight, tt);


			// Compute bump-mapping function for mortar grooves
			sbump = smoothstep(0, mortarWidth, ss) - smoothstep(1 - mortarWidth, 1, ss);
			tbump = smoothstep(0, mortarHeight, tt) - smoothstep(1 - mortarHeight, 1, tt);
			stbump = sbump * tbump;



			// Calculate normal direction
			float3 pos = float3(sCoord, tCoord, IN.posWorld.z);
			pos = mul(float4(pos, 1.0), _Object2World).xyz;
			float3 I = normalize(_WorldSpaceCameraPos.xyz - pos.xyz); // Get the view direction.
			float3 normalDirection = calculatenormal(pos + normalize(IN.normal) * stbump);
			normalDirection = normalize(faceforward(normalDirection, I));


			fixed4 texColor = mix(_MortarColor, _BrickColor, w * h); // Calculate texture color for this point on the surface.

			float atten = 1.0;
			fixed3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
			fixed3 diffuseReflection = atten * _LightColor0.xyz * saturate( dot(normalDirection, lightDirection));
			fixed3 lightFinal = diffuseReflection + UNITY_LIGHTMODEL_AMBIENT.xyz;

			
			fixed4 c = fixed4(texColor * lightFinal * _Color.xyz, 1.0);

			// Albedo comes from a texture tinted by color
			//fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
			
		}
		ENDCG
	} 
	//FallBack "Diffuse"
}

I have attached a screenshot so you can see what’s happening.

As you can see, I have created a simple plane and applied my surface shader to it. It does indeed make the plane look like it is made of brick, but clearly the texture is far too dark, and there is no shading on the mortar lines like there is supposed to be. There is a directional light sitting above the plane and the light is not disabled.

I have determined what I think is part of the problem. I think part of the problem involves the line that calculates the diffuse lighting.

fixed3 diffuseReflection = atten * _LightColor0.xyz * saturate( dot(normalDirection, lightDirection));

It seems that the dot product in this line is always returning 0 for some reason. This would result in an all black texture on the plane, if not for the following line that adds ambient light onto the result of this line.

I don’t know what is causing the problem and have been trying hard to find it. I just cannot get it to work. By the way, the diffuse lighting code is from the Noob To Pro Unity Shader Writing series on cgcookie.com.

Noob to Pro Unity Shader writing tutorial series

One more thing to show you. Here is the code in the calculatenormal() and faceforward() functions as they are currently implemented in the include file just in case there might be an issue there too.

    float3 calculatenormal(float3 p)
    {
    	return cross(ddx(p),  ddy(p));
    }
    
    
    float3 faceforward(float3 normal, float3 incidentRay)
    {
    	if (dot(normal, incidentRay) >= 0)
    		return -normal;
    	else
    		return normal;
    }

I really hope someone can help me solve this problem because it’s driving me crazy! lol I’m overlooking something or doing something wrong somewhere, but the question is where. Any help would be greatly appreciated! Thanks!

Did you manage to get anywhere with this? Same situation myself. (Sorry for the answer - it wont let me comment).

@sosh
I haven’t touched this in a long time, but I found the project and started messing with it today after seeing a notification in my email about your comment. I have discovered a couple of things. If I omit the call to faceforward() on line 105 in the shader itself and simply normalize it instead, then I can see some output from the mortar code. It’s not correct as the transition is sudden between a lighter and darker shade of gray. That’s not really what you expect out of smoothstep(). I suspect the call to faceforward() is not the problem, though.

When I first opened the project, the shader looked normal lighting-wise. So I looked through the shader code and compared it to the code above. It turns out that this was because on line 112 in the shader, there was a - in front of LightDirection, so it was being inverted. This makes the surface appear lighter. It must’ve been something I was messing with last time I touched this project. So I changed it back to match the code above.

I’ve also observed that this shader ignores shadows. If I place a cube on top of the plane, no shadow appears. Another strange issue is that if I rotate the camera around and look at the plane from different angles, it seems that when the camera looks at the plane from an angle that is from the opposite direction the light is coming from, the whole plane changes brightness suddenly. This only happens when you have the call to faceforward() omitted and simply normalize that normal value instead.

From what I can tell right now, it seems the problem is with the normals. I have been doing some debugging by changing line 120 to set the albedo to other values. I set it to the normalDirection but I got a pure white plane. So it appears that there is a problem with the normals, since obviously the normal should not be the same for every point on the surface… I haven’t figured out what is causing this yet, though. It may be that this one thing is messing up everything else. Maybe we can figure this out yet, lol.

Normally it would make sense for all of the normals to point straight up since its a flat plane. In this case they shouldn’t since we’re perturbing them with the stBump value for the mortar bump mapping. So the mortar code is not working correctly it seems. This still doesn’t explain the lighting being too dark, though.