Hello,
I am trying to offset a billboards UV (U, specifically) by view direction inside the billboards shader. Currently I am able to do this, but the math is wrong somewhere.
What I am trying to do is to have the billboards U pan smoothly as you rotate the camera around it, creating a 3D-effect. If I step to the left looking at the billboard, its U pans to the right as if it had depth. This is what I have right now:
ParchedYellowFunnelweaverspider
So obviously SOMETHING happens when you change the view direction, but it isn’t a smooth continuous movement of the UVs. I believe my two main issues is figuring out what 3 values viewDir returns and what to do with the,m. In the GIF you can see some tubes on the ground which is colored based on viewDir, and with the help of them I was able to figure this out:
This is my assumption of what viewDir returns based on my experiments, I still am not sure…
With this I made this shader
Shader "Custom/viewdir" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags {
"Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True"
}
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float3 viewDir;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
// Set albedo to viewDir, each axis has its own color.
o.Albedo.r = IN.viewDir.x;
o.Albedo.b = IN.viewDir.z;
o.Albedo.g = ((1- IN.viewDir.x) * (1- IN.viewDir.z))/4;
// IN.viewDir.y is cleraly not the right angle by itself,
// above is a rough estimate... Probably entirely wrong.
float viewY = ((1- IN.viewDir.x) * (1- IN.viewDir.z))/4; // Instead of IN.viewDir.y ...
float viewDirFinal = IN.viewDir.x + viewY + IN.viewDir.z; // Should probably not be added, but then how?
o.Alpha = tex2D(_MainTex, float2(IN.uv_MainTex.x + viewDirFinal, IN.uv_MainTex.y));
// Add viewDir manipulation to UV's U-offset for 3D rotation effect.
// _MainTex is a black and white arrow for alpha.
}
ENDCG
}
FallBack "Diffuse"
}
And as you can see, viewDirFinal is where the magic is supposed too happen but there is where I’m stumped. I have tried many variations but still can’t seem to get it right. I am also quite sure my idea of what viewDir.y is supposed to be is wrong.
Anyone has any idea of how to proceed? Or am I not making myself clear?
Thanks
I made some progress:
WhisperedBruisedAsiantrumpetfish
Since it seemed to be moving in the right direction sometimes, I started experimenting with if-else statements. It’s really hard when you can’t print out the viewDir values, but as you can see I kind of got it working. It is still not entirely smooth, the panning slows down right before switching to the other if-statement. New shader below:
Shader "Custom/viewdir" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags {
"Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True"
}
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float3 viewDir;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float viewDirFinal = 0;
if( IN.viewDir.z > 0)
viewDirFinal = (IN.viewDir.x);
else
viewDirFinal = 1- (IN.viewDir.x);
// Set albedo to viewDir, each axis has its own color.
o.Albedo.r = IN.viewDir.x;
o.Albedo.b = IN.viewDir.z;
o.Alpha = tex2D(_MainTex, float2(IN.uv_MainTex.x + viewDirFinal, IN.uv_MainTex.y));
// Add viewDir manipulation to UV's U-offset for 3D rotation effect.
// _MainTex is a black and white arrow for alpha.
}
ENDCG
}
FallBack "Diffuse"
}
Stopped using the make-shift IN.viewDir.y and instead focused on only the two I knew were there. When one of the axises starts bugging out, switch to new one with if-statement. Will change this to step later for optimisation 
Still, it isn’t perfect and I still don’t know what values viewDir returns. If anyone has any idea of how to make it better or any other ideas I would be very grateful.
viewDir is a vector from the current pixel to the camera position.
It will be different for different points of your billboard, so the texture will be stretched.
What you probably need is to calculate an offset in C# (based on current camera position and middle of the billboard, for example) and use this offset in the shader.
The viewDir variable is a unit length direction vector. That is a float3 value with a length of 1 pointing in a direction. If you’re not setting the normal in the surface shader, it’s in world space. It should be exactly the same as doing normalize(_WorldSpaceCameraPos.xyz - IN.worldPos.xyz). Think of using a position on a sphere with a radius of 1 unit to give a direction. (1, 0, 0) is directly along the world space x axis, (0, 1, 0) is directly up, and (0.7071, 0.0, 0.7071) is a 45 degree angle between the world space x and z axes. This kind of value doesn’t nicely, linearly change as you rotate around something like Euler radian or degree angles might, so you’d want to convert from the vector to an angle. For your case since you presumably don’t care about the camera roll or pitch, this is quite easy.
float radianAngle = atan2(IN.viewDir.z, IN.viewDir.x); // should give you a value between -pi and pi
However, like @aleksandrk noted, using the viewDir isn’t great for this. It’d probably be better to just use the surface’s world normal.
float radianAngle = atan2(worldNormal.z, worldNormal.x);
Wow, this was exactly what I was looking for! Thanks a lot.
I’m not sure if @bgolus means to calculate worldnormal inside C# like @aleksandrk proposed, but I did it inside the shader. Right now it looks like this:
float3 worldNormal = UnityObjectToWorldNormal(o.Normal.xyz);
I’ve been told it’s faster to do in a vertex function, but I had some trouble with that so settled with doing it in the surface right now.
The float radianAngle works very well. It moves quite fast when using it however, so to counteract this I divided it with pi (3.1415). Now the texture pans more according to camera movement, it feels more like it’s a 360 radius. Is this the right way to go about it?
Also right now the biggest issue I have is that at a certain angle it seems to want to “reset” and pans really quickly for a short time. This looks pretty odd when rotating around it at a steady pace. Is it because I haven’t calculated the correct world normals?
If you’re not setting o.Normal to anything in the surface function, o.Normal already is the world normal.
All you should need is atan2(o.Normal.z, o.Normal.x)
. If you write to o.Normal (like if you’re using normal maps) then you have to pass it in another way.