9
\$\begingroup\$

I used a vertex shader (based on this example) to animate a flag waving. When I have a single flag in my scene, it works correctly.

When I duplicate the flag, all of the copies become wildly distorted after I hit play.

Animation showing normal behaviour and distortion after duplicating.

Here is the shader code I'm using:

Shader "Custom/Flag" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Speed ("Speed", Range(0, 5.0)) = 1
        _Frequency ("Frequency", Range(0, 1.3)) = 1
        _Amplitude ("Amplitude", Range(0, 5.0)) = 1
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        Cull off

        Pass {

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            float _Speed;
            float _Frequency;
            float _Amplitude;

            v2f vert(appdata_base v)
            {
                v2f o;
                v.vertex.y +=  cos((v.vertex.x + _Time.y * _Speed) * _Frequency) * _Amplitude * (v.vertex.x - 5);
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv);
            }

            ENDCG

        }
    }
    FallBack "Diffuse"
}

How can I make this work with multiple flags using the same material?

Should I use a new material instance for each object like this? It doesn't seem optimal:

    material = new Material (Shader); // Create new material
    material.SetFloat ("_Speed", _Speed);
    material.SetFloat ("_Frequency", _Frequency);
    material.SetFloat ("_Amplitude", _Amplitude);
    material.SetTexture ("_MainTex", _MainTex);
    flagObj.GetComponent<MeshRenderer>().sharedMaterial = material; // Set values
\$\endgroup\$

1 Answer 1

9
\$\begingroup\$

This is Unity's automatic batching systems at work.

In order to save you draw calls, when Unity sees multiple (small) models using the same material, it combines them into a single mesh so it can render them all together.

To retain each model's individual transformation (translation/rotation/scale) in space, the transforms are baked into the vertex positions in a single shared coordinate system for the whole batch, which might differ greatly from the model's original local coordinate system.

This is usually safe for most materials, since we usually don't care about where our geometry is in local space, only its resulting position in the camera view / orientation to light sources.

But it can wreak havoc with vertex shaders that make assumptions about how the vertices are laid out in the model's local coordinate system — like vertex animations that displace vertices in local space, or scale their intensity based on local position.

A quick fix for these shaders is to add the tag "DisableBatching"="True" to the ShaderLab tags at the top of the subshader. This tells Unity that this material is not safe to batch, and it will go back to rendering each model individually with its original coordinate system, at some draw call cost.

If you want to render a lot of these models and still get the benefit of batching, you can modify some shaders to use other sources of information here (eg. using the vertex normal as a displacement direction rather than assuming it points along a particular local axis, or using UV coordinates to scale and offset the effect rather than local position). If you need an additional source of input, you can add a vertex colour or additional UV coordinate channel. These attributes are not modified when batching, so your effect won't change when batched together (though adding more vertex attributes means the maximum number of models that can be batched together will be smaller, since each uses more data now)

\$\endgroup\$
5
  • \$\begingroup\$ What if all the objects in the batch have the same transformation, is there a better way to pass that info to all the batch objects at once rather than passing it individually per object? \$\endgroup\$
    – Serg
    Commented Feb 2, 2021 at 8:06
  • \$\begingroup\$ Sounds like you might want to post a new question explaining your use case for drawing multiple objects at exactly the same position/rotation/scale. \$\endgroup\$
    – DMGregory
    Commented Feb 2, 2021 at 10:39
  • \$\begingroup\$ Sorry, I put it wrong. Actually, all the objects have different positions but the same rotation. The shader does some calculations depending on the rotation. Maybe the answer is just "impossible"? \$\endgroup\$
    – Serg
    Commented Feb 2, 2021 at 18:37
  • \$\begingroup\$ Sounds like you might want to post a new question showing the shader you need help batching, its intended output, and the wrong output you're getting currently. \$\endgroup\$
    – DMGregory
    Commented Feb 2, 2021 at 18:39
  • \$\begingroup\$ Posted: gamedev.stackexchange.com/questions/188851/… \$\endgroup\$
    – Serg
    Commented Feb 3, 2021 at 22:14

You must log in to answer this question.

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