Search Unity

Question Rotate Mesh inside Shader

Discussion in 'General Graphics' started by FE-Games, May 14, 2021.

  1. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Hello,

    iam need to rotate a mesh inside my custom shader.
    the shader becomes the data from a vector3 using computeBuffers, no i need to use the direction the shader becomes from script to rotate it to this direction. i never used something like this and don't know how to begin with it.

    rotation in shader is new for me xD

    Someone can help me?
    thanks in advance
     
  2. gregoired

    gregoired

    Joined:
    Apr 8, 2013
    Posts:
    20
    I would pass a Matrix4x4 containing the rotation inside the shader, and apply this rotation inside the vertex shader,

    In your CSharp :
    Code (CSharp):
    1.  
    2. ...
    3. Matrix4x4 rotMatrix = Matrix4x4.Rotate(yourRotation);
    4. material.SetMatrix("_Rotation",rotMatrix);
    5. ...
    6.  
    And in your vertex shader :

    Code (CSharp):
    1.  
    2. ...
    3. o.vertex   = mul(_Rotation, float4(v.vertex.xyz, 1.));
    4. ...
    5.  
     
    Propagant likes this.
  3. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Okay, iam really new to shaders, my shader has allready a "o.vertex = mul" calculation.
    Code (CSharp):
    1. v2f o;
    2.                 UNITY_INITIALIZE_OUTPUT(v2f, o);
    3.                 o.vertex = mul(UNITY_MATRIX_VP, fixed4(worldPosition, 1.0f));
    4.            
    5.                 //Rotation
    6.                 o.vertex = mul(rotationBuffer[instanceID], float4(v.vertex.xyz, 1.));
    so this is i end with.

    iam sending things to the shader with ComputeBuffers
    Code (CSharp):
    1.                         Quaternion rot = Quaternion.LookRotation(position, new Vector3(0, 1, 0);
    2.                         Matrix4x4 rotMatrix = Matrix4x4.Rotate(rot);
    3. //ComputeBuffer
    4.                         rotations.Add(rotMatrix);
    Iam only need to goal this:

    1. Iam have a list with Directions inside, this directions is all i need for the shader to know which rotation the object should have. The shader is a gpu instancing shader with ComputeBuffers, so i need to transfer the position and directions the mesh should look to the shader.

    position is allready transfering well, but iam don't know much about shader rotations and so i need to solve this.

    iam read about rotate a vector using a quaternion, if this is possible for using in shaders, i am save one ComputeBuffer, because i can transfer all vectors with directions together.

    Sorry about the bad english, iam from germany :D

    Hope someone know what i mean.
     
    Last edited: May 17, 2021
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    This is calculating the clip space position. You do not want to be rotating that. Presumably you want to be rotating the object in world space, so you should be modifying that. And really you'll want to be rotating before you've applied the world position offset otherwise you'll be rotating your object around the world origin instead of around its own pivot.

    What is the vector you're passing to it? Is it just a world space position, or a world space vector relative to that object's position? (i.e.: target position - object position) Hopefully it's a relative vector. But both of those function's you're calling c#, the
    Quaternion.LookRotation()
    and
    Matrix4x4.Rotate()
    , are big functions under the hood. That's not something you want to mimic in the shader.

    But here's the thing, if you're passing in a full transform matrix for rotation, you should just make that a full transform matrix with the position too and use that as is instead of applying the position and rotation separately. Unity's built in instancing works exactly this way, even for particles. Then you apply that transform to get the world position, and the
    UNITY_MATRIX_VP
    to get the clip space position and you're done. This is by far the simplest way to get what you want working.


    The other option is you'll need to construct a rotation matrix in the shader yourself. This is possible, and can be done in a way far less expensive than the "build a quaternion and then a rotation matrix from the quaternion" method required by c#. The code that creates the look at quaternion likely creates a look at rotation matrix and converts that to a quaternion!

    The code for that is something like this:
    Code (csharp):
    1. float3 forward = normalize(forwardVec[instanceID]); // or pre-normalize it in c#
    2. float3 right = normalize(cross(forward, float3(0,1,0)));
    3. float3 up = cross(right, forward); // does not need to be normalized
    4. float3x3 rotationMatrix = float3x3(right, up, forward);
    5.  
    6. float3 worldPosition = mul(v.vertex, rotationMatrix) + position[instanceID];
     
    segant, unity_Ie0Odhe1gaOaCg and bzor like this.
  5. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Thank you for the answer iam unsterstand the most but not all.

    so the first way is to send a complete matrix4x4 to the shader with a computeBuffer, so i only need one buffer with all data for rotation and position, right?

    the problem is matrix4x4 is very unkown for me, i dont know much about.

    the second method is, sending position and direction to the shader using a Buffer, and combining this?

    Which one is faster for 10000 objects?

    its possible for me to precalculate the matrix4x4 once, for all objects, so i only need to send it once to the shader?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Matrices are pretty important. You’re not going to avoid them when you’re doing shaders. The above example shader code is creating a 3x3 rotation matrix and applying the offset manually, but otherwise is very similar to a 4x4 matrix.

    However the advantage of passing a 4x4 matrix is the c# code does all the work for you and you don’t really need to understand matrices. Use
    Matrix4x4.TRS()
    to make the matrix, pass it along, and you’re done. If you want to be fancy you can pass along just the first 3 rows since the fourth will always be (0,0,0,1).

    It’s less a question of the number of objects and more a question of the number of vertices. The GPU is likely going to be faster at doing the math than your CPU, depending on your CPU & GPU of course. But if you have a mesh with several thousand vertices ... it might be slower to do it on the GPU since each vertex has to calculate the matrix again. But if it’s a couple of boxes or quads? Almost certainly faster to calculate the matrix on the GPU.

    But ultimately the only way to know is to try both and find out.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    If the instanced meshes don’t move or change rotation, yeah, calculate the Matrix4x4 for each once in c# and you’re done.
     
  8. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    yes they don't moving, its for world generation in low poly style. all can be done once and then the cpu loads the chunks from a file and send only the obejcts to gpu who should be rendered.

    so 3x3 Matrix is only the rotation and 4x4 Matrix is a complete Rotation+Position thing?
    i need some code for understanding well, iam learning better in practice.

    Should i send the 4x4 Matrix as a ComputeBuffer or is there a extra thing for doing this?

    Some other questions:

    its possible to load a file inside the shader which contains something like:
    ID 0 = Color.White
    ID 1 = Color.Black
    and so on?

    I only use Graphics.DrawMeshInstancedIndirect in the update method and nothing else. Above a certain number of objects, the CPU is fully utilized and my FPS go down, but the GPU is not utilized according to Unity, why is that?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You should read up on 3d transform matrices.
    https://catlikecoding.com/unity/tutorials/rendering/

    It's not really important to understand how matrices work, only really what they do and how to make use of them. Mainly they transform from one "space" to another. The easiest one to understand being the object to world space. In object space 0,0,0 is the pivot of the mesh. In world space 0,0,0 is the center of the world. When you move, scale, or rotate an object you're modifying what that transform matrix needs to be to move, scale, and rotate the mesh from its object space to where you want it in the world space. Beyond that there's the view space, clip space, NDC space, window space, and screen space... almost all of which you can ignore for now.

    If you want to know more, there are a ton of resources out there you can read on the topic.

    For your case, if the objects truly never need to change their position or orientation after they've initially been spawned, then yes, you really just need to fill a compute buffer of Matrix4x4 values with their appropriate object to world transforms and use that in the shader instead of the Unity supplied
    unity_ObjectToWorld
    matrix and you're done.

    Depends on what you mean by "file".

    Can you have a shader load an arbitrary file by itself? No. Shaders can't do that. It can only take data that's supplied to it and output either per vertex values that'll be interpolated, or a color value to be displayed.

    Can you pass in list of values for the shader to read from? Yes. That's what a compute buffer / structured buffer is. If you want color values instead of positions, make a
    Vector4
    compute buffer and fill it with color values. You could also stored the same data in a texture if you want for platforms that don't support compute buffers, but there isn't a significant benefit to using a texture for this over a compute buffer in most cases.

    What do you mean by "the GPU is not utilized"? Unity's profiler and stats window can be confusing, but generally anything it shows you about "rendering" is CPU related and Unity doesn't support always GPU profiling on some platforms so that part of the profiler is blank.
     
    Last edited: May 18, 2021
  10. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Okay, so i only need to send the martix4x4 to the shader, with this i got to things at once (position and rotation). then the only last thing the shader need to know is the id of the object, with this id does the shader know everything about color and texture.

    second question is about, if i hard write some variables to the shader like.

    var id1 = Color.White
    then i need some If-Else in the shader script, is this a problem for performance when a shader has some if-else statements?

    and the last things is


    the red marked FPS drops when iam increase the count of objects. the cpu causes it, and render thread is still the same. but DrawMeshInstancedIndirect should reduce cpu cost and use the gpu right?
     
    Last edited: May 18, 2021
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    The render thread is the CPU time spent setting up rendering. You're side stepping most of that with
    DrawMeshInstancedIndirect()
    because you're doing the work the render thread would be doing yourself in the main thread. There's literally nothing in the stats window about the GPU. Even the FPS is only the CPU side frame rate unless you're using Vsync or hitting cases where the GPU is bogged down to the point that it is sending "woah woah, hold up, you're going too fast and I can't keep up!" messages back to the CPU (which you can see as Gfx.WaitForPresent in the profiler).

    Yeah, don't do that.

    if else statements in a shader aren't strictly evil, though plenty of people will tell you they are (again, this is a decade plus out of date "fact"). However if you have 1000 objects that all need their own unique color value, just have another compute buffer with unique values in it. If you only need a limited number of color values that the objects pick from you certainly could use an if else statement, but you're better off passing in an array of colors to the shader and have the compute buffer be an index into the color array.
     
  12. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Okay thank you so far. iam now need to get the matrix4x4 to work.
    its possible to send a shader some data only single time and use them all the time?

    like i send the shader a list with colors, and using a computebuffer iam sending a index, so the shader can use the list with the index and has the color for the object.

    another question is:
    is a compute buffer with 4 x uint faster/smaller for sending data then a vector4?
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You only need to setup the buffer / array values once if they don't change. For compute buffers you may need to re-assign it to the material each update, as the material and buffer can get disconnected if the application loses focus, but this isn't updating / changing the data in the buffer.

    A
    uint
    is 32 bits. Each component of a
    Vector4
    is 32 bits. There's no difference between sending 4
    uints
    and a
    Vector4
    apart from now you have to convert the
    uint
    values to floats in the shader. If you want to reduce the data you're sending, send only the first 3 rows of the
    Matrix4x4
    and reconstruct the fourth in the shader (it's always
    float4(0,0,0,1)
    ).

    Unity's particle system instancing has some example code in this regard:
    https://github.com/TwoTailsGames/Un...ncludes/UnityStandardParticleInstancing.cginc
    Code (csharp):
    1. struct DefaultParticleInstanceData
    2. {
    3.     float3x4 transform;
    4.     uint color;
    5.     float animFrame;
    6. };
    7.  
    8. StructuredBuffer<UNITY_PARTICLE_INSTANCE_DATA> unity_ParticleInstanceData;
    9.  
    10. UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
    11.  
    12. // transform matrix
    13. objectToWorld._11_21_31_41 = float4(data.transform._11_21_31, 0.0f);
    14. objectToWorld._12_22_32_42 = float4(data.transform._12_22_32, 0.0f);
    15. objectToWorld._13_23_33_43 = float4(data.transform._13_23_33, 0.0f);
    16. objectToWorld._14_24_34_44 = float4(data.transform._14_24_34, 1.0f);
     
  14. FE-Games

    FE-Games

    Joined:
    Jul 1, 2020
    Posts:
    15
    Okay thank you so far, iam currently at holidays. when iam back iam testing it. hope i find it out.