Often Normal MapsA type of Bump Map texture that allows you to add surface detail such as bumps, grooves, and scratches to a model which catch the light as if they are represented by real geometry.
See in Glossary are used to create additional detail on objects, without creating additional geometry. Let’s see how to make a shaderA program that runs on the GPU. More info
See in Glossary that reflects the environment, with a normal map texture.
Now the math is starting to get really involved, so we’ll do it in a few steps. In the shader above, the reflection
direction was computed per-vertex (in the vertex shader), and the fragment shader was only doing the reflection
probe cubemapA collection of six square textures that can represent the reflections in an environment or the skybox drawn behind your geometry. The six squares form the faces of an imaginary cube that surrounds an object; each face represents the view along the directions of the world axes (up, down, left, right, forward and back). More info
See in Glossary lookup. However once we start using normal maps, the surface normal itself needs to be calculated on a per-pixel basis, which means we also have to compute how the environment is reflected per-pixel!
We’ll have to learn a new thing now too; the so-called “tangent space”. Normal map textures are most often expressed in a coordinate space that can be thought of as “following the surface” of the model. In our shader, we will need to to know the tangent space basis vectors, read the normal vector from the texture, transform it into world space, and then do all the math from the above shader. Let’s get to it!
Shader "Unlit/SkyReflection Per Pixel"
{
Properties {
// normal map texture on the material,
// default to dummy "flat surface" normalmap
_BumpMap("Normal Map", 2D) = "bump" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float3 worldPos : TEXCOORD0;
// these three vectors will hold a 3x3 rotation matrix
// that transforms from tangent to world space
half3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x
half3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y
half3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z
// texture coordinate for the normal map
float2 uv : TEXCOORD4;
float4 pos : SV_POSITION;
};
// vertex shader now also needs a per-vertex tangent vector.
// in Unity tangents are 4D vectors, with the .w component used to
// indicate direction of the bitangent vector.
// we also need the texture coordinate.
v2f vert (float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
{
v2f o;
o.pos = UnityObjectToClipPos(vertex);
o.worldPos = mul(_Object2World, vertex).xyz;
half3 wNormal = UnityObjectToWorldNormal(normal);
half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
// compute bitangent from cross product of normal and tangent
half tangentSign = tangent.w * unity_WorldTransformParams.w;
half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
// output the tangent space matrix
o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
o.uv = uv;
return o;
}
// normal map texture from shader properties
sampler2D _BumpMap;
fixed4 frag (v2f i) : SV_Target
{
// sample the normal map, and decode from the Unity encoding
half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
// transform normal from tangent to world space
half3 worldNormal;
worldNormal.x = dot(i.tspace0, tnormal);
worldNormal.y = dot(i.tspace1, tnormal);
worldNormal.z = dot(i.tspace2, tnormal);
// rest the same as in previous shader
half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
half3 worldRefl = reflect(-worldViewDir, worldNormal);
half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
fixed4 c = 0;
c.rgb = skyColor;
return c;
}
ENDCG
}
}
}
Phew, that was quite involved. But look, normal mapped reflections!