Search Unity

Camera facing billboards for VR

Discussion in 'AR/VR (XR) Discussion' started by eastes, Jan 30, 2017.

  1. eastes

    eastes

    Joined:
    Sep 1, 2012
    Posts:
    59
    Camera facing particle billboards for VR. Has anyone managed to stop the rotation of them when rotating heads.

    See here: http://i.giphy.com/l3q2FlgFUsr5qVHTa.gif


    The particles look horrible in VR as they rotate when the head rotates. Has anyone been able to reduce the effect of this? I've tried setting Billboard Alignment to facing.
     
    Airmouse likes this.
  2. jonijoba

    jonijoba

    Joined:
    Jul 17, 2013
    Posts:
    7
    Same problem here, have you found any solution?
     
  3. SiliconDroid

    SiliconDroid

    Joined:
    Feb 20, 2017
    Posts:
    302
    Workaround:
    For ground based billboards (like trees) you can use a regular quad object or prefab with this optimized 1 dimension rotation script on it:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class vr_billboard : MonoBehaviour
    6. {
    7.     static Transform tCam = null;
    8.     void Update ()
    9.     {
    10.         if(!tCam)
    11.         {
    12.             if(!Camera.main)
    13.             {
    14.                 return;
    15.             }
    16.             tCam = Camera.main.transform;
    17.         }
    18.         transform.LookAt(tCam.position, Vector3.up);
    19.     }
    20. }
    For clouds up above like @eastes gif: Put them high enough and flat in XZ and no billboard needed or you could try swapping Vector3.up in above code for another axis if you want something above and fairly close.

    Full and general quad faces camera solution would require 2 dimensional constrained quaternion rotation.
     
    Last edited: Apr 16, 2017
  4. RibsNGibs

    RibsNGibs

    Joined:
    Jun 17, 2016
    Posts:
    15
    I just spent a week a little while ago tackling grass in Unity terrain, which does the same thing, and found some solutions. I have no experience with the particle billboards so I don't know if it will transfer over but maybe you will find one useful.

    1) as SiliconDroid said, for far away clouds don't even bother billboarding them

    2) you can rotate the objects themselves with a script as SiliconDroid said and not billboard in the shader, but if you have a lot of objects (like several thousand grass quads) this is really, really slow. But this is easiest.

    3) You can write a new billboard shader with a vert shader that orients to camera position instead of camera.forward. This approach suffered from some weird problem where you'd have to disable batching in the shader or the vert shader would run incorrectly and all your different quads would end up transformed on top of each other. Turning off batching was OK for small numbers but bad for ~5000 grass cards.

    4) The "best" solution I found which I'm using now for my grass was to copy the builtin unity grass shader and override it with my custom copy where I oriented to the camera position instead of camera forward. Again, I haven't messed with the particle system at all, but I would guess you could do something similar.
     
    SiliconDroid likes this.
  5. SiliconDroid

    SiliconDroid

    Joined:
    Feb 20, 2017
    Posts:
    302
    Yup... I guess that's gonna be the best solution for particles also.

    I'm guessing the "broken" billboard shader was written to match camera rotation instead of lookat camera for some batching performance reason as all quads will have same worldspace vert matrix, not sure tho? Shaders to me at the moment are a black art.
     
  6. RibsNGibs

    RibsNGibs

    Joined:
    Jun 17, 2016
    Posts:
    15
    Shaders are totally a black art to me too. I get the sl part but the hooks into unity and cg and pragma this and Tags that are totally confusing.

    Weirdly enough my custom "face the camera instead of camera.forward" shader which overrides the builtin grass shader and is working in the builtin terrain system, although it does have a "DisableBatching" = "True" line in it, somehow results in very few draw calls - much fewer than if I added the same number of grass objects on my own. I don't know how or what the Terrain system is doing but it actually works really well.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,347
    For Shuriken particles you can counter rotate the particles against the camera's roll, but it is expensive. For billboard shaders, most do a trick to expand themselves in view space, but that means they feel "attached" to your face. @RibsNGibs comments are good ones. A side effect of using a billboard shader to orient towards the camera position rather than "camera forward" is you need a second vector to construct a full orientation, and the easiest world vector to use is world up, the result is a billboard that isn't locked to the roll of the camera.

    It does have one problem, which is if you look up or down close to 90 degrees your billboards will either start to spin or shrink depending on the implementation, but luckily people don't usually rotate their heads that far.
     
  8. aimino

    aimino

    Joined:
    Feb 6, 2015
    Posts:
    6
    I have same problem. I'd like to try your solution. Would you like to share your custom "face the camera instead of camera.forward" shader code?
     
  9. kritoa

    kritoa

    Joined:
    Apr 21, 2017
    Posts:
    60
    I'm sure this isn't the best way to do this, but this worked for me (I just copied the builtin shader and the functions it called to do the billboarding and changed some code in the TerrainBillboardGrass function. I don't know if the naming matters, but if you put this in a file called WavingGrassBillboard.shader somewhere in your asset folder your grass should face the camera position instead of the camera view direction.

    Code (CSharp):
    1. // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
    2.  
    3. Shader "Hidden/TerrainEngine/Details/BillboardWavingDoublePass" {
    4.     Properties {
    5.         _WavingTint ("Fade Color", Color) = (.7,.6,.5, 0)
    6.         _MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
    7.         _WaveAndDistance ("Wave and distance", Vector) = (12, 3.6, 1, 1)
    8.         _Cutoff ("Cutoff", float) = 0.5
    9.     }
    10.    
    11. CGINCLUDE
    12. #include "UnityCG.cginc"
    13. #include "TerrainEngine.cginc"
    14.  
    15.         void TerrainBillboardGrassKritoa( inout float4 pos, float2 offset )
    16.         {
    17.             float3 grasspos = pos.xyz - _CameraPosition.xyz;
    18.             if (dot(grasspos, grasspos) > _WaveAndDistance.w)
    19.                 offset = 0.0;
    20.  
    21. // aim at camera instead of orienting to camera plane. Probably looks better to use (0,1,0) instead of newup.
    22.  
    23.             float3 right = normalize(cross(float3(0, 1, 0), grasspos));
    24.             float3 newup = normalize(cross(grasspos, right));
    25.  
    26.             pos.xyz += offset.x * right;
    27.             pos.xyz += offset.y * newup;
    28.  
    29. //            pos.xyz += offset.x * _CameraRight.xyz;    // old method to orient to camera orientation
    30. //            pos.xyz += offset.y * _CameraUp.xyz;
    31.         }
    32.  
    33.  
    34. void WavingGrassBillboardVertKritoa (inout appdata_full v)
    35. {
    36.     TerrainBillboardGrassKritoa (v.vertex, v.tangent.xy);
    37.     // wave amount defined by the grass height
    38.     float waveAmount = v.tangent.y;
    39.     v.color = TerrainWaveGrass (v.vertex, waveAmount, v.color);
    40. }
    41.  
    42. struct v2f {
    43.     float4 pos : SV_POSITION;
    44.     fixed4 color : COLOR;
    45.     float4 uv : TEXCOORD0;
    46.     UNITY_VERTEX_OUTPUT_STEREO
    47. };
    48. v2f BillboardVert (appdata_full v) {
    49.     v2f o;
    50.     UNITY_SETUP_INSTANCE_ID(v);
    51.     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    52.     WavingGrassBillboardVert (v);
    53.     o.color = v.color;
    54.    
    55.     o.color.rgb *= ShadeVertexLights (v.vertex, v.normal);
    56.        
    57.     o.pos = UnityObjectToClipPos(v.vertex);  
    58.     o.uv = v.texcoord;
    59.     return o;
    60. }
    61. ENDCG
    62.  
    63.     SubShader {
    64.         Tags {
    65.             "Queue" = "Geometry+200"
    66.             "IgnoreProjector"="True"
    67.             "RenderType"="GrassBillboard"
    68.             "DisableBatching"="True"
    69.         }
    70.         Cull Off
    71.         LOD 200
    72.         ColorMask RGB
    73.                
    74. CGPROGRAM
    75. #pragma surface surf Lambert vertex:WavingGrassBillboardVertKritoa addshadow exclude_path:deferred
    76.            
    77. sampler2D _MainTex;
    78. fixed _Cutoff;
    79.  
    80. struct Input {
    81.     float2 uv_MainTex;
    82.     fixed4 color : COLOR;
    83. };
    84.  
    85. void surf (Input IN, inout SurfaceOutput o) {
    86.     fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;
    87.     o.Albedo = c.rgb;
    88.     o.Alpha = c.a;
    89.     clip (o.Alpha - _Cutoff);
    90.     o.Alpha *= IN.color.a;
    91. }
    92.  
    93. ENDCG          
    94.     }
    95.  
    96.     Fallback Off
    97. }
    98.  
     
    Sailendu and SiliconDroid like this.
  10. aimino

    aimino

    Joined:
    Feb 6, 2015
    Posts:
    6
    Thank you for sharing your shader. I understood it and tried to modify TerrainBillboardTree in same way but trees still rotate.

    Code (CSharp):
    1. void TerrainBillboardTree( inout float4 pos, float2 offset, float offsetz )
    2. {
    3.     float3 treePos = pos.xyz - _TreeBillboardCameraPos.xyz;
    4.     float treeDistanceSqr = dot(treePos, treePos);
    5.     if( treeDistanceSqr > _TreeBillboardDistances.x )
    6.         offset.xy = offsetz = 0.0;
    7.  
    8.     float3 right = normalize(cross(float3(0, 1, 0), treePos));
    9.     float3 newup = normalize(cross(treePos, right));
    10.  
    11.     // positioning of billboard vertices horizontally
    12. //    pos.xyz += _TreeBillboardCameraRight.xyz * offset.x;
    13.     pos.xyz += right * offset.x;
    14.  
    15.     // tree billboards can have non-uniform scale,
    16.     // so when looking from above (or bellow) we must use
    17.     // billboard width as billboard height
    18.    
    19.     // 1) non-compensating
    20.     //pos.xyz += _TreeBillboardCameraUp.xyz * offset.y;
    21.    
    22.     // 2) correct compensating (?)
    23.     //float alpha = _TreeBillboardCameraPos.w;
    24.     //float a = offset.y;
    25.     //float b = offsetz;
    26.         // 2a) using elipse-radius formula
    27.         ////float r = abs(a * b) / sqrt(sqr(a * sin(alpha)) + sqr(b * cos(alpha))) * sign(b);
    28.         //float r = abs(a) * b / sqrt(sqr(a * sin(alpha)) + sqr(b * cos(alpha)));
    29.         // 2b) sin-cos lerp
    30.         //float r = b * sin(alpha) + a * cos(alpha);  
    31.     //pos.xyz += _TreeBillboardCameraUp.xyz * r;
    32.    
    33.     // 3) incorrect compensating (using lerp)
    34.     // _TreeBillboardCameraPos.w contains ImposterRenderTexture::GetBillboardAngleFactor()
    35.     //float billboardAngleFactor = _TreeBillboardCameraPos.w;
    36.     //float r = lerp(offset.y, offsetz, billboardAngleFactor);  
    37.     //pos.xyz += _TreeBillboardCameraUp.xyz * r;
    38.    
    39.     // so now we take solution #3 and complicate it even further...
    40.     //
    41.     // case 49851: Flying trees
    42.     // The problem was that tree billboard was fixed on it's center, which means
    43.     // the root of the tree is not fixed and can float around. This can be quite visible
    44.     // on slopes (checkout the case on fogbugz for screenshots).
    45.     //
    46.     // We're fixing this by fixing billboards to the root of the tree.
    47.     // Note that root of the tree is not necessary the bottom of the tree -
    48.     // there might be significant part of the tree bellow terrain.
    49.     // This fixation mode doesn't work when looking from above/below, because
    50.     // billboard is so close to the ground, so we offset it by certain distance
    51.     // when viewing angle is bigger than certain treshold (40 deg at the moment)
    52.    
    53.     // _TreeBillboardCameraPos.w contains ImposterRenderTexture::billboardAngleFactor
    54.     float billboardAngleFactor = _TreeBillboardCameraPos.w;
    55.     // The following line performs two things:
    56.     // 1) peform non-uniform scale, see "3) incorrect compensating (using lerp)" above
    57.     // 2) blend between vertical and horizontal billboard mode
    58.     float radius = lerp(offset.y, offsetz, billboardAngleFactor);
    59.            
    60.     // positioning of billboard vertices veritally
    61. //    pos.xyz += _TreeBillboardCameraUp.xyz * radius;
    62.     pos.xyz += newup * offset.y;
    63.  
    64.     // _TreeBillboardCameraUp.w contains ImposterRenderTexture::billboardOffsetFactor
    65.     float billboardOffsetFactor = _TreeBillboardCameraUp.w;
    66.     // Offsetting billboad from the ground, so it doesn't get clipped by ztest.
    67.     // In theory we should use billboardCenterOffsetY instead of offset.x,
    68.     // but we can't because offset.y is not the same for all 4 vertices, so
    69.     // we use offset.x which is the same for all 4 vertices (except sign).
    70.     // And it doesn't matter a lot how much we offset, we just need to offset
    71.     // it by some distance
    72.     pos.xyz += _TreeBillboardCameraFront.xyz * abs(offset.x) * billboardOffsetFactor;
    73. }
     
  11. kritoa

    kritoa

    Joined:
    Apr 21, 2017
    Posts:
    60
    Are you having problems with the actual billboarded trees - the ones really, really far away that are drawn with a single texture, or the trees with billboarded leaf and branch clusters - the trees up close that look mostly correct (trunk and branches and ~50% of the leaves look OK, and ~50% of the leaves rotate with camera)? I did an (awful) fix for geometry trees w/ billboarded leaf clusters that I could dig up later, but I haven't looked at the full billboarded trees (I think my quality settings have an LOD bias that makes them never go to full billboard).

    In any case, a cursory look of your code looks right. To be safe, I'd name the function something different (TerrainBillboardTreeAimino), and make sure that your shader calls that. I am unsure of the order of precedence if you have identically named functions.
     
  12. aimino

    aimino

    Joined:
    Feb 6, 2015
    Posts:
    6
    I have problems with the full billboarded trees. I confirmed that my shader is called by changing some parameters. I don't completely understand the mechanism of Unity's billboard processing, but it seems to be rotated by another process.
     
    Last edited: May 18, 2017
  13. kritoa

    kritoa

    Joined:
    Apr 21, 2017
    Posts:
    60
    I just tried out the billboarded trees and I was able to fix them by just going into Edit->ProjectSettings->Quality and toggling on the "Billboards Face Camera Position" button option.
     
    rmon222 likes this.
  14. aimino

    aimino

    Joined:
    Feb 6, 2015
    Posts:
    6
    Thank you for trying that. I guess that you tried with SpeedTree billboards.

    In this thread Unity stuff wrote that the quality option "Billboards face camera position" only works with SpeedTree billboards.
    https://forum.unity3d.com/threads/u...ug-with-oculus-unity-what-is-going-on.406006/

    For example, with this asset(set billboard start to 5), it doesn't work.
    https://www.assetstore.unity3d.com/jp/#!/content/6459
     
  15. graphicDNA

    graphicDNA

    Joined:
    Jun 26, 2017
    Posts:
    47
  16. sewy

    sewy

    Joined:
    Oct 11, 2015
    Posts:
    150
    Could you collaborate on that please? I am using this code which billboards good, but VR camera roll rotation is bad (pitch would be tolerable).
    Code (CSharp):
    1. o.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0)) + float4(-v.vertex.x, v.vertex.y, 0.0, 0.0) * float4(_Scale.x, _Scale.y, 1.0, 1.0));
     
  17. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,347
    For Unity particles, just click off the "Allow Roll" option in the particle system's Renderer settings.
    upload_2020-9-21_10-54-20.png
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,347
    If you're writing a custom shader not for particles, you can use something like this:
    Code (csharp):
    1. Shader "Unlit/WorldUpBillboardTest"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _CameraFacing ("Camera Facing", Range(0,1)) = 1.0
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.  
    13.         Pass
    14.         {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             // make fog work
    19.             #pragma multi_compile_fog
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2f
    30.             {
    31.                 float4 pos : SV_POSITION;
    32.                 float2 uv : TEXCOORD0;
    33.                 UNITY_FOG_COORDS(1)
    34.             };
    35.  
    36.             sampler2D _MainTex;
    37.             float4 _MainTex_ST;
    38.             float _CameraFacing;
    39.  
    40.             v2f vert (appdata v)
    41.             {
    42.                 v2f o;
    43.  
    44.                 // center camera position
    45.                 #ifdef UNITY_SINGLE_PASS_STEREO
    46.                 float3 camPos = (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5;
    47.                 #else
    48.                 float3 camPos = _WorldSpaceCameraPos;
    49.                 #endif
    50.  
    51.                 // world space mesh pivot
    52.                 float3 worldPivot = unity_ObjectToWorld._m03_m13_m23;
    53.  
    54.                 // x & y axis scales only
    55.                 float3 scale = float3(
    56.                     length(unity_ObjectToWorld._m00_m01_m02),
    57.                     length(unity_ObjectToWorld._m10_m11_m12),
    58.                     1.0
    59.                     );
    60.  
    61.                 // calculate billboard rotation matrix
    62.                 float3 f = normalize(lerp(
    63.                     -UNITY_MATRIX_V[2].xyz, // view forward dir
    64.                     normalize(worldPivot - camPos), // camera to pivot dir
    65.                     _CameraFacing));
    66.                 float3 u = float3(0.0,1.0,0.0);
    67.                 float3 r = normalize(cross(u, f));
    68.                 u = -normalize(cross(r, f));
    69.                 float3x3 billboardRotation = float3x3(r, u, f);
    70.  
    71.                 // apply scale, rotation, and translation to billboard
    72.                 float3 worldPos = mul(v.vertex.xyz * scale, billboardRotation) + worldPivot;
    73.  
    74.                 // transform into clip space
    75.                 o.pos = UnityWorldToClipPos(worldPos);
    76.  
    77.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    78.                 UNITY_TRANSFER_FOG(o,o.vertex);
    79.                 return o;
    80.             }
    81.  
    82.             fixed4 frag (v2f i) : SV_Target
    83.             {
    84.                 // sample the texture
    85.                 fixed4 col = tex2D(_MainTex, i.uv);
    86.                 // apply fog
    87.                 UNITY_APPLY_FOG(i.fogCoord, col);
    88.                 return col;
    89.             }
    90.             ENDCG
    91.         }
    92.     }
    93. }
     
    esme94089, God-at-play and sewy like this.