Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

TurboForest: Unity fast billboard forest rendering

Discussion in 'Assets and Asset Store' started by Alekxss, Jan 23, 2014.

  1. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Hi community!
    I'm new here and as to say hi want to share my little forest render technique to you.

    This is little idea how to can be render massive forest from bird fly height.
    For my project i need to render massive forest and standart instances and batching not works for me at acceptaible speed.

    $scr.jpg

    In action:


    Can be used in strategy or flightsim games, or archviz presentation projects.

    GTX650 renders 100 000 trees more than 100 FPS with shadows, without shadows - near 300.

    Idea is - billboards, but little advanced. I'm prerender tree turn 16 frames from side to top by X, and select frame depends on camera to ground plane angle. So why I get nice billboard look at any angle :)

    For more speed up, each tree collecting to batch (up to 10 000 trees per batch mesh)

    Script and shader are simple, and i think you can easy modify it as you want.

    Webplayer page and help here
    Unitypackage here (24.01.2014)

    Unity5 version with Android support here
    https://forum.unity3d.com/threads/t...d-forest-rendering.224165/page-6#post-2866049

    Traffic system from video

    Everyone who get first version better to download it again, this one is faster.

    Paid version with mobile support, brightness and saturation randomizers here
    https://www.assetstore.unity3d.com/en/#!/content/26354
     
    Last edited: Nov 28, 2016
  2. softwizz

    softwizz

    Joined:
    Mar 12, 2011
    Posts:
    793
    This will be so usefull.

    Can the textures be smaller or does each tree image need to be a ceratin size.
     
  3. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Yes, texture can be any size, but frames apear must be same - 4 trees, 16 frames and it's align in texture.
     
  4. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Nice!

    A hint - there's no need to hack the frustum culling system, you can set the mesh bounds manually:
    Code (csharp):
    1. mf.sharedMesh.bounds = new Bounds(center: (min + max) / 2, size: max - min);
     
  5. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Thanks, i'll try it :)
     
  6. dirtybassett

    dirtybassett

    Joined:
    Oct 3, 2012
    Posts:
    59
    Thank you for sharing this, cant wait to try it out.
     
  7. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    There's one thing I don't quite understand. When I raise the quads per mesh limit from 10000 (40k verts per mesh) up to 16000 quads (64k verts), why do I get the error?
     
  8. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
  9. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Yeah, it's strange.

    I'm checked it now here, and max indices for quads is 42 664 i.e. 10 666 quads per mesh (surface\submesh).
    Vertices is 64k, and it's correct, error is indexCount <= kMaxIndices.

    Here's fixed version which generates each batch with max indices count:Download

    Link in first post updated.
    Everyone who get first version better to download it again, this one is faster.
    P.S. shadow now is own for each tree (there was a mistake in first version)
     
    Last edited: Jan 24, 2014
  10. TyTiKi

    TyTiKi

    Joined:
    Nov 5, 2012
    Posts:
    9
    Really nice! Downloading the package to learn how it works right now! :D
     
  11. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788
    Wow this is great, on thing that i'm curious are those billboard can be modified to receive lighting?
    something like these... Link
     
  12. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    It have normal map now, and trees on shadow side is much dark when on sun side, and on middle (sun\shadow) side it's shaded natural.
    I will add receive shadows support, but it's not good idea i thinking...
     
  13. misterPantoni

    misterPantoni

    Joined:
    Feb 7, 2013
    Posts:
    44
    whow, this is really nice - great work! Actually i could use this in a current project, but i would need to modifiy the shader in a way, so that it is not always pointing upwards (World-up) but looking to a local-up position. (My "terrain" is kind of rotatable, so now trees look wierd when the terrain is tilted). can you give me a clue where to look for this kind of behaviour?
     
    Last edited: Jan 24, 2014
  14. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    In TurboForest shader, this vector is what you looking for:

    float d=saturate(dot(dir,float3(0,1,0)));
     

    Attached Files:

    Last edited: Jan 24, 2014
  15. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    But it's only vector to select correct texture Sprites (billboards) itself always look to up dir, it may be some complex to make them rotated in view space.

    Anyway if you want to make it this the main calculations:

    output.pos = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(input.pos.x, input.pos.y, input.pos.z, 1.0) ) - float4(input.vertex.x, -input.vertex.y, 0.0, 0.0));

    where input.pos.x, input.pos.y, input.pos.z is sprite center
    input.vertex.x, -input.vertex.y is sprite corner

    i think somwhere here you can pass normal of each tre and turn it.
     
  16. misterPantoni

    misterPantoni

    Joined:
    Feb 7, 2013
    Posts:
    44
    Thanks a lot, works like a charm now! Just feeding my "map"'s transform.up via Shader.SetGlobalVector to the Shader as Upvector.
     
  17. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    :) can you show a screenshot?
     
  18. Gibbonuk

    Gibbonuk

    Joined:
    Dec 10, 2013
    Posts:
    175
    Hi Aily (i belive i remember you from another engine?) anyway, i think this is amazing and something i could really make use of right now, the only reason for me not doing is because of "placing" the tree's. Correct me if im wrong but this is done by a mesh map? Is there anyway way of making trees "placable" by hand, or even better using a "splatmap" kinda way?

    Thanks
    Andy
     
  19. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Hi, yes you remember me correct ;)

    Current script doesn't support to set each tree manualy. Why you don't want to use separete mesh to place trees, you can hide it (disable renderer) or even disable this mesh at scene starts, after trees placed.
     
  20. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    You can self add any features you need, just look at TurboForest.cs scrip, remove all from GenerateTurboForest() proc.

    And add there:

    tfQuad q;

    q=new tfQuad(new Vector3(-5,0,0));
    q.scale=1;
    q=new tfQuad(new Vector3(5,0,0));
    q.scale=1;

    BuildMesh();
    tfQuad.quads.Clear();
    System.GC.Collect();

    On start script will create 2 random tree at those positions :) Don't forget - maximum trees per mesh is10666, so you can fill trees, generate mesh, clear trees list many times till all your tres be placed.
     
  21. Frednaar

    Frednaar

    Joined:
    Apr 18, 2010
    Posts:
    153
    Very nice thank you.... I am currently checking the webplayer and maybe there's a small bug

    When you look at a tree from the top after zooming and rotate the view, the terrain rotates but the tree billboard does not. I understand why it does that as it is a billboard, but I was wondering if this could be solved somehow....

    thanks
    Fred
     
  22. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    This can be solved if use of 3D textures, which makes shader more complex and it's project can became from "TurboForest" to "3DTextureSlowAsUsualForest" :) I hope you understand.

    Main idea of it's technique is render forest fast as possible with acceptable forest look.

    This is one of my projects, it uses usual meshes for trees and looks as you want, but my next project want to render x10 times more trees that it scene have and much complex city and much complex scripts.

    $scr.jpg

    So TurboForest is little tool to easy use forest for such scenes, where each tree look not so crytical, as look of big forest (strategy for example, or flightsim).

    Maybe someday i will need real 3d textured trees, but it will be next tool ;)
     
  23. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    This looks like a really nice project! Would it be possible to add some comments to the shader? Its a bit complex to understand whats going on : p
     
  24. Psyche_RTS

    Psyche_RTS

    Joined:
    Oct 8, 2012
    Posts:
    26
    Hi Aily.

    great way to render forest!! It runs like a charm.
    Very interesting video of your project. I used to work on this kind of "big sized" environnement.
    What is your traffic system?
    Can you tell me more about it? Is it available from the asset sore?
     
  25. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Yep :) even with complex scenes.

    No, it's my own scripts, hardcoded with application.
    It's very simple, i place "Path" objects and add "waypoints" objects as it's children, named 1......last from start to end.
    On start script little smooth path and place random cars.
    Each car is shifted right on it width in model, so how it's easy to make 2 ways car movements.

    But it's all realy very simple, for example if car finish it path on roads cross, it's just disapears, so don't looks natural when near it :)

    But if you realy need, i can share it separated from main app without any updates, as it is.
     
  26. Psyche_RTS

    Psyche_RTS

    Joined:
    Oct 8, 2012
    Posts:
    26
    It would be great if you could share it.
    I could be a good start for me to work on it (as I'm not a programmer).
    It can create life in my environnements.

    Thanks a lot!
     
  27. alexmbrau

    alexmbrau

    Joined:
    Apr 28, 2013
    Posts:
    34
    I would also like to have a version that works with splatmat.
     
  28. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    What is this?
     
  29. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    As i promised:
    http://aily.ru/SimpleTraffic/SimpleTraffic.html
     
    Jaqal likes this.
  30. Psyche_RTS

    Psyche_RTS

    Joined:
    Oct 8, 2012
    Posts:
    26
    Thanks a lot Aily!

    I will try this as soon as possible!
     
  31. gecko

    gecko

    Joined:
    Aug 10, 2006
    Posts:
    2,240
    Very cool! However, when I try it in an iPad build, the trees do not render (though GPU acts like they are rendering). Is there a way to make it work on mobile?
     
  32. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Ok, will check it.
     
  33. 1thewitcher

    1thewitcher

    Joined:
    Jul 23, 2013
    Posts:
    10
    Hi Aily, thanks for the script.
    I find a little bug. If look at trees while standing in point light or spot light range, trees begin to behave as if they all fall under this coverage.
     
  34. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Thanks for beta-testing :) seems like I need to make something like TurboForest2 with all bug fixes and some new features.
     
  35. 1thewitcher

    1thewitcher

    Joined:
    Jul 23, 2013
    Posts:
    10
    One more thing in your bug stack)
    Trees are not visible on Android devices. And editor shows this message in Inspector tab: "PerformanceChecks.ShaderWithClipAndroid"
     
  36. 1thewitcher

    1thewitcher

    Joined:
    Jul 23, 2013
    Posts:
    10
  37. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Hi :) seems like you realy like this forest stuff.

    You know, i don't maked it for mobile, and not checked on any platform, only on Windows, so working with mobile it's only "possibility" ;)

    I will check all of this bugs someday, but not right now, little busy here.

    Thanks for tips.
     
  38. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Maybe on mobile devices normal buffer can store only -1.....1 range values.
    Normals - is trees positions, that is why the all in zero.
    You can try to find this in script, divide by 100 and in shader multiply by 100 :) to check fast.
     
  39. 1thewitcher

    1thewitcher

    Joined:
    Jul 23, 2013
    Posts:
    10
    Yeaah i like your solution of filling map with bunch of objects by cheap performance price, it is perfect for mobile platforms) If only it will be optimized for them i would even buy it in Asset store)
     
    Last edited: Mar 7, 2014
  40. 1thewitcher

    1thewitcher

    Joined:
    Jul 23, 2013
    Posts:
    10
    Unfortunately your solution of positioning problem dont work for me(( Trees still in 0.

    normals[ii]=q.pos / 100;
    normals[ii+1]=q.pos / 100;
    normals[ii+2]=q.pos / 100;
    normals[ii+3]=q.pos / 100;
    //----------------------------------------------

    output.pos = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(input.pos.x * 100, input.pos.y * 100, input.pos.z * 100, 1.0) ) - float4(input.vertex.x, -input.vertex.y, 0.0, 0.0));
     
    Last edited: Mar 7, 2014
  41. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Will check it at evening, when good Android device will return to home :D
     
  42. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    350
    Hello there! If anyone still interested for android solution. You must swap data for vertices and normals, i.e. save world position to the vertices array, and save quad corners to the normals array, and do some swapping in shader.
    Cheers)
     
  43. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    Great!
    This is more clever, than my decision. As i said before - maybe Android have less buffer size for normals, than PC.
    Thanks for share to all ;)
     
  44. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    350
    Thanks you, for sharing this, it's very cool! Here very raw lifebar prototype. Seems, in this way we can proceed hundreds of bars, without notable performance hit on Android device.
    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4.  
    5. public class LifeBarDrawner : MonoBehaviour
    6. {
    7.     public Vector3 scale = new Vector3(1, 0.25f);
    8.  
    9.     public Transform[] units;
    10.  
    11.     private Vector3[] _quadPos = new Vector3[] { new Vector3(-0.5f, -0.5f, 0), new Vector3(0.5f, -0.5f, 0), new Vector3(0.5f, 0.5f, 0), new Vector3(-0.5f, 0.5f, 0) };
    12.     private Vector2[] _quadUvs = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1) };
    13.     private MeshFilter _mf;
    14.  
    15.     Vector3[] _vert;
    16.     Vector2[] _uvs;
    17.     Vector3[] _normals;
    18.     int[] _indices;
    19.  
    20.     void Start()
    21.     {
    22.         _mf = gameObject.GetComponent<MeshFilter>();
    23.         _mf.sharedMesh = new Mesh();
    24.         _mf.sharedMesh.MarkDynamic();
    25.  
    26.         _vert = new Vector3[units.Length * 4];
    27.         _uvs = new Vector2[_vert.Length];
    28.         _normals = new Vector3[_vert.Length];
    29.         _indices = new int[_vert.Length];
    30.  
    31.         SetPositions();
    32.  
    33.         for (int i = 0; i < _vert.Length; i++)
    34.         {
    35.             int ind = Repeat(i, 4);
    36.             _normals[i] = Vector3.Scale(_quadPos[ind], scale);
    37.             _uvs[i] = _quadUvs[ind];
    38.             _indices[i] = i;
    39.         }
    40.         _mf.sharedMesh.normals = _normals;
    41.         _mf.sharedMesh.uv = _uvs;
    42.         _mf.sharedMesh.SetIndices(_indices, MeshTopology.Quads, 0);
    43.  
    44.         _mf.sharedMesh.bounds = new Bounds(center: Vector3.zero, size: Vector3.one * 1000);
    45.     }
    46.  
    47.     void LateUpdate()
    48.     {
    49.         SetPositions();
    50.     }
    51.  
    52.     private void SetPositions()
    53.     {
    54.         for (int i = 0; i < _vert.Length; i += 4)
    55.         {
    56.             Vector3 unitPos = units[i/4].transform.position;
    57.             _vert[i] = unitPos;
    58.             _vert[i + 1] = unitPos;
    59.             _vert[i + 2] = unitPos;
    60.             _vert[i + 3] = unitPos;
    61.         }
    62.         _mf.sharedMesh.vertices = _vert;
    63.     }
    64.  
    65.  
    66.     private static int Repeat(int t, int length)
    67.     {
    68.         return t - (t / length) * length;
    69.     }
    70. }
    71.  
    72.  
    Code (csharp):
    1.  
    2.  
    3. Shader "Cg/LifeBarNormal"{
    4. Properties
    5. {
    6.         _MainTex ("Base (RGB)", 2D) = "white" {}
    7. }
    8.  
    9. SubShader
    10. {
    11.         Tags {"Queue"="Opaque" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
    12.         LOD 100
    13.         Lighting Off
    14.        
    15.         Pass
    16.         {  
    17.                 CGPROGRAM
    18.                         #pragma vertex vert
    19.                         #pragma fragment frag
    20.                        
    21.                         #include "UnityCG.cginc"
    22.  
    23.                         struct appdata_t
    24.                         {
    25.                                 float4 vertex : POSITION;
    26.                                 float2 texcoord : TEXCOORD0;
    27.                                 float3 normal : NORMAL;
    28.                         };
    29.  
    30.                         struct v2f
    31.                         {
    32.                                 float4 vertex : SV_POSITION;
    33.                                 half2 texcoord : TEXCOORD0;
    34.                         };
    35.  
    36.                         sampler2D _MainTex;
    37.                         float4 _MainTex_ST;
    38.                        
    39.                         v2f vert (appdata_t v)
    40.                         {
    41.                                 v2f o;
    42.                                 o.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(v.vertex.xyz, 1.0)) - float4(v.normal.x, -v.normal.y, 0.0, 0.0));
    43.                                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    44.                                 return o;
    45.                         }
    46.                        
    47.                         fixed4 frag (v2f i) : COLOR
    48.                         {
    49.                                 fixed4 col = tex2D(_MainTex, i.texcoord);
    50.                                 return col;
    51.                         }
    52.                 ENDCG
    53.         }
    54. }
    55.  
    56. }
    57.  
    58.  
    Maybe you already realized that, but anyway. You can get performance boost in almost no cost,in case are the pieces are not huge random shapes. If you split chunks by rectangle pattern and set properly render bounds, they will be culled by camera fustrum: nothing been rendered--- even more FPS.
     
  45. Baldwin

    Baldwin

    Joined:
    Apr 1, 2014
    Posts:
    37
    Lol...why didnt you include on how to use this thing?? What to do??
     
  46. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
  47. HeadClot88

    HeadClot88

    Joined:
    Jul 3, 2012
    Posts:
    736
    Whoa - how come i did not see this before!

    You sir have won my Internets for today!
     
  48. Baldwin

    Baldwin

    Joined:
    Apr 1, 2014
    Posts:
    37

    Thanks, question why is it spawning on the over side of the object aswell? Like a mirror, also, can this work on any imported mesh? Cant seem to get it to work on my terrian plane
     
  49. Alekxss

    Alekxss

    Joined:
    Mar 25, 2013
    Posts:
    170
    If you look into script, there have line:
    Vector3 tpos=new Vector3(0,100000,0); // raycast from (to check if tree on mesh)

    and next
    float castDistance=Mathf.Abs(tpos.y*2);

    Script geting this game object collider, and raycast using values below, i.e. from 100000 height to -100000 in global space, maybe your model top side is upper than y=100000?

    As you can see - object must have collider.

    And i dont get it - what is like a mirror? Trees are flipped vertical (it must be impossible)?

    You can share screenshot, or your ground plane.
     
  50. Baldwin

    Baldwin

    Joined:
    Apr 1, 2014
    Posts:
    37
    So to get this to work I must make my mesh smaller? Or increase the code values??? And I'll get back to you on that right now I want the tress on my imported mesh