Rotating mesh in vertex shader

I am trying to rotate a mesh with vertex shader.
I found this snippet and I am using it to rotate the mesh:

And in the vertex shader I do this, where v is the input struct and o is the output:

Problem is it is rotating around the world axis and not the mesh pivot. Why is it so if I am doing it before doing the UnityObjectToClipPos? I should be in object space. What am I missing?

Is your mesh dynamically batched or part of a canvas?

1 Like

Thanks! That was the problem, it was dynamically batched.

For completeness, in case somebody is looking to do the same, to disable dynamic batching only for some objects and not globally you can add a tag in the shader “DisableBatching” = “True” .

3 Likes

@00christian00 this is really interesting - any chance you could post your shader code up here, as I’m not sure how to put the rest of this together to rotate the mesh?

1 Like

I’m not a shader expert to give advice, but anyway here is it. If you need to move automatically you multiply angle per _Time.y at row 49. In that case you will have angle per seconds as input.

Shader "DiffuseRotate"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
        _Angle ("Angle", float) = 1
    }
    SubShader
    {
        Tags {"LightMode"="ForwardBase"}
        Tags { "RenderType"="Opaque" "DisableBatching" = "True"  }

        Pass
        {

            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            // compile shader into multiple variants, with and without shadows
            // (we don't care about any lightmaps yet, so skip these variants)
            #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
            // shadow helper functions and macros
            #include "AutoLight.cginc"
        uniform float _Angle;
            struct v2f
            {
                float2 uv : TEXCOORD0;
                SHADOW_COORDS(1) // put shadows data into TEXCOORD1
                fixed3 diff : COLOR0;
                fixed3 ambient : COLOR1;
                float4 pos : SV_POSITION;
            };
            float4 RotateAroundYInDegrees (float4 vertex, float degrees)
                 {
                     float alpha = degrees * UNITY_PI / 180.0;
                     float sina, cosa;
                     sincos(alpha, sina, cosa);
                     float2x2 m = float2x2(cosa, -sina, sina, cosa);
                     return float4(mul(m, vertex.xz), vertex.yw).xzyw;
                 }
        
            v2f vert (appdata_base v)
            {
                v2f o;
                v.vertex = RotateAroundYInDegrees(v.vertex, _Angle);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                o.diff = nl * _LightColor0.rgb;
                o.ambient = ShadeSH9(half4(worldNormal,1));
                // compute shadows data
                TRANSFER_SHADOW(o)
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
                fixed shadow = SHADOW_ATTENUATION(i);
                // darken light's illumination with shadow, keep ambient intact
                fixed3 lighting = i.diff * shadow + i.ambient;
                col.rgb *= lighting;
                return col;
            }
            ENDCG
        }

        // shadow casting support
        UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"

    }

}
3 Likes

@00christian00 - that’s awesome and many thanks - will check it out as soon as I can!

1 Like

Pump!
Hi @00christian00 , Did you figure how to do this? I’m having the same problem.
I think the first thing to do is figure out how to get the pivot point of each mesh in particles.
Thanks.

Particles are a different beast. By default particle systems are always batched meshes regardless of if you have disable batching in your shader. Particle systems simply had no other way of working.

Recently they added custom data streams, which includes the option to pass the particle’s position to each particle in the form of data stored in the mesh UVs. They also added support for instanced particles which give you this data even more readily. You’ll want to look at how the “Particles/Standard Unlit” shader works.

1 Like

Doesn’t the shader I posted works for you?
I didn’t work on it anymore.

Not exactly, I’m looking for a way to rotate the particle mesh around its pivot like your first post mention

The problem can be visualized like this

I see there is a Center Vertex stream, I guess this is the Center of each mesh particle

If this is the center of the mesh, then this is the pivot I want to rotate around. But for now just assume this is the center of the mesh, how can I rotate around this center?
Here is my approach:
I generate the rotation matrix.

float3x3 YRotationMatrix(float degrees, float3 pivot)
            {
                float alpha = degrees * UNITY_PI / 180.0;
                float s = sin(alpha);
                float c = cos(alpha);
               //But how can I insert the pivot???
                return float3x3(
                    c, 0, -s,
                    0, 1, 0,
                    s, 0, c);
            }

appdata_t vert (appdata_t v)
{
float4 pos =  v.vertex;
//v.texcoord1.yzw is the pivot
pos.xyz = mul(YRotationMatrix(180,v.texcoord1.yzw), pos.xyz);

Hi Nathan, Have you ever thought about move the center (vertex) to the root (0,0,0) then rotate the vertex, then move it back?

Thank Nathan, I finally figure it out. And my guess is right: Center Vertex stream is the Center of each mesh particle

          float3x3 YRotationMatrix(float degrees)
            {
                float alpha = degrees * UNITY_PI / 180.0;
                float sina, cosa;
                sincos(alpha, sina, cosa);
                return float3x3(
                    cosa, 0, -sina,
                    0, 1, 0,
                    sina, 0, cosa);
            }

            appdata_t vert (appdata_t v)
            {
                float4 pos = v.vertex;
                float3 pivot = v.texcoord1.yzw; //Vertex Stream Center

               //Move to root
               pos.xyz -= pivot;
              
               //Rotate
               pos.xyz = mul(YRotationMatrix(180), pos.xyz);
              
               //Move it back
               pos.xyz += pivot;

                //Done! now return the result
                appdata_t o;
                o.vertex = UnityObjectToClipPos(pos);

                return o;
            }

Problem solved! :smile:

That issue was solved disabling batching for that shader. The code I posted should have been working.

Tags { "RenderType"="Opaque" "DisableBatching" = "True"  }

I think this way is more performant than using vertex stream, since you are not wasting bandwidth between cpu and shader.

Except, as I stated above, this tag is ignored by particle systems. They are always batched unless you’re using a shader with particle system specific instancing code.

Awsome man! Good job! Would you mind posting the full code? Having troubles my self lol

Check the full code here: https://discussions.unity.com/t/717131

1 Like

Hey, thanks!! Perfect timing haha. Im still stuck on this XD