Search Unity

Question Billboard shader with sprite outlines and rotation around z-axis (can't get rotation to work)

Discussion in 'Shaders' started by tomtefar, Apr 5, 2022.

  1. tomtefar

    tomtefar

    Joined:
    Mar 19, 2015
    Posts:
    6
    TL;DR: Have billboard shader for sprite. Want to be able to rotate sprite around its own local z-axis while still facing camera correctly.

    Hi!

    I'm trying to put together a custom billboard sprite shader with outlines for my arcade kart racing game.

    I'm not new to programming but shaders and matrices are just beyond me. I've managed to merge and modify the following two shaders for billboards and outlines respectively into something that looks pretty much like I want it to:

    https://en.wikibooks.org/wiki/Cg_Programming/Unity/Billboards
    https://www.ronja-tutorials.com/post/049-sprite-outlines/

    However, I also want to be able to have the sprite rotate around its Z-axis while always facing the camera (see picture. It's for visualising the driver "tumbling" inside the car when hit by a weapon).

    billboard_sprite_outline_w_arrows.png

    I found multiple posts for similar problems with different solutions (https://forum.unity.com/threads/billboard-with-single-axis-rotation.533501/ https://www.reddit.com/r/Unity3D/comments/ahqbod/a_billboard_sprite_shader_in_only_one_axis/ etc) but can't wrap my head around it and get it to work with my shader. Having spent the better part of the weekend and today on this I feel it's time to let it rest for a bit and ask for help.

    I've managed to get the sprite rotating by supplying the object transform's z-rotation via C# script but would rather get the rotation directly in the shader but haven't figured out how to properly access that info from the available matrices.

    Any help or push in the right direction would be super appreciated.

    I realize I can achieve the effect easily without a special shader (and previously did) but I'm currently adding split screen multiplayer and want to be able to have the same sprite facing all cameras in the scene instead of having separate sprites for each camera. I hope that makes sense.

    Here's my code.

    Code (CSharp):
    1. Shader "BillboardOutlineZRotation"
    2. {
    3.     Properties
    4.     {
    5.         // Billboard stuff
    6.         _MainTex ("Texture Image", 2D) = "white" {}
    7.         _ScaleX ("Scale X", Float) = 1.0
    8.         _ScaleY ("Scale Y", Float) = 1.0
    9.         _RotationZ ("Rotation Z", Float) = 1.0 // set via C# script
    10.  
    11.         // Outline stuff
    12.         _Color ("Tint", Color) = (0, 0, 0, 1)
    13.         _OutlineColor ("OutlineColor", Color) = (1, 1, 1, 1)
    14.         _OutlineWidth ("OutlineWidth", Range(0, 1)) = 0.5
    15.     }
    16.     SubShader
    17.     {
    18.         Tags {
    19.             "Queue" = "Transparent"
    20.             "IgnoreProjector" = "True"
    21.             "RenderType" = "Transparent"
    22.             "DisableBatching" = "True"
    23.         }
    24.  
    25.         ZWrite Off
    26.         Blend SrcAlpha OneMinusSrcAlpha
    27.         Cull Off
    28.  
    29.         Pass
    30.         {
    31.             CGPROGRAM
    32.  
    33.             #include "UnityCG.cginc"
    34.  
    35.             #pragma vertex vert
    36.             #pragma fragment frag
    37.  
    38.             // User-specified uniforms  
    39.             uniform sampler2D _MainTex;
    40.  
    41.             // Billboard stuff
    42.             uniform float _ScaleX;
    43.             uniform float _ScaleY;
    44.  
    45.             // z-rotation
    46.             uniform float _RotationZ;
    47.  
    48.             // Outline stuff
    49.             float4 _MainTex_ST;
    50.             float4 _MainTex_TexelSize;
    51.             fixed4 _Color;
    52.             fixed4 _OutlineColor;
    53.             float _OutlineWidth;
    54.  
    55.             struct vertexInput {
    56.                 // Billboard stuff
    57.                 float4 vertex : POSITION;
    58.                 float2 tex : TEXCOORD0;
    59.  
    60.                 // Outline stuff
    61.                 fixed4 color : COLOR;
    62.             };
    63.             struct vertexOutput {
    64.                 // Shared stuff
    65.                 float4 pos : SV_POSITION;
    66.                 float2 tex : TEXCOORD0;
    67.      
    68.                 // Outline stuff
    69.                 fixed4 color : COLOR;
    70.                 float3 worldPos : TEXCOORD1;
    71.             };
    72.  
    73.             // For rotating the billboard around z-axis
    74.             float4 RotateAroundZInDegrees (float4 vertex, float degrees)
    75.             {
    76.                 float alpha = degrees * UNITY_PI / 180.0;
    77.                 float sina, cosa;
    78.                 sincos(alpha, sina, cosa);
    79.                 float2x2 m = float2x2(cosa, -sina, sina, cosa);
    80.                 return float4(mul(m, vertex.xy), vertex.z, vertex.w).xyzw;
    81.             }
    82.  
    83.             vertexOutput vert(vertexInput input)
    84.             {
    85.                 vertexOutput output;
    86.      
    87.                 // Billboard stuff, make shader use game object scale
    88.                 float4 scale = float4(
    89.                 length(unity_ObjectToWorld._m00_m10_m20),
    90.                 length(unity_ObjectToWorld._m01_m11_m21),
    91.                 length(unity_ObjectToWorld._m02_m12_m22),
    92.                 1.0
    93.                 );
    94.  
    95.                 // float4 rotationZ = float4(unity_ObjectToWorld._m02_m12_m22, 0);
    96.  
    97.                 // object's forward vector (local +Z direction)
    98.                 // float3 objWorldForward = unity_ObjectToWorld._m01_m11_m21;
    99.                 // get objects current Y rotation from its rotation matrix in radians
    100.                 // float objWorldHeading = atan2(objWorldForward.z, objWorldForward.x);
    101.  
    102.                 // Outline stuff
    103.                 output.worldPos = mul(unity_ObjectToWorld, input.vertex);
    104.                 output.tex = TRANSFORM_TEX(input.tex, _MainTex);
    105.                 output.color = input.color;
    106.  
    107.                 // Billboard stuff
    108.                 output.pos = mul(UNITY_MATRIX_P,
    109.                 float4(UnityObjectToViewPos(float4(0.0, 0.0, 0.0, 1.0)), 1.0)
    110.                 + RotateAroundZInDegrees(input.vertex, _RotationZ)
    111.                 * float4(_ScaleX, _ScaleY, 1.0, 1.0) * scale);
    112.                      
    113.                 output.tex = input.tex;
    114.  
    115.                 return output;
    116.             }
    117.  
    118.             float4 frag(vertexOutput input) : SV_TARGET
    119.             {
    120.                 // Outline stuff only
    121.                 // Get regular color
    122.                 fixed4 col = tex2D(_MainTex, input.tex);
    123.                 col *= _Color;
    124.                 col *= input.color;
    125.      
    126.                 float2 sampleDistance = _MainTex_TexelSize.xy * _OutlineWidth;
    127.  
    128.                 // Sample directions
    129.                 #define DIV_SQRT_2 0.70710678118
    130.                 float2 directions[8] = {float2(1, 0), float2(0, 1), float2(-1, 0), float2(0, -1),
    131.                     float2(DIV_SQRT_2, DIV_SQRT_2), float2(-DIV_SQRT_2, DIV_SQRT_2),
    132.                 float2(-DIV_SQRT_2, -DIV_SQRT_2), float2(DIV_SQRT_2, -DIV_SQRT_2)};
    133.  
    134.                 // Generate border
    135.                 float maxAlpha = 0;
    136.                 for(uint index = 0; index<8; index++){
    137.                     float2 sampleUV = input.tex + directions[index] * sampleDistance;
    138.                     maxAlpha = max(maxAlpha, tex2D(_MainTex, sampleUV).a);
    139.                 }
    140.  
    141.                 // Apply border
    142.                 col.rgb = lerp(_OutlineColor.rgb, col.rgb, col.a);
    143.                 col.a = max(col.a, maxAlpha);
    144.  
    145.                 return col;
    146.             }
    147.  
    148.             ENDCG
    149.         }
    150.     }
    151. }
     
    Last edited: Apr 6, 2022