Toqozz / blog-code

code from blog posts
https://toqoz.fyi
MIT License
90 stars 36 forks source link

Drawing Thousands of Meshes with DrawMeshInstanced / Indirect in Unity #6

Open Toqozz opened 3 years ago

Toqozz commented 3 years ago

Discuss the blog post here; share your thoughts, improvements, or any issues you run into so that others can find them.

thecrazy commented 3 years ago

I played around with your demo and was trying to use the standard shader but for some reason nothing is drawn unless I use the color variation shader.

On the blog it seems to suggest you were able to do it, any idea what may be causing this?

I'm not rendering anything fancy, just replaced the shader on your material to standard.

Toqozz commented 3 years ago

Hmmm. My guess is that Unity has updated their shader in a new version and it no longer works with DrawMeshInstanced. What version of Unity are you using?

I'll have more of a look into it now.

Toqozz commented 3 years ago

I tested Unity 2021.1.5f1, 2020.3.6f1, and 2019.4.1f1 and it seems to work on all of them.

Note that the standard shader will not work while using DrawMeshInstancedIndirect, only DrawMeshInstanced. With DrawMeshInstancedIndirect, we had to define and fill a custom _Properties buffer, which Unity's standard shader has no knowledge of.

thecrazy commented 3 years ago

Oh ok I will take a look at it again. Thank you.

StumpyTheAngry commented 2 years ago

This is an excellent demo and has potentially solved a large headache I've been dealing with for a while. You mention several times in the blog post the idea that properties need to be byte-wise identical - are you able to expand on that a bit. I'm not that familiar with the term and am keen to understand how to avoid any issues.

Toqozz commented 2 years ago

This is an excellent demo and has potentially solved a large headache I've been dealing with for a while. You mention several times in the blog post the idea that properties need to be byte-wise identical - are you able to expand on that a bit. I'm not that familiar with the term and am keen to understand how to avoid any issues.

So the Shader and the C# script are separate things and even run on separate hardware, and so they have no real type information about each other.

Essentially there's a data interpretation that needs to happen. Unity needs to send data from your C# code (system memory) to the GPU (gpu memory) to actually do anything with it on the GPU. The problem is that the GPU doesn't really have any information about what data is being sent, it just gets the raw bytes. So we create an identical structure on the GPU to be able to interpret it correctly -- this is what I mean when I say that the two structures need to be byte-wise identical.

Very simplified example with some comments:

// CSharp structure.
// The size of a Vector4 is 16 bytes -- 4 * 4 byte floats.
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/sizeof
struct MeshProperties {
    Vector4 color;    // Vector4s are 16 bytes -- 4 * 4 byte floats,  This is true or hlsl code too. 
}

// Create a buffer and send the bytes to the GPU here.., probably with `ComputeBuffer`
...
// HLSL structure
// The size of a float4 is also 16 bytes -- 4 * 4 byte floats, so this matches.
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-scalar
struct MeshProperties {
    float4 color;
};

StructuredBuffer<MeshProperties> _Properties;

// Do something with MeshProperties here...
...

The sanity checks to do are:

  1. Are the sizes of the structs identical?
  2. Is the order of the fields within the structs identical?

If you get 1. wrong, you can get out of bounds memory access, which is never good. If you get 2. wrong, you can get garbage data. For example if you had 2 floats and you mess up the order, you'll obviously get a mismatch.


This is actually a useful concept for a variety of other programming stuff too. When communicating between programming languages the same sort of thing has to happen. You also have to be careful to make sure that the language itself doesn't mess anything up. For example, in Rust they have #[repr(c)], which ensures your Rust struct has the same structure (lol) as your C one.

benb7760 commented 2 years ago

I'm fairly new to this aspect of Unity so forgive if this question is naive;

In the examples utilizing DrawMeshInstancedIndirect, you call SetBuffer on the material. Would this mean that this material can only be used for this particular set of mesh instances?

The scenario I am working towards is as follows: my material is actually a Texture Array, from which my shader selects textures based on each instance (among other things). If I used this approach, would this mean I would need to create a new material for this particular set of DrawMeshInstancedIndirect, and keep another copy for other rendering calls which utilize the texture array?

Could we instead call SetBuffer on a MaterialPropertyBlock and use the overload of DrawMeshInstancedIndirect, providing this MaterialPropertyBlock. Would this have the same effect, while allowing the material to be re-used elsewhere?

My main concern is that the Texture Array I am using will ideally serve quite a few different render calls which can all draw from the same array of textures, and if I need to create a new Material for each set of render calls, I will be increasing the memory considerably, as I am expecting the arrays to be considerable in size.

Toqozz commented 2 years ago

I'm fairly new to this aspect of Unity so forgive if this question is naive;

In the examples utilizing DrawMeshInstancedIndirect, you call SetBuffer on the material. Would this mean that this material can only be used for this particular set of mesh instances?

The scenario I am working towards is as follows: my material is actually a Texture Array, from which my shader selects textures based on each instance (among other things). If I used this approach, would this mean I would need to create a new material for this particular set of DrawMeshInstancedIndirect, and keep another copy for other rendering calls which utilize the texture array?

Could we instead call SetBuffer on a MaterialPropertyBlock and use the overload of DrawMeshInstancedIndirect, providing this MaterialPropertyBlock. Would this have the same effect, while allowing the material to be re-used elsewhere?

My main concern is that the Texture Array I am using will ideally serve quite a few different render calls which can all draw from the same array of textures, and if I need to create a new Material for each set of render calls, I will be increasing the memory considerably, as I am expecting the arrays to be considerable in size.

Sorry for the late reply.

I’m a little unclear on what you’re trying to do. Are you saying that you have a mesh that you want to draw many times, but only some of them using the texture array, and the rest using DrawMeshInstanced, not touching the texture array at all?

You are free to use or ignore whatever data you want in the shader:

// Even ids use texture, odd use properties (don’t actually do this).
if (v.id % 2 == 0) {
    // Use and do texture array stuff.
} else {
    // Use and do properties buffer stuff.
}

If my understanding of what you want is correct, I would just do the simple thing and set the texture array on the material (note: on the material, not the material instance -- this should make it available to any instance, DrawMeshInstanced or otherwise), and then set a material keyword on the instanced materials to decide on which path you want to go through: https://docs.unity3d.com/Manual/shader-variants-and-keywords.html .

tomorrowevening commented 1 year ago

How could Standard shading lighting be implemented?

elih1492 commented 1 year ago

I tried copying the scripts into my own project and nothing was showing up, so I downloaded the project files and it's was working on there so I copied the project exactly (I imported the assets) into my project and still nothing is showing up when I play.