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

Why doesn't Unity provide a way to disable completely frustum culling?

Discussion in 'General Graphics' started by vutruc80, Sep 18, 2018.

  1. vutruc80

    vutruc80

    Joined:
    Jun 28, 2013
    Posts:
    57
    In my project, there are often ~ 600 meshes and i intentionally want to render it all so frustum culling is none sense and it eats up 20-30% Camera.render time? And there is just no way to disable it. That's a tragedy!
    From technical point of view i believe it's not that hard to implement an option like that.
    Why doesn't Unity want to do it at all as there are so many similar requests from 6,7 years ago?
     
    richardkettlewell likes this.
  2. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    Why do you want to disable frustum culling? Cameras should only automatically cull what they will not render, and the act of culling those objects is much cheaper than running them through the GPU and culling them pixel-by-pixel. Is it that your mesh is fully outside the camera until your vertices are displaced using a shader?

    If you absolutely must fool the engine into thinking these objects are on-screen, maybe you can modify your Mesh.Bounds to cover a large enough area that your camera will always see it? It's a guess, I haven't tested it.

    Good luck!
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    warthos3399 likes this.
  4. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    SRP offers manual control over culling but if it was me I would also go with the suggestion bgolus offered.
     
  5. vutruc80

    vutruc80

    Joined:
    Jun 28, 2013
    Posts:
    57
    Indirect instancing works best if we want to draw a mesh for a particular amount of times. That's not my case, my dynamic scene changes frame by frame.

    I'm currently in optimization phase so i'm not sure i want to rewrite all my shaders using shader graph just to be able to disable frustum culling.
     
  6. vutruc80

    vutruc80

    Joined:
    Jun 28, 2013
    Posts:
    57
    In this case i know beforehand that all the meshes are in the camera view.
     
    richardkettlewell likes this.
  7. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    I agree. I suffer the same problem, I would like to see if disabling culling would result in faster render times. This makes sense for a lot of things, like UI cameras for example (if you're not using Unity UI and canvases etc).
     
  8. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    How is it that the mesh can be in the camera view but not the frustum?

    EDIT: Nevermind, I misunderstood the issue. I'm surprised (and a bit skeptical) that your performance issues would be caused by frustum culling. Using 20-30% render time is not a red flag unless the render time itself is large.
     
  9. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    I have the same issue, i instantiate some particles in a script and move them in the vertex shader.
     
  10. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    I will emit circa 1000 particles and by the end of the emission i will have about 2 mil particles, is it possible to achieve that with indirect instancing. And it might not be wise to not do cull at all, i just want unity to be able to know the position of particles i changed via shaders so that the frustum culling to be valid
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    2 million quads is exactly the kind of use case instanced indirect is good at handling.
     
  12. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    However if that bypass frustum culling won't i have performance issues with that many particles? Especially when it will be in VR and i am going to need 90fps
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Depends. Would drawing a 8 million poly mesh kill your frame rate on the hardware you’re targeting? Because if you were to draw a single large mesh, all of those vertices are still being calculated even if they’re off screen. This is no different.

    But if you’re moving particles on the shader via compute or the older shader blit style, it’s monstrously expensive to get that data back to the CPU to cull. So the answers might be to cull them on the GPU with a compute shader, or use fewer particles.
     
  14. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    Its not like an app/game that needs to be run in a variety of targets. I have some specific data from a smoke diffuse in a room simulation and i just want to do the 3d rendering part in VR in Unity. So the answer is that it will be run in a high end system (2080ti/20-series). So you suggest to do the culling via compute shaders? Do the draw calls via computeInstancedIndirect?
     
  15. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    I also cant use less particles i have specific data(amount of CO2 per voxel). So i have a specific amount of particles i need to cover the room.
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I suggest not worrying about it unless it’s causing a performance issue.
     
  17. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    Hey i tried to render my particles via DrawMeshInstancedIndirect and i have two issues. First of all particles seem to flicker when i am using FixedUpdate with interval(0.01) but they dont with normal Update.(However since i am gonna need a constant 0.01s per frame a fixedupdate is preferable). The second and the biggest is that blending doesn't seem to work. Smoke is transparent obviously and the farthest particles do show and overwrite full the nearest.
    upload_2019-9-2_18-39-38.png
    I do have zwrite off and Blend SrcAlpha OneMinusSrcAlpha. About the code, i basically used the example code from the documentation https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html with some changes to match my case. For instance i initiate at start a starting position and just issue the indirect draw call at update.
    upload_2019-9-2_18-45-4.png

    upload_2019-9-2_18-45-26.png
    positions is an array of list<Vector3> which contains the positions of smoke instances in every 0.01sec
    This is the shader, ignore the commented code
    Code (CSharp):
    1. Shader "Unlit/SmokeShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Opacity("Opacity", Float) = 0.0
    7.         /*_Color("Albedo", Color) = (1,1,1,1)
    8.        
    9.         [HideInInspector] _ID("Id",Int) =0.0*/
    10.         //[HideInInspector] _PreviousPos("PreviousPosition", VectorArray) = (0.0,0.0,1.0)
    11.        
    12.        
    13.     }
    14.         SubShader
    15.     {
    16.         Tags { "Queue" = "Transparent" "RenderType" = "TransparentCutout" "IgnoreProjector" = "True" }
    17.         LOD 100
    18.  
    19.  
    20.         ZWrite Off
    21.  
    22.         Blend SrcAlpha OneMinusSrcAlpha
    23.         Pass
    24.         {
    25.             CGPROGRAM
    26.             #pragma vertex vert
    27.             #pragma fragment frag
    28.             #pragma target 4.5
    29.             // make fog work
    30.  
    31.             #pragma multi_compile_instancing
    32.             #include "UnityCG.cginc"
    33.             sampler2D _MainTex;
    34.             float _Opacity;
    35.             #if SHADER_TARGET >= 45
    36.                 StructuredBuffer<float3> previousPosition;
    37.             #endif
    38.     //        struct appdata
    39.     //        {
    40.                 //UNITY_VERTEX_INPUT_INSTANCE_ID
    41.                 //    //uint n : SV_InstanceID;
    42.                 //
    43.     //            float4 vertex :POSITION;
    44.     //            float2 uv : TEXCOORD0;
    45.     //        };
    46.  
    47.             struct v2f
    48.             {
    49.                 float4 pos : SV_POSITION;
    50.                 float2 uv_prev : TEXCOORD0;
    51.                 float2 uv_next : TEXCOORD1;
    52.  
    53.             };
    54.            
    55.            
    56.                
    57.            
    58.            
    59.  
    60.             v2f vert (appdata_full v, uint instanceID : SV_InstanceID)
    61.             {
    62.                 v2f o;
    63.                 /*UNITY_SETUP_INSTANCE_ID(v);
    64.                 UNITY_TRANSFER_INSTANCE_ID(v, o);*/
    65.                 #if SHADER_TARGET >= 45
    66.                     float3 pos = previousPosition[instanceID];
    67.                 #else
    68.                     float3 pos = 0;
    69.                 #endif
    70.                
    71.  
    72.                 float3 worldPosition = pos.xyz;
    73.                
    74.                 //int id = UNITY_ACCESS_INSTANCED_PROP(InstanceProperties, _ID);
    75.                
    76.                
    77.                 //float l = 100.0*fmod(_Time.x,0.01);
    78.                 //float3 currentPos = (1 - l) * (_PreviousPos[id].xyz) + l * (_NextPos[id].xyz);
    79.                 //l /= 0.02;
    80.                
    81.                 ////float l = 2.0;
    82.                 ////float3 currentPos = (1-l)* nextPos + l * nextPos003;
    83.                 //if (l <= 1)
    84.                 //{
    85.                 //    currentPos = (1 - l) * previousPos + l * nextPos001;
    86.                 //}
    87.                 //else if (l <= 2)
    88.                 //{
    89.                 //    currentPos = (2 - l) * nextPos001 + (l - 1) * nextPos;
    90.                 //}
    91.                 //else if (l <= 3)
    92.                 //{
    93.                 //    currentPos = (3 - l) * nextPos + (l - 2) * nextPos003;
    94.                 //}
    95.                 //else if (l <= 4)
    96.                 //{
    97.                 //    currentPos = (4 - l) * nextPos003 + (l - 3) * nextPos004;
    98.                 //}
    99.                 //else if (l <= 5)
    100.                 //{
    101.                 //    currentPos = (5 - l) * nextPos004 + (l - 4) * nextPos005;
    102.                 //}
    103.                 //float3 currentPos = previousPos + _Time.y * vel;
    104.                
    105.                 //float4 currentPos = float4(nextPos,1.0);
    106.                
    107.                 float4x4 worldMatrix = unity_ObjectToWorld;
    108.                 worldMatrix[0][3] = worldPosition.x;
    109.                 worldMatrix[1][3] = worldPosition.y;
    110.                 worldMatrix[2][3] = worldPosition.z;
    111.  
    112.                 float3 worldpos = worldPosition.xyz + v.vertex.xyz;
    113.                 o.pos = mul(UNITY_MATRIX_VP, float4(worldpos,1.0f));
    114.                
    115.                 /*worldMatrix[0][3] = 1;
    116.                 worldMatrix[1][3] = 1;
    117.                 worldMatrix[2][3] = 1;
    118.                 worldMatrix[3][3] = 1.0;*/
    119.                 /*worldMatrix[3][0] = 1.0;
    120.                 worldMatrix[3][1] = 1.0;
    121.                 worldMatrix[3][2] = 1.0;
    122.                 worldMatrix[3][3] = 1.0;*/
    123.                
    124.                 //float3 vpos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
    125.                 //float4 worldPos = float4(nextPos.x, nextPos.y, nextPos.z, 1);
    126.                 //o.vertex = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_V, mul(_Translate, v.vertex)));
    127.                 /*o.pos = mul(UNITY_MATRIX_P,
    128.                     mul(mul(UNITY_MATRIX_V,worldMatrix), float4(0.0, 0.0, 0.0, 1.0))
    129.                     + float4(v.vertex.x, v.vertex.y,0.0, 0.0)*float4(2.0f,2.0f,1.0f,1.0f));*/
    130.                 //v.vertex = mul(unity_WorldToObject, worldPos);
    131.  
    132.  
    133.  
    134.  
    135.  
    136.                 float speed = 12;
    137.              
    138.                 float2 size = float2(1.0f / 8, 1.0f / 8);
    139.                 uint totalFrames = 64;
    140.                 uint index = _Time.y*speed + instanceID;
    141.                 uint indexX = (index)%8;
    142.                 uint indexX2 = (index  + 1 ) % 8;
    143.                 uint indexY = floor(((index ) % 64) / 8);
    144.                 uint indexY2 = floor(((index  +1) % 64) / 8);
    145.                 float2 offset = float2(size.x * indexX, -size.y * indexY);
    146.                 float2 offset2 = float2(size.x * indexX2, -size.y * indexY2);
    147.                 float2 newUV = v.texcoord * size;
    148.                 newUV.y = newUV.y + size.y * (8 - 1);
    149.                 o.uv_prev = newUV + offset;
    150.                 o.uv_next = newUV + offset2;
    151.  
    152.                
    153.                
    154.                 //o.vertex = UnityObjectToClipPos(v.vertex);
    155.                 //o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    156.                 /*float3 RS = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
    157.                 float3 newRS = mul((float3x3)_Rotation, RS);
    158.                 float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1);
    159.                
    160.                 float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0);
    161.                 float4 outPos = mul(UNITY_MATRIX_P, viewPos);*/
    162.  
    163.  
    164.  
    165.                
    166.                    
    167.                     /*o.vertex = mul(UNITY_MATRIX_P,
    168.                     mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0))
    169.                     + float4(v.vertex.x, v.vertex.y, 0.0, 0.0)*float4(2.0,2.0,1.0,1.0));*/
    170.  
    171.                    
    172.                
    173.                 return o;
    174.             }  
    175.  
    176.             fixed4 frag (v2f i) : SV_Target
    177.             {
    178.                 float speed = 12;
    179.                 /*UNITY_SETUP_INSTANCE_ID(i);*/
    180.                 // sample the texture
    181.                
    182.                 float l = fmod(_Time.y,1.0/speed);
    183.                 l = l*speed;
    184.                 fixed4 tex = lerp(tex2D(_MainTex, i.uv_prev), tex2D(_MainTex, i.uv_next), l);
    185.                
    186.                 fixed4 col = tex;
    187.                 // apply fog
    188.                 //UNITY_APPLY_FOG(i.fogCoord, col);
    189.                 //col.a *= _Opacity;
    190.                
    191.                 return col;
    192.             }
    193.             ENDCG
    194.         }
    195.     }
    196. }
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Are you’re calling Graphics.DrawMeshInstancedindirect in the fixed update? That’s a big no-no as it might be getting called multiple times per rendered frame. Updating the particle positions using a fixed update loop is totally fine, but you should only be drawing in update or using a camera attached command buffer.

    Blending is working just fine. Sorting is not working, because you need to handle that manually when using DrawMeshInstancedIndirect, likely using a bitonic merge compute shader. Or you need to use opaque geometry, or some form of approximated order independent transparency like weighted blended OIT.
     
  19. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    Ok you got me confused there. How will be the draw call valid when the material's buffers arent updated. Basically i have in a list the position of particles every 0.01sec so i want to update the material every that interval. Correct me if i am wrong but in a basic rendering loop you update some buffers change the state of parametres in the rendering pipeline and then you issue the draw call.Then the gpu executes the corresponding shaders. If the draw call is every 0.02 for instance and i want new data every 0.01 how will be that correct? Also i don't understand how can be called multiple times per frame since i have one draw call in the update.

    Yeah i kinda thought of it might need that, i have seen some experiments in github of people working with compute shaders and draw indirect so i will check those out.
     
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    FixedUpdate is potentially called multiple times per rendered frame. It’s useful for updating animator or physics at a fixed time step, but the actual rendering of the scene is unrelated to FixedUpdate and happens after Update.

    For example, if you have a fixed update of 0.01, but you’re only rendering at 10 fps (update delta of 0.1), fixed update will be called 10 times before each update, and before a frame is rendered.
    https://docs.unity3d.com/Manual/ExecutionOrder.html

    If you’re rendering at 90hz (like for desktop VR), that’s an update of 0.0111..., which means every 10 frames or so the fixed update will be called twice.
     
  21. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    That would make sense if i was using both update and fixedupdate. However like i said i only use fixedUpdate ( unless an internal update still occurs and has different frame rate ,so there is an issue like you said) How can achieve only with update the fixed 10msec time per frame i want?
     
  22. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    I also have another issue i noticed (it doesnt seem to be cause of sorting). When i move the camera a bit in a specific direction the particles disappear and reappear if i move from that direction
    upload_2019-9-3_9-6-37.png
    If i move a bit to the right
    upload_2019-9-3_9-6-56.png
    And moving again more to the right they reappear
    upload_2019-9-3_9-8-0.png
     
  23. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    This is a gif in which i move around the particles as you will see in a frame it disappears
     

    Attached Files:

  24. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    It’s irrelevant if you’re only using fixed update, the engine renders frames after the update loop, and fixed update is potentially called multiple times per update. I’m talking engine level fixed update and update. See that execution order link.

    And you don’t do it with just a fixed update. Like I said, it’s good for physics and the like that need a fixed time step. It’s totally valid to run the particle physics in fixed update, but render them only in update. That is in fact how Unity does a bunch of internal stuff.
     
  25. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I’m guessing that’s using the “old” method where you have a few thousand quad meshes placed in the scene and are updating their location via the shader rather than using an explicit Draw command? The location where the game objects are placed is going out of view and being frustum culled. Since the CPU does the culling, it doesn’t know where, or even if, the GPU is moving them. That’s going back to the start of this thread and why I recommenced using DrawMeshInstancedIndirect to begin with.
     
  26. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    This is the result of DrawMeshInstancedIndirect, so why does that happen?And i dont move them i just have initialised a position array then i pass it to a buffer in the shader. At update i call DrawMeshInstancedIndirect and the shader based on the id of the instance access a position in the structuredBuffer i passed and uses that to place the particle
     
  27. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    This is it basically upload_2019-9-3_11-8-57.png
    Above is the Start and u can see the update and an init function which is called at start
     
  28. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Try using larger bounds?
     
    INeatFreak likes this.
  29. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    It seems it gets fixed if i set the Bounds to 1000
     
  30. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    Yeah i guess it applied the bounds to the one quad which gets instantiated
     
  31. psomgeorg

    psomgeorg

    Joined:
    Mar 16, 2019
    Posts:
    99
    Thank you for your help i really appreciate it you saved me from days searching. I am searching right now about sorting in compute shaders or order-independent methods. And about the fixed update you suggest setBuffers of data per 0.01 of the shader and at update draw call.
     
  32. LaireonGames

    LaireonGames

    Joined:
    Nov 16, 2013
    Posts:
    705
    Another +1 to be able to completely disable frustum culling.

    I'm writing a job to do occlusion culling and the first step is to do a manual frustum cull before doing the more complex occlusion cull, which means Unity also doing a FC is now pointless.

    This is especially true since I am using jobs for the heavy lifting meaning the usual things like MeshRenderer.isVisible is a bit useless cause I will have to do those checks on the main thread.

    If on the other hand I had access to a raw list of currently rendered meshes that I could throw into a job, that would be great and let me use both nicely.

    I could use the CullingGroup API but that feels like a fiddly mess + data duplication.

    As a side note I stumbled on this thread because I thought Unity would have a way of disabling/enabling the frustum cull in the scene view so I can sanity check what a specific camera is seeing but oh well.
     
    Last edited: Sep 23, 2022
  33. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Well, the right way of doing this would be via GPU culling, or rather GPU not drawing things to begin with so have you thought about using DrawMeshInstancedIndirect?
     
  34. LaireonGames

    LaireonGames

    Joined:
    Nov 16, 2013
    Posts:
    705
    Yup, wont make sense.

    I use GPU culling for things I'm instancing with that such as grass but this occlusion culling system is for the meshes to make up the world (Minecraft-esq game) so each one is unique and cant be baked ahead of time
     
  35. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Not seeing why that would change anything? It is still an ideal use-case so far.
     
  36. LaireonGames

    LaireonGames

    Joined:
    Nov 16, 2013
    Posts:
    705
    Ah ok I might have missed your point.

    Each mesh is unique so I would have to call DrawMeshInstancedIndirect once for each mesh. My assumption is you were trying to say I should try to draw everything with one DrawMeshInstancedIndirect call, which is what I can do with grass etc but is impossible with unique meshes.

    Are you then saying I call DrawMeshInstancedIndirect once per mesh and thus by calling it culling wont happen?

    If so, interesting....

    Is there going to be any extra overheads doing that? I've always used it with the assumption that its better for drawing lots of things in one call.

    Edit:
    Ah duh I'm with you now, this is what I get for trying to think when sick. I now need to change my systems to be more manual and get rid of MeshRenderers entirely. Instead I register a manual draw call using the results of my job to only submit the calls I need that frame.
     
    Last edited: Sep 23, 2022