Vertex manipulation problem with objects that contain localRotation or localScale

Greetings. I’ve been trying for some time to make a shader that bends “flat/ortogonal” geometry into a cylindrical shape, but I couldn’t make it to work perfectly. After many trial and errors I managed to make the shader script work as I intended, but only for objects that don’t contain any form of rotation or scale. One workaround was to make a C# script that completely resets any rotated or scaled mesh vertices to origin, using a transform matrix, but this seemed inefficient to do all the time with all scene gameObjects. I understand how object space and world space work, I just don’t understand why my shader is using the local transform of the object’s mesh, when I am ‘clearly’ using a world position based vertex transformations.

Shader "Custom/CylinderBendShader"
{
    Properties
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
         _CurveOrigin ("Curve Origin", Vector) = (0,0,0)
         _Length ("Length", Float) = 36.0 //// Length in X coord, to completely wrap around
        _RadiusMultiplier ("Radius Multiplier", Float) = 1.0
        //_Toggle ("OFF/ON", Range(0, 1)) = 0
    }

    SubShader
    {
        Tags {"Queue"="Transparent" "RenderType"="Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 normal : TEXTCOORD1;
            };

            float4 _Color;
            float3 _CurveOrigin;
            float _Length;
            float _RadiusMultiplier;
            uniform float _Toggle;

            v2f vert(appdata v)
            {
                v2f o;
                float4 wpos = mul(unity_ObjectToWorld, v.vertex);
                half Ydist = wpos.y - _CurveOrigin.y;

                float x,y,z;
                // MAIN CYLINDER WRAPPING PART, I'm not very good at math/trig, I just found this on the web
                x = _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * sin(2 * UNITY_PI * (wpos.x/_Length));
                y = _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * cos(2 * UNITY_PI * (wpos.x/_Length));
                z = wpos.z;

                // I added this by mistake with trial and error, but it's needed to put the geometry in proper place, not sure why
               x += (v.vertex.x - wpos.x);
               y += (v.vertex.y - wpos.y);
               z += (v.vertex.z - wpos.z);

               // just lerping for visual purposes
                float lerpX = lerp(v.vertex.x, x, _Toggle);
                float lerpY = lerp(v.vertex.y, y, _Toggle);
                float lerpZ = lerp(v.vertex.z, z, _Toggle);

                o.pos = UnityObjectToClipPos(float3(lerpX, lerpY, lerpZ));

                o.uv = v.uv;
                o.normal = v.normal;
                o.Ydistance = wpos.y;
                o.Zdistance = wpos.z;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
               ///////FRAG SHADER
            }
            ENDCG
        }
    }
}

4iixrt

I attached the video showing the shader in action. I am lerping between normal state and bended state. Every geometry behaves good, except for the blue cylinder which is rotated and scaled. Doesn’t work properly even if it’s only rotated or only scaled. Only translation seems to work fine.
Thanks for any help in advance. @bgolus

your separation of variables is bothering me, so i cleaned it up for you.

            v2f vert(appdata v)
            {
                v2f o;
                float4 wpos = mul(unity_ObjectToWorld, v.vertex);
                half Ydist = wpos.y - _CurveOrigin.y;

                float3 finalPos;
                // MAIN CYLINDER WRAPPING PART, I'm very good at math/trig, I just found this on the web
                finalPos.x = _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * sin(2 * UNITY_PI * (wpos.x/_Length));
                finalPos.y = _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * cos(2 * UNITY_PI * (wpos.x/_Length));
                finalPos.z = wpos.z;

                // I added this by mistake with trial and error, but it's needed to put the geometry in proper place, not sure why
                finalPos += (v.vertex.xyz - wpos.xyz);

                // just lerping for visual purposes
                finalPos = lerp(v.vertex.xyz finalPos, _Toggle);

                o.pos = UnityObjectToClipPos(finalPos);

                o.uv = v.uv;
                o.normal = v.normal;
                o.Ydistance = wpos.y;
                o.Zdistance = wpos.z;
                return o;
            }

extra credit: only store wpos as a float3 if you never end up using the w component

well, thank you, and sorry it’s ‘bothering’ you, but this didn’t really help… I already cleaned the code up a lot, since I have hundreds of commented lines, where I am trying different ways to achieve the desired effect and solve some of my problems…

Edit: I’ve just noticed the cheeky little comment edit :stuck_out_tongue:

1 Like

sorry, not trying to be rude i just thought it might be useful for you to use one lerp instead of 3 etc. as for your actual problem, i’m not sure what the cause is as i’ve never experienced the matrix giving wrong results. i’m also not sure that what invertex said matters because as far as i know the vertex contains 1 in the w component already. at least, in all my tests of rotating scaling etc i couldn’t reproduce your problem.

glad you liked the comment :wink:

1 Like

I just couldn’t go to sleep until I figured this out… I just didn’t understand in which order I have to transform the vertices, to which state (object, world) and when to apply the wrapping formula… but I finally did it… and it works… and it’s such a nice feeling…
First problem I noticed in my first post was that I wasn’t even converting anything back to object space before sending it to the frag shader… then I knew that I first have to move vertices to origin before I can apply any form of transformation… but I was caught up in my own confusion, as to which transform matrix to do this in, either apply this to the vertex data directly or work in world space… and when do I convert back…
But I’m glad it’s done now… at least for now…
I’ve been reading this forum for days, before and after posting, and I owe it to @bgolus and his patience to teach strangers on the internet the intricate art of shader coding… he goes in depth with many details that you cannot find anywhere else; so for that I want to thank you, if you ever get to see this message… cheers!
And thank you also @POOKSHANK for the emotional support :stuck_out_tongue:

v2f vert(appdata v)
            {
                v2f o;
                float3 wpos = mul(unity_ObjectToWorld, v.vertex);
                half Ydist = wpos.y - _CurveOrigin.y;

                float3 finalPos =  _CurveOrigin - wpos;

                // MAIN CYLINDER WRAPPING PART, I'm the best at math/trig, I just found this on the web
                finalPos.x +=  _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * sin(2 * UNITY_PI * (wpos.x/_Length));
                finalPos.y +=  _RadiusMultiplier * Ydist *_Length/(2 * UNITY_PI) * cos(2 * UNITY_PI * (wpos.x/_Length));
                finalPos.z += wpos.z;

                finalPos = mul(unity_WorldToObject, finalPos);
                finalPos = v.vertex.xyz + finalPos;

                // just lerping for visual purposes
                finalPos = lerp(v.vertex.xyz, finalPos, _Toggle);
                o.pos = UnityObjectToClipPos(finalPos);
                o.uv = v.uv;
                o.normal = v.normal;
                o.Ydistance = wpos.y;
                o.Zdistance = wpos.z;
                return o;
            }
1 Like

glad you could become the best at trig :wink: