11
\$\begingroup\$

Since version 5 (?) Unity has a new component-type BillboardRenderer. Unfortunately the documentation is quite poor.

It can be added in the inspector by clicking on "Add Component -> Misceallaneous -> Billboard Renderer" but apparently it requires a Billboard Asset to do anything. There doesn't seem to be any way to create one from the Unity interface.

One of the few sentences from the equally poor documentation of BillboardAsset reads:

imageCount Number of pre-baked images that can be switched when the billboard is viewed from different angles.

My newest project will have sprite/polygon mix graphics, so a component which renders a billboard with a different sprite depending on the view angle is something I could really make use of. But there doesn't seem to be any method to add such images.

So I wondered if you could post an example how this component is used.

\$\endgroup\$
2
  • \$\begingroup\$ Does the billboard do what I'd expect? Or something else? (I'd expect it to keep the image facing the camera.) \$\endgroup\$
    – Evorlor
    Feb 28 '16 at 17:13
  • \$\begingroup\$ @Evorlor That's what I would expect too, but so far I haven't managed to get it to do anything. \$\endgroup\$
    – Philipp
    Feb 28 '16 at 17:23
6
\$\begingroup\$

UPDATE (2018): There are more properties exposed since I wrote this answer. Maybe we can create it now, maybe not. Gotta research.

You can't use it.

Here is decompiled BillboardAsset code:

using System;

namespace UnityEngine
{
    /// <summary>
    ///   <para>BillboardAsset describes how a billboard is rendered.</para>
    /// </summary>
    public sealed class BillboardAsset : Object
    {
        /// <summary>
        ///   <para>Height of the billboard that is below ground.</para>
        /// </summary>
        public float bottom
        {
            [WrapperlessIcall]
            get;
            [WrapperlessIcall]
            set;
        }

        /// <summary>
        ///   <para>Height of the billboard.</para>
        /// </summary>
        public float height
        {
            [WrapperlessIcall]
            get;
            [WrapperlessIcall]
            set;
        }

        /// <summary>
        ///   <para>Number of pre-baked images that can be switched when the billboard is viewed from different angles.</para>
        /// </summary>
        public int imageCount
        {
            [WrapperlessIcall]
            get;
        }

        /// <summary>
        ///   <para>Number of indices in the billboard mesh. The mesh is not necessarily a quad. It can be a more complex shape which fits the actual image more precisely.</para>
        /// </summary>
        public int indexCount
        {
            [WrapperlessIcall]
            get;
        }

        /// <summary>
        ///   <para>The material used for rendering.</para>
        /// </summary>
        public Material material
        {
            [WrapperlessIcall]
            get;
            [WrapperlessIcall]
            set;
        }

        /// <summary>
        ///   <para>Number of vertices in the billboard mesh. The mesh is not necessarily a quad. It can be a more complex shape which fits the actual image more precisely.</para>
        /// </summary>
        public int vertexCount
        {
            [WrapperlessIcall]
            get;
        }

        /// <summary>
        ///   <para>Width of the billboard.</para>
        /// </summary>
        public float width
        {
            [WrapperlessIcall]
            get;
            [WrapperlessIcall]
            set;
        }

        /// <summary>
        ///   <para>Constructs a new BillboardAsset.</para>
        /// </summary>
        public BillboardAsset()
        {
        }

        [WrapperlessIcall]
        internal extern void MakeMaterialProperties(MaterialPropertyBlock properties, Camera camera);

        [WrapperlessIcall]
        internal extern void MakePreviewMesh(Mesh mesh);

        [WrapperlessIcall]
        internal extern void MakeRenderMesh(Mesh mesh, float widthScale, float heightScale, float rotation);
    }
}

There is literally no way to set images, even by reflection. One may think: “okay, you can't do it directly, but maybe there is some kind of factory provided?”. I press Find Usages in decompiler and get: BillboardAssetInspector and BillboardRenderer.

Here is BillboardRenderer:

using System;

namespace UnityEngine
{
    /// <summary>
    ///   <para>Renders a billboard.</para>
    /// </summary>
    public sealed class BillboardRenderer : Renderer
    {
        /// <summary>
        ///   <para>The BillboardAsset to render.</para>
        /// </summary>
        public BillboardAsset billboard
        {
            [WrapperlessIcall]
            get;
            [WrapperlessIcall]
            set;
        }

        /// <summary>
        ///   <para>Constructor.</para>
        /// </summary>
        public BillboardRenderer()
        {
        }
    }
}

Wow, this class is even dumber. It's just logic-less data holder. Obviously, all work is done by Renderer. More precisely, by one or couple of [WraplessIcall]-methods in it. I won't put its code here because it's long and useless list of [WraplessIcall]-members.

Unlike UnityEngine.dll content, BillboardAssetInspector (that is residing in UnityEditor.dll) has real code in it. Again, I won't put its code here, because it's clear from its name that it's nothing more than Inspector, anyway.

Same situation with BillboardAssetInspector.


Got it, it's for internal usage; but where is it used exactly?

In SpeedTree system (look at the last image in particular).

Why does documentation explain useless stuff instead of warning against using it right away?

Probably, just copy-pasted everything from internal development documentation, improved parts that are important for newcomers and in general usage; then was too busy participating in VR hype to bother polishing such dark corners of documentation.

What can we do about it?

Tell them that they overlooked this “dark corner” in the documentation, for example: In Unity Editor, open Help → Report a bug..., in What is problem related to choose documentation, etc.

What can one use instead?

Possible options include:

\$\endgroup\$
5
  • 2
    \$\begingroup\$ As another alternative: often when I need to place a bunch of billboards, I use a particle system, with its emission & animation turned off so I can manually position each billboarded quad where I want it. \$\endgroup\$
    – DMGregory
    Mar 7 '16 at 12:35
  • \$\begingroup\$ @DMGregory Can't disagree, Unity's particle system is great for many things that are not even remotely close to “particles”. Even better, since Unity 5, support for particle system customizing was explicitly improved/optimized. Should I add this option to the answer, or these comments are enough, what do you think? \$\endgroup\$ Mar 7 '16 at 13:14
  • \$\begingroup\$ I think it's fine to leave in the comments. If someone wants more details on it, I think it's meaty enough to ask a new question. \$\endgroup\$
    – DMGregory
    Mar 7 '16 at 13:17
  • \$\begingroup\$ I don't believe it for internal usage, the doc said: " You can also create your own once you know how the billboard is described." - docs.unity3d.com/ScriptReference/BillboardAsset.html \$\endgroup\$
    – 123iamking
    Aug 6 '18 at 4:03
  • \$\begingroup\$ @123iamking And now there are more properties exposed. So, yeah, it's probably possible to utilize them directly now. \$\endgroup\$ Aug 7 '18 at 0:21
1
\$\begingroup\$

To use BillboardRenderer, you need Billboard Asset, you can construct Billboard Asset with C# script. Check this post.

The Billboard Asset have content like this: Billboard.asset

 %YAML 1.1
 %TAG !u! tag:unity3d.com,2011:
 --- !u!226 &22600000
 BillboardAsset:
   m_ObjectHideFlags: 0
   m_CorrespondingSourceObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_Name: Billboard_Original
   serializedVersion: 2
   width: 10.350581
   bottom: -0.2622106
   height: 7.172371
   imageTexCoords:
   - {x: 0.230981, y: 0.33333302, z: 0.230981, w: -0.33333302}
   - {x: 0.230981, y: 0.66666603, z: 0.230981, w: -0.33333302}
   - {x: 0.33333302, y: 0, z: 0.33333302, w: 0.23098099}
   - {x: 0.564314, y: 0.23098099, z: 0.23098099, w: -0.33333302}
   - {x: 0.564314, y: 0.564314, z: 0.23098099, w: -0.33333403}
   - {x: 0.66666603, y: 0, z: 0.33333302, w: 0.23098099}
   - {x: 0.89764804, y: 0.23098099, z: 0.230982, w: -0.33333302}
   - {x: 0.89764804, y: 0.564314, z: 0.230982, w: -0.33333403}
   vertices:
   - {x: 0.47093, y: 0.020348798}
   - {x: 0.037790697, y: 0.498547}
   - {x: 0.037790697, y: 0.976744}
   - {x: 0.52906996, y: 0.020348798}
   - {x: 0.95930207, y: 0.498547}
   - {x: 0.95930207, y: 0.976744}
   indices: 040003000000010004000000050004000100020005000100
   material: {fileID: 2100000, guid: 6e680dda9368db5418f19388474277a2, type: 2}

Here is the C# code that used to generate the file above

 using System.Collections;
 using System.Collections.Generic;
 using UnityEditor;
 using UnityEngine;

     public class BillboardBaker : MonoBehaviour
     {
 #if UNITY_EDITOR
         public BillboardAsset m_outputFile;
         public Material m_material;

         [ContextMenu("Bake Billboard")]
         void BakeBillboard()
         {
             BillboardAsset billboard = new BillboardAsset();

             billboard.material = m_material;
             Vector4[] texCoords = new Vector4[8];
             ushort[] indices = new ushort[12];
             Vector2[] vertices = new Vector2[6];
             texCoords[0].Set(0.230981f, 0.33333302f, 0.230981f, -0.33333302f);
             texCoords[1].Set(0.230981f, 0.66666603f, 0.230981f,-0.33333302f);
             texCoords[2].Set(0.33333302f, 0.0f, 0.33333302f,0.23098099f);
             texCoords[3].Set(0.564314f, 0.23098099f, 0.23098099f,-0.33333302f);
             texCoords[4].Set(0.564314f, 0.564314f, 0.23098099f,-0.33333403f);
             texCoords[5].Set(0.66666603f, 0.0f, 0.33333302f,0.23098099f);
             texCoords[6].Set(0.89764804f, 0.23098099f, 0.230982f,-0.33333302f);
             texCoords[7].Set(0.89764804f, 0.564314f, 0.230982f,-0.33333403f);

             indices[0] = 4;
             indices[1] = 3;
             indices[2] = 0;
             indices[3] = 1;
             indices[4] = 4;
             indices[5] = 0;
             indices[6] = 5;
             indices[7] = 4;
             indices[8] = 1;
             indices[9] = 2;
             indices[10] = 5;
             indices[11] = 1;

             vertices[0].Set(0.47093f, 0.020348798f);
             vertices[1].Set(0.037790697f, 0.498547f);
             vertices[2].Set(0.037790697f, 0.976744f);
             vertices[3].Set(0.52906996f, 0.020348798f);
             vertices[4].Set(0.95930207f, 0.498547f);
             vertices[5].Set(0.95930207f, 0.976744f);

             billboard.SetImageTexCoords(texCoords);
             billboard.SetIndices(indices);
             billboard.SetVertices(vertices);

             billboard.width = 10.35058f;
             billboard.height = 7.172371f;
             billboard.bottom = -0.2622106f;

             if (m_outputFile != null)
             {
                 EditorUtility.CopySerialized(billboard, m_outputFile);
             }
             else
             {
                 string path;
                 path = AssetDatabase.GetAssetPath(m_material) + ".asset";
                 AssetDatabase.CreateAsset(billboard, path);
             }
         }
 #endif
     }

for more details, please check the post I gave in the beginning of the answer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.