I have plane that I want to roll up using a vertex shader, like this:


I found a math demo that shows the kind of curve I want my mesh to follow. I tried implementing this in my shader code, but the results don't form a roll as expected:

Shader " Custom/Rolling "
_MainTex ("Base (RGB)", 2D) = "white" {}
_Bend("Bend",Float) = 1
_Strength("Strength",Float) = 1

Tags {"Queue"="Geometry" "IgnoreProjector"="true" "RenderType"="Opaque"}
Cull Off

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

struct v2f
half2 texcoord  : TEXCOORD0;
float4 vertex   : SV_POSITION;

sampler2D _MainTex;
float _Bend;
float _Strength;

v2f vert(appdata_full v)
v2f o;

//I don't know what should I write here for rolling ,please help me.
//v.vertex.y *= ???
v.vertex.y *= exp(_Bend+(v.vertex.x*_Strength)); // something like this but this hasn't twist effect
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = v.texcoord;
return o;

float4 frag (v2f i) : COLOR

float2 uv = i.texcoord.xy;
float4 tex = tex2D(_MainTex, uv);

return tex;
Fallback Off

Instead of a neat roll, the end of my plane shoots up into the sky!


  • \$\begingroup\$ Use the formula from the desmos link to perform the operation from a script (changing the Mesh during Update()). Once it works, figure out if the performance is fine, if it isn't you can move it to the shader. \$\endgroup\$
    – Peter
    Commented Dec 8, 2017 at 10:21
  • \$\begingroup\$ @Peter thanks but I believe shader is better way , i like to implement rolling by shader , anyway i don't know how can I apply desmos formula. \$\endgroup\$ Commented Dec 8, 2017 at 10:57
  • \$\begingroup\$ You won't exactly lose any time by doing it from script first. The big part is creating the mesh and deciding how you want to "bind" the mesh to the transformation, which is easier to code and debug from script, and quite easy to translate afterwards.l To translate the formula, either use one of the plane's coordinates (e.g. x) as p from the desmos formula (which requires you to always calculate the transformed model from the same original model), or set uv3 to p while creating the model (which allows you to work with a single model which you continuously transform). \$\endgroup\$
    – Peter
    Commented Dec 8, 2017 at 11:07
  • \$\begingroup\$ Nice representation. \$\endgroup\$ Commented Dec 8, 2017 at 15:57
  • \$\begingroup\$ Hey did you figure it out ? \$\endgroup\$ Commented Apr 2, 2020 at 23:53

1 Answer 1


Here I'm going to assume your geometry is set up like so:

  • It's a ribbon of quads extending from z = 0 to z = end

  • The z = 0 line will be the outer lip of the roll, and z = end will be the part tucked deepest inside the spiral

  • Each quad in the ribbon will span an equal angle when coiled in the roll (but not an equal length of ribbon: the quads in the tightest part of the roll will be smaller) - this gets us a consistent resolution for drawing our curves, and we'll correct for the texture stretching in the shader

  • The ribbon is 1 unit wide along the x axis

  • The UVs are set up with u/x running across the ribbon, and v/y pointing down the ribbon

  • The origin of your object represents the point where the roll should contact the floor

  • The roll will be centered somewhere above this point (depending on how many windings are left on it), and the unwound portion will extend in the local -z direction.

We can draw this with a shader like so, using the formula for arc length along an archimedean spiral that I explain in this answer:

Unity demo showing a rainbow ribbon spooling and unspooling

Shader "Custom/Roll"
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _StartAngle("Start Angle (Radians)", float) = 60.0
        _AnglePerUnit("Radians per Unit", float) = 0.2
        _Pitch("Pitch", float) = 0.02
        _UnrolledAngle("Unrolled Angle", float) = 1.5
        Tags { "RenderType"="Opaque" }
        LOD 200

        Cull Off

        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows vertex:vert

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
            float2 uv_MainTex;
            float3 viewDir;

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _StartAngle;
        float _AnglePerUnit;
        float _Pitch;
        float _UnrolledAngle;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
            // put more per-instance properties here

        float arcLengthToAngle(float angle) {
            float radical = sqrt(angle * angle + 1.0f);
            return _Pitch * 0.5f * (angle * radical + log(angle + radical));

        void vert(inout appdata_full v) { 
            float fromStart = v.vertex.z * _AnglePerUnit;

            float fromOrigin = _StartAngle - fromStart;
            float lengthToHere = arcLengthToAngle(fromOrigin);
            float lengthToStart = arcLengthToAngle(_StartAngle);

            v.texcoord.y = lengthToStart - lengthToHere;

            if (fromStart < _UnrolledAngle) {
                float lengthToSplit = arcLengthToAngle(_StartAngle - _UnrolledAngle);
                v.vertex.z = lengthToSplit - lengthToHere;
                v.vertex.y = 0.0f;
                v.normal = float3(0, 1, 0);
            else {
                float radiusAtSplit = _Pitch * (_StartAngle - _UnrolledAngle);
                float radius = _Pitch * fromOrigin;

                float shifted = fromStart - _UnrolledAngle;

                v.vertex.y = radiusAtSplit - cos(shifted) * radius;
                v.vertex.z = sin(shifted) * radius;

                v.normal = float3(0, cos(shifted), -sin(shifted));

        void surf (Input IN, inout SurfaceOutputStandard o)
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;

            o.Normal *= sign(dot(IN.viewDir, o.Normal));
    FallBack "Diffuse"

Note that the arc length to the start and arc length to the split/unwinding point are constant for all vertices in a frame, so for efficiency you can pre-compute these in a script and pass them in as uniforms instead.

Here's an example of it working on a single 10x10 Unity plane. Obviously, to have a longer ribbon/tighter spool, you'll need more triangles to represent it smoothly like in the example above. You can get that by assembling multiple planes, or generating your own mesh exactly to your specifications with the Mesh class. If you go this route, you can even bake-in the arcLengthToHere as a texture coordinate, to eliminate more transcendental math from the vertex shader. :)

Example on a single plane

  • \$\begingroup\$ Hi DMGregory thanks.can you send it as a package because It didn't work on plane because of vertex numbers.If it works I will accept your answer. \$\endgroup\$ Commented Apr 4, 2020 at 5:35
  • \$\begingroup\$ Those are planes you see in my gif (about 8 of them stacked end-to-end: I dumped their vertices into world space then transformed them from there for a quick demo, rather than making a custom long ribbon mesh myself). What specific vertex number issue did you encounter? We can likely address that in an answer that can help other users too, rather than via a package that benefits you alone. \$\endgroup\$
    – DMGregory
    Commented Apr 4, 2020 at 5:38
  • \$\begingroup\$ I mean simple plane has few vertices so it is low poly and it didn't work correctly.ok give me address. \$\endgroup\$ Commented Apr 4, 2020 at 5:41
  • \$\begingroup\$ If you want a smooth curve with lots of winding, you'll need more vertices. A vertex shader can't magic more geometry out of nowhere. So I'd recommend using a mesh with the density you need. You could also use tessellation to generate more density on demand on the GPU, but I'd argue that's more complexity than this situation calls for. \$\endgroup\$
    – DMGregory
    Commented Apr 4, 2020 at 5:57
  • \$\begingroup\$ I know this issue but as I haven't access to modeling software I can't increase vertices right now.I just want to make sure that your answer is correct and then I accept your answer. \$\endgroup\$ Commented Apr 4, 2020 at 5:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .