I would like to use object-space normals in my project, but there isn’t anything about tangents whatsoever in the default shaders. I assume the tangent solver is a built-in function inside Unity. What would you have to do to, make an object use object-space normals?
The reason I want to use object space normals is because it would remove smoothing issues.
There is a object-space normal map shader in the asset store, but it only works in DX11. Which is useless and puzzling to me since object-space normal maps are faster.
The best advice I have gotten through searching the forums is to write my own shader. But this seems a bit over my head.
By default, the surface shaders will work in object space only if o.Normal is not written to. As soon as you write to o.Normal, they will work in tangent space (assuming that o.Normal is a tangent space normal map).
So you can put together a custom SurfaceOutput and give it a float3 objNormal and pass your object space normal into that. Then you can continue on as per normal, using s.objNormal rather than o.Normal whenever you want to sample the surface normal.
Remember not to store your object space normal map as “Normal” type. You’ll need to store it as “Advanced” type and tick “Bypass sRGB Sampling”.
Here’s a really simple example;
Shader "Object Space Normal Map" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
_ObjMap ("Object Space Normal (RGB)", 2D) = "bump" {}
_Cutoff ("Alpha Cut-Off Threshold", Range(0,1)) = 0.5
}
SubShader{
Tags { "RenderType" = "TransparentCutout" }
CGPROGRAM
#pragma surface surf ObjNormal
// Custom struct for surface output, I've added my own ObjNormal variable at the end.
struct SurfaceOutputObjNormal {
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed Alpha;
fixed Specular;
fixed Gloss;
fixed3 ObjNormal;
};
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex, _ObjMap;
float _Cutoff;
void surf (Input IN, inout SurfaceOutputObjNormal o) // Here I've given it SurfaceOutputObjNormal rather than the standard SurfaceOutput.
{
fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = albedo.rgb;
o.Alpha = albedo.a;
o.ObjNormal = tex2D(_ObjMap, IN.uv_MainTex).rgb * 2 - 1;
// NOTE: You cannot write to o.Normal here, otherwise the Surface Shader will switch to tangent space mode.
}
inline fixed4 LightingObjNormal (SurfaceOutputObjNormal s, fixed3 lightDir, fixed3 viewDir, fixed atten) // Here I've given it SurfaceOutputObjNormal rather than the standard SurfaceOutput.
{
clip(s.Alpha - _Cutoff);
viewDir = normalize(viewDir);
lightDir = normalize(lightDir);
s.ObjNormal = normalize(s.ObjNormal);
float NdotL = saturate ( dot(s.ObjNormal, lightDir) ); // All your lighting should be calculated using s.ObjNormal rather than s.Normal.
fixed4 c;
c.rgb = s.Albedo * NdotL * _LightColor0.rgb * atten * 2;
c.a = 1.0;
return c;
}
ENDCG
}
FallBack "Transparent/Cutout/VertexLit"
}
Thank you very much for your help Farfarer. It almost works from some angles, and from some, not so much. And no self shadowing occurs either. I thought it was my normal map at first, but I have inverted each channel of the map individually and it only changes the angles from where it almost works. I used the default settings (minus tangents) in Xnormal to bake the object space normal map.
Yeah now it works in object space; I can rotate the object and the lighting changes. Yet it still only works from some directions.
In fact, upon further inspection it appears that the object lights up completely with a slightly higher lighting hotspot in the direction of the light if the light is in a certain position. And in another position it becomes completely black, save for the slight hotspot nearest to the light.
It shouldn’t matter actually, I’ve not given it a _PrePass function so it won’t be able to do anything for deferred anyway.
I’ll have a prod and see what’s up, though.
EDIT: ARGH, I’m an idiot, I’ve not uncompressed the object map to -1-1 range, it’s still in 0-1. I’ve updated the original shader code to properly unpack it. Hopefully that should work once you get your +/- X/Y/Z to match Unity’s coordinate system.
It works flawlessly now apart from the deferred part. I can’t thank you enough Farfarer.
By the way, do you recommend any other documents or tutorials apart from nvidia’s Cg tutorial to get into writing shaders? I figured I needed to start to really learn this thoroughly.
Uhm, I’m not sure. Most of what I’ve learned is from reading the source from the built-in shaders and general googling of bits and pieces.
Before that I was playing a lot with node-based shader editors. That helped me get the hang of what all the functions do in a bit of a friendlier environment, then I could just write them as code.
@Farfarer - Thank you for you answers here, I was struggling with this also.
Can you elaborate more on how to get it to work in deferred mode?
It seems to not interact with the lights when I try and create a transparent bumped - specular shader.