Yes, it should be before the dot product. A normal map stores a tangent space normal, or a direction relative to a surface orientation. The lightDir is in world space, so you need to rotate the normal map’s tangent space normal to world space. That function generates the rotation matrix needed to do that from the world position, UVs, and optionally the mesh’s world space surface normal.

Here’s a basic extension of Unity’s built in Particles/Alpha Blended shader with support for normal mapping. It has a toggle for switching between using the mesh normals (which will break horribly on billboard particles) and calculating them in the fragment shader.

```
Shader "Particles/Alpha Blended Normal Map" {
Properties {
_TintColor ("Tint Color", Color) = (1.0,1.0,1.0,1.0)
_MainTex ("Particle Texture", 2D) = "white" {}
[Normal][NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {}
_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
[Toggle(USE_MESH_TANGENT)] _UseMeshTangent ("Use Mesh Tangent", Float) = 0.0
[Enum(Off,0,Front,1,Back,2)] _Culling ("Face Culling", Float) = 0.0
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
Cull [_Culling]
ZWrite Off
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_particles
#pragma multi_compile_fog
#pragma shader_feature USE_MESH_TANGENT
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
// toggle between the traditional transposed tangent to world matrix order
// and "direct" tangent, bitangent, normal order.
// USE_TBN is slightly faster and has identical results, but looks "wrong"
// for people used to mul(matrix, vector);
#define USE_TBN
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
// mesh normal needed for lighting and normal maps
half3 normal : NORMAL;
#ifdef USE_MESH_TANGENT
// normally needed for normal maps, but not available for particle billboards
half4 tangent : TANGENT;
#endif
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD2;
#endif
#ifdef USE_MESH_TANGENT
// tangent to world space transform
half3 tspace0 : TEXCOORD3;
half3 tspace1 : TEXCOORD4;
half3 tspace2 : TEXCOORD5;
#else // !USE_MESH_TANGENT
// used for calculating tangent to world transform
half3 normal : NORMAL;
float3 worldPos : TEXCOORD3;
#endif
};
// Unity version of http://www.thetenthplanet.de/archives/1180
float3x3 cotangent_frame( float3 normal, float3 position, float2 uv )
{
// get edge vectors of the pixel triangle
float3 dp1 = ddx( position );
float3 dp2 = ddy( position ) * _ProjectionParams.x;
float2 duv1 = ddx( uv );
float2 duv2 = ddy( uv ) * _ProjectionParams.x;
// solve the linear system
float3 dp2perp = cross( dp2, normal );
float3 dp1perp = cross( normal, dp1 );
float3 T = dp2perp * duv1.x + dp1perp * duv2.x;
float3 B = dp2perp * duv1.y + dp1perp * duv2.y;
// construct a scale-invariant frame
float invmax = rsqrt( max( dot(T,T), dot(B,B) ) );
#ifndef USE_TBN
return transpose(float3x3( T * invmax, B * invmax, normal ));
#else
return float3x3( T * invmax, B * invmax, normal );
#endif
}
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
#ifdef SOFTPARTICLES_ON
o.projPos = ComputeScreenPos (o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
#endif
o.color = v.color * _TintColor;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
#ifdef USE_MESH_TANGENT
// calculate tangent to world transform
half3 wNormal = UnityObjectToWorldNormal(v.normal);
half3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
// compute bitangent from cross product of normal and tangent
half tangentSign = v.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);
#else // ! USE_MESH_TANGENT
// calculate world position
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
// world space normal
o.normal = UnityObjectToWorldNormal(v.normal);
#endif
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
sampler2D_float _CameraDepthTexture;
float _InvFade;
half4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif
// tangent space normal map
half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.texcoord));
#ifdef USE_MESH_TANGENT
// if we calculated the tangent to world matrix in the vertex shader
half3 worldNormal;
worldNormal.x = dot(i.tspace0, tnormal);
worldNormal.y = dot(i.tspace1, tnormal);
worldNormal.z = dot(i.tspace2, tnormal);
#else // ! USE_MESH_TANGENT
// support for back face rendering
tnormal.xy *= facing;
#ifndef USE_TBN
half3x3 tspace = cotangent_frame(i.normal, i.worldPos, i.texcoord);
half3 worldNormal = mul(tspace, tnormal);
#else
half3x3 tbn = cotangent_frame(i.normal, i.worldPos, i.texcoord);
half3 worldNormal = mul(tnormal, tbn);
#endif // USE_TBN
#endif // USE_MESH_TANGENT
// normalize world normal
worldNormal = normalize(worldNormal);
// basic lambert lighting
half ndotl = dot(worldNormal, _WorldSpaceLightPos0.xyz);
half lambert = saturate(ndotl);
half3 lighting = lambert * _LightColor0.rgb;
// ambient lighting
half3 ambient = ShadeSH9(half4(worldNormal,1));
lighting += ambient;
half4 col = i.color * tex2D(_MainTex, i.texcoord);
col.rgb *= lighting;
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
}
```

I should note the above is just example code and only implements ambient and a single main directional light. It does not have support for vertex lighting or multiple per pixel lights like that video depicts.