~500 one color box static mesh actors, performance

Two part question…

Part 1:

I am spawning ~500 actors, then as the game is played the player’s actions can cause these actors to get destroyed. The actors are static mesh actors (AStaticMeshActor). It works fine except the performance is horrible.

For these ~500 actors, I don’t care about lighting or physics or ticks or anything. They are just simple one-color boxes that I want to be visible to the player.

How do I get the maximum performance out of these actors? If I was writing the same program with C++ and OpenGL (or DirectX), this would be easy to accomplish. So I know it’s not an issue of my host hardware which is very fast. The problem is that UE4 is slow for no good reason. There must be a way to turn off whatever useless activities UE4 is doing with each of these 26x19 actors? How can I disable lighting, physics, ticks, etc?

I want to disable any and all things that affect performance. The ~500 entities don’t even have to be “actors” if there’s a faster alternative. All I want them to do is to be physical boxes that the player can see (and again - they don’t even need lighting).

Another idea is to use instancing but I don’t think that should be necessary because from an OpenGL perspective the way I’d implement this is a single list of vertices, primitive type is triangle list, VS just does model world view transform, then pass-through PS just outputs a hardcoded color (eg gray).

Part 2:

If I was writing this in OpenGL/DirectX, I’d draw all ~500 entities in a single draw call because they are all the same material. The ~500 entities are just one-color boxes (eg gray boxes) and they are all the same color. So in OpenGL/DirectX, it would just draw a triangle list from a list of vertices. The list of vertices I could update as needed. I know how to do it in OpenGL, but how do I it efficiently in UE4? Does the materials system handle optimizations like that? Ie if I have multiple actors with the same material, will UE4 combine them into a single vertex buffer and draw them all in a single draw call?

Update:

Re McStrife: The cubes are generated with C++ code:

AHQGrayOutActor* pActorGrayOut = GetWorld()->SpawnActor(AHQGrayOutActor::StaticClass());

AHQGrayOutActor extends AStaticMeshActor because that makes it easy to customize my cube. However, it doesn’t need to be a static mesh if there’s something else I can use instead?

The cubes do not require any shadows or lighting or physics or collisions or ticks or anything except to be drawn. This is why I explained how I would do it if I wrote my app from scratch using OpenGL.

Re HungryDoodles: SetMobility(EComponentMobility::Static); in my AHQGrayOutActor ctor causes me to get a warning message “LIGHTING NEEDS TO BE REBUILT”. I’m guessing this is because my actors are generated in C++ code? The reason I do them is C++ code is just because there’s 26x19 of them in a grid so it’s easy to position them in C++ code. I’m not sure how to do that in the Unreal Editor.

Update 2:

I’ve attached a screen shot. Basically all I’m trying to do is “gray out” (using black boxes) unvisited portions of a board - in other words “fog of war”. I’m open to doing it a different way. Eg dynamically edit the board texture. Eg generate scaled static mesh actors of varying size - ie instead of 26x19 1x1 squares, my C++ code could search for the largest possible black rectangles starting with a single 26x19 box.

However, consider the following. In OpenGL, we could draw a box with 12 triangles. 26x19 x 12 = 5928. By not drawing the bottom side of the cube, we could reduce it to 26x19 x 10 = 4940. The VS/PS for my desired output is basically the simplest possible shader - VS just multiples pos by ModelViewProj, PS just outputs a hardcoded color. Any modern GPU, even $100 previous generation cheap Android smart phones can draw millions of triangles a frame. Doing a single draw call for ~5000 triangles with a pass-through shader shouldn’t be expensive. I’m assuming it’s only expensive because UE4 is doing stuff I don’t want it to do. So there must be a way to tell UE4 to just draw the triangles with a VS/PS and to not do anything extra - no physics, no ticks, no shadows, no lighting, no nothing etc.

Thank you for any leads on this.

Update 3:

Another idea is to have the entire fog of war be a single actor with a static mesh that is dynamically generated. I wonder - is there a way to dynamically generate vertex positions for an actor’s static mesh in C++ code?

Update 4:

I suppose what I want is called “draw call bacthing” . The idea is to have objects drawn with a particular material to be combined into a single draw call by combining their vertex buffers into a single vertex buffer. Because of culling (we can gain performance via CPU culling of objects based on a bounding box) (and objects can be far apart), it might make sense to have the developer specify which objects to batch (either that or have UE4 group them intelligently eg based on distance). From what I’ve read, UE4’s instancing doesn’t actually reduce draw calls - it just groups them (ie do all the draw calls for a same material in a row).

I was naively assuming this happened automatically… But apparently UE4 does not support this (but Unity does)?

Update 5:

I’ve attached a second screenshot of the board from a different angle.

As far as the details of the graying out… The color (gray, yellow, red, dark gray, black, etc) is TBD. Whether it’s transparent is TBD. Whether gray-out modifies the board texture vs. adds a small height is TBD (in the screen shots, the actors add some height).

In this third screen shot you can see what I mean by height:

Hey pemcode,

to improve your performance you can do several things.
First of all they don’t need any shadows (do they?) SO you can click on your Cubes on disable the Cast Shadow Flag in the Lighting Category.
The Resolution of the Light Map can also be lower (minimum is 4)

The next thing is the editing of the Material
Create your own material.
Click on the Output Node and go to Material->Shading Model and change it to Unlit.
Now fill your Emissive Color with Grey (0.5,0.5,0.5).
Do this if they really should not be affected by lighting.

Maybe there are several more changes you can make to improve the performance.
building your lighting should also make a difference.

Greetings

Also if it’s the same mesh with the same material Instanced Static Mesh should help a lot, this will draw whole array in a same amount of drawcalls as one (sometimes there is more drawcalls than one per material).
To be batched the actor and mesh component should be static, not movable. After building level geometry these cubes will become super-static and their rendering time will absurdly low.

@HungryDoodles: the objects (black boxes) are generated dynamically in C++ code. I suppose I could generate them in the editor (how?) (or manually create them) as long as they can still be Destroy()'ed (I have to check whether you can Destroy() a static actor with a static mesh?)…

Ah! You brought the picture of what you’re trying to do and now I can understand what needed.
First of all, as @ McStrife said you need to disable some rendering flags if you don’t need lightning at all.
Secondly, material should be unlit, maybe also transparent or masked, I can see some transparent layers on picture.
Now the most hardcore: dynamic texture for the whole screen layer. Dynamic textures explained here: Is it possible to create textures dynamically - Rendering - Epic Developer Community Forums
If I was solving such problem I could do it like this: Create two textures: 32x32 and other one is big enough to contain all other textures as tiles there; create material in such way: 32x32 texture will be texture index array for each screen square. Then pixel shader decides which sub-texture from big one to use.
Basic algorithm: Shader has two inputs: 32x32 uncompressed texture with point interpolation representing boxes indexes in corresponding positions and another texture which contains tiles of all other textures. For pixel shader function there is some uv-mapping magic:
->Get pixel value as int32 (you can use hlsl in Custom node, type int available there). It’s called index.
->Assuming that big texture has tiles placed in grid with size A we can calculate next 2D vector:
((float)(index/A) / A, (float)(index % A) / A) - this represents uv pitch, let’s call it pitch.
->Now we need to interpolate between linear movement in texture of indexes (plane) and texture of tiles, so we need to interpolate between size 32 and size A. We can discard coordinate shift in 32x32 texture by this: alpha = fmod(TexCoord * 32, 1).
->Finally we can get final texture coordinates: customTextCoord = pitch + Lerp(0, 1.0 / A, alpha).
Now this material will draw max of 32x32 blocks (can be bigger) 2D layer in one draw call. You can use several planes to make more layers.
P.S. Do you know how old versions of Minecraft was handling textures?There was big .png image somewhere in main .jar. All textures was collected in this big one as tiles, there was just the same principle.

Assuming we don’t want grayed-out squares to have additional height, this seems like a good idea. Earlier I was trying to do it with actors, but it can instead be done in the board’s PS. I haven’t fully digested your answer, but I think a good idea might be…

The PS can take as input the board texture plus a 1-bit 32x32 texture (really 26x19) that tracks which of the 26x19 squares are grayed-out. In the PS, we need to do a reverse modelViewProj (like we do in shadow mapping) to get from screen space to model space (of the board). Then we do two texture lookups. The normal one to the big 2048x1642 board texture, plus a second texture lookup to the 32x32 texture. If the 32x32 texel lookup is flagged true for gray-out, then we multiply the color we got from 2048x1642’s by the grayed-out color scaling, else we just use the texel color from the 2048x1642 texture as is.

That’s how I’d do it in OpenGL… But I’m not sure about UE4. I’ll have to think about that and also digest @HungryDoodles answer.

Update: One thing I notice in your answer is “big enough to contain all other textures as tiles there”. I only need to gray-out the board texture. The other objects don’t need to be grayed out. Sorry about the confusion.

Hey pemcode,

dynamically creating them does not mean that you cannot change these values without the editor.
If a value is available to be edited in the editor it can also be changed via C++ Code (Except they have private Access and no getter/setter)

This is strange really, that you have only 5 fps here, there is nothing that tough here to render. Even if there is 26192*2=1976 drawcalls (including lighting and transparency, but i don’t see some) that’s not that huge amount.
Can you share screenshot of profiler? In viewport settings there is a stats, there will be D3D11RHI or somewhat like that, I don’t actually remember.
Or print in console “stat d3d11rhi” for rendering statistics or “stat slow” for rendering timing.
Maybe there is CPU “giving up”?
I see you still have shadows, shadows can be disabled in light settings in level.
And what graphics card you have? I once had around 9000-10000 drawcalls (that’s how much I launched bullets into the air), but fps dropped from 60 to only 20, not that bad for such numbers. Bullets weren’t transparent, these was debug lines, poor PCI bus suffered a lot from updating 8192 bullets, so GPU itself felt fine.

One way to do this is have a 32x32 texture marking which squares are grayed out, then have the material (or shader?) use the 32x32 texture to compute the grayed out color. However, I think a simpler way to do it is to just make the actual 2048x2048 board texture a dynamic texture, then have my C++ code edit the texture colors. When a new square is revealed, my C++ code will just modify the 2048x2048 board texture. So far I’ve read about dynamic textures and it looks overly complicated and I’m not sure whether I’m on the right path…

http://unrealoutpost.com/blog/2016/04/13/fog-of-war-part-1/ - I just found this tutorial. It’s using dynamic textures for fog of war in Unreal Engine 4.11… So from a brief glance (notice the WarCraft 2 screen shot), I think it looks promising.

Though my scenario is a little different than WarCraft 2 because the fog of war doesn’t need to be as dynamic. Movement through the fog isn’t fliud because it’s a board game. Movement is very much discrete - each of the 26x19 squares is either 100% fog or 0% fog. Also, when a board game piece moves a square and new square becomes visible (no longer grayed out by fog), it becomes permanently visible. So updates only happen after each discrete square move, and the squares are updated discretely. Another difference is that only the board texture is modified - we don’t need to gray out anything else because there’s nothing to gray out (ie we don’t need to gray out other board game pieces).

@HungryDoodles I’ll post more later but here’s an update. I was able to edit the 2048x2048 directly in C++ code at run-time, but it crashes unless I disable mip maps when I write to the texels. It doesn’t make sense - I don’t think there’s any good reason why mipmaps have to be disabled to write to a texel, so maybe I’m doing something wrong, but I actually think UE4 is just broken here. I’m also using an uncompressed texture format with colors when I’d prefer a binary texture or whatever uses the least bits, because that’s what seems to work…

In any event, for now I’m thinking I want the 2048x2048 to use mipmaps for better performance… So I’m using a separate 32x32 texture without mipmaps for dynamic texture edits in C++ at run-time.

I haven’t figured out the mapping yet. It’s not just a matter of 2048x2048 divided by 64 equals 32x32, because there’s an offset in the top-left corner and a max in the bottom-right corner, but I’m working on that… The material takes UV coordinates as input in the range of 0.0f to 1.0f rather than 0 to 2047.

Another issue is that my 32x32 sampler has to be linear color, and I’m not sure how to do nearest point sampling (directx/opengl jargon).

Thank you for the ideas, I’ll post more later…

About texture filtering here https://answers.unrealengine.com/questions/47796/texture-nearest-neighbour-filtering.html
If field “Filter” is visible here then it’s accessible through C++ runtime (class UTexture has this field as enum TextureFilter, looked up in documentation, there is nearest option).
About the textures:

Big texture is pre-edited grid of textures. Little squares with “Tex” means that it’s texels size of 64x64.

Awesome diagram, though I’ve made some improvements to better describe my scenario. Notice the 26x19 squares take up the entire static mesh’s 638.7x512.3, but they do not take up the entire 2048x2048 texture. Top-left of the squares relative to the textures is not (0,0).

So we map 2048x2048 texture onto static mesh (only the 2048x1643 region). We also map 26x19 squares onto a sub-region of the static mesh…

I made some progress on using the 32x32 texture, but my math is still a bit off, so I think I need to review the details to get the math exactly right in my material. My material takes as input texture coordinates for the 2048x2048 texture, so we need to use those inputs to get the correct texel from the 32x32 texture (which we can blend with the texel we get from the 2048x2048 texture)…

The input to the material is UNORM so UV = (0 to 1, 0 to 1). Actually for my scenario the input is really (0.19775 to 1, 0.19775 to 1) because the 2048x2048 texture is actually 2048x1643 with padding. So basically we need to map from (0.19775 to 1, 0.19775 to 1) to be in the range of (0 to 25, 0 to 18). But actually there’s a slight offset from the top-left so I think it’s really (0.19775 + dx to 1, 0.19775 + dy to 1) mapped to (0 to 25, 0 to 18).

I may need to verify this but my notes say TL Edge 68,54; BR Edge 76,502. That’s pixels I measured in my original texture which was 6387x5123 (which I later resized to 2048x1643) (and I made the static mesh 638.7x512.3). So in terms of the static mesh, edgeRight is 7.6 and edgeBottom is 50.2.

Here’s a summary of the math…

In terms of the static mesh, the board squares are in the range of:
X: 6.8 to 638.7-7.6 … notice (638.7-7.6-6.8)/26 = 24.01 which is close to square width
Y: 5.4 to 512.3-50.2 … notice (512.3-50.2-5.4)/19 = 24.03 which is square height

Our UV input (used to sample 2048x2048 texture) to the material is in the range of:
X: 0 to 1
Y: 1-1643/2048 to 1 => 0.19775390625 to 1

In terms of the 32x32 texture, the board squares are in the range of:
X: 0 to 25/31 => 0 to 0.80645161290322580645
Y: 0 to 18/31 => 0 to 0.58064516129032258065

So how do we map our 2048x2048 UV input to 32x32 UV?
X: ?
Y: ?

Basically I think I need to do some combination of offset and scale to our 2048x2048 UV inputs to get the desired 32x32 UV. That’s my current idea…

An alternate idea I might consider is to add a second set of texture coordinates to my OBJ file. That would allow the graphics pipeline to do some of the math for me. However, I think even then we may still need to do some math in the shader (?). Hmmm… I’ll post more on this later…

Well, I have already described math algorithm above.
I made that material and it works just fine.
I put today’s date (at least in my timezone) in a first row and just sequence of texels in a second.

This is how material looks like. Notice Unlit shading model here.

This is how Custom node looks like in case if you don’t know.

And code for Custom node: [Texelator code][4]

Had a busy week, but I’m back on this… The next thing I want to figure out is how to specify two sets of UV texture coordinates. It’s not obvious to me whether this is possible in wavefront OBJ files or if I need to use Maya somehow then export an FBX.

I was able to get it to work by using Maya to create a second UV channel for my FBX file. Thanks for the help!