EPIC bounty-Unity, модифицируйте стандартный шейдер, чтобы случайно перевернуть текстуру?

представьте себе сцену с десятью простыми "кубиками" все равно.

каждый из них имеет тот же материал "M1", который имеет простой стандартный шейдер, и простой PNG в качестве текстуры.

Если вы идете к материалу и регулируете плитку,

enter image description here

enter image description here

вы можете удобно перевернуть текстуры для различных взглядов.

обратите внимание, что, конечно, это изменится все десять!--20--> из кубов все сразу.

можно ли изменить стандартный шейдер, чтобы просто

  • на каждом объекте, который использует материал

  • шейдер случайным образом изменяет плитку (и, скажем, смещение)

  • опять же, я имею в виду базис"на объект"; в Примере каждого из десяти будет случайно отличающийся.

(Итак: один куб будет показывать плитки -1,1, один куб будет показывать плитки -1, -1 и так далее ... каждый куб отличается, хотя все используют то же самое материал, существует только один материал.)

Примечание

(1) совершенно тривиально генерировать несколько разных версий материала, каждая с разной плиткой, и случайным образом выбирать одну из них для каждого куба. так не пойдет!--1-->

(2) Обратите внимание, что если вы различаетесь материал, он, конечно, делает не одну копию. вы не можете иметь тысячи и тысячи материалов.

решение есть (один) шейдер, который знает варьировать (скажем, случайно) смещение, внутри шейдера - на каждом объекте работает. (т. е. на основе конкретного объекта)

вот об этом я и спрашиваю.

4 ответов


я заметил, что вы действительно хотите сделать это только с шейдером. Вы не можете сделать это с помощью шейдеров. Проблема заключается в невозможности генерировать случайное число в шейдере. проблема заключается в том, чтобы сделать это один раз. Я не нашел способа сделать это, и не думаю, что вы сможете.

это проблема, которая должна решаться с кодом на C# стороне.

(1) совершенно тривиально генерировать несколько разных версий материал, каждый с различной плиткой, и случайным образом Выберите один из них для каждого куба. так не пойдет!--10-->

(2) Обратите внимание, что если вы меняете материал, он, конечно, делает более одного копировать. вы не можете иметь тысячи и тысячи материалов.

не проблема вообще. Это MaterialPropertyBlock используется для. Это позволяет изменять свойство шейдера без создания нового экземпляра этого материала.

"используйте его в ситуациях, когда вы хотите нарисовать несколько объектов с одним и тем же материалом, но немного разными свойствами." MaterialPropertyBlock

приведенный ниже код приведет к созданию многих экземпляров материала:

void Start()
{
    MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
    int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
    int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
    Vector2 tile = new Vector2(tileX, tileY);
    meshRenderer.material.SetTextureScale("_MainTex", tile);
}

С MaterialPropertyBlock этот вопрос решается. Материальная копия не производится. Поскольку вы заботитесь о производительности, вы должны использовать Shader.PropertyToID:

void Start()
{
    int propertyID = Shader.PropertyToID("_MainTex_ST");
    meshRenderer = gameObject.GetComponent<MeshRenderer>();
    int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
    int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
    Vector2 tile = new Vector2(tileX, tileY);

    MaterialPropertyBlock matPropBlock = new MaterialPropertyBlock();
    //Get the current MaterialPropertyBlock settings
    meshRenderer.GetPropertyBlock(matPropBlock);
    //Assign the new tile value
    matPropBlock.SetVector(propertyID, tile);
    //matPropBlock.SetVector(Shader.PropertyToID("_MainTex_ST"), tile);
    //Apply the modified MaterialPropertyBlock back to the renderer
    meshRenderer.SetPropertyBlock(matPropBlock);
}

если это делается после того, как в игре, на самом деле не имеет смысла прикреплять скрипт к каждому GameObject. Просто прикрепите его t только к одному пустому GameObject, найдите все объекты по тегу, затем запустите код на каждом из них.

void Start()
{
    GameObject[] objs = GameObject.FindGameObjectsWithTag("YourObjTag");
    int propertyID = Shader.PropertyToID("_MainTex_ST");
    for (int i = 0; i < objs.Length; i++)
    {
        MeshRenderer meshRenderer = objs[i].GetComponent<MeshRenderer>();
        int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
        int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
        Vector2 tile = new Vector2(tileX, tileY);

        MaterialPropertyBlock matPropBlock = new MaterialPropertyBlock();
        //Get the current MaterialPropertyBlock settings
        meshRenderer.GetPropertyBlock(matPropBlock);
        //Assign the new tile value
        matPropBlock.SetVector(propertyID, tile);
        //Apply the modified MaterialPropertyBlock back to the renderer
        meshRenderer.SetPropertyBlock(matPropBlock);
    }
}

Pseudorandomly flipped

Shader "RandomFlip"
{
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
    Tags { "RenderType"="Opaque" "DisableBatching"="True" }
    LOD 100

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fog

        #include "UnityCG.cginc"

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

        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
        };

        sampler2D _MainTex;
        float4 _MainTex_ST;

        v2f vert (appdata v)
        {
            // position of pivot in world space
            float3 pivotWorldPos = float3( unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w );

            // randomness achieved by feeding trigonometry function with large numbers
            float flipHorizontally = sin( ( pivotWorldPos.x + pivotWorldPos.y + pivotWorldPos.z ) * 1000 ) > 0;
            float flipVertically = cos( ( pivotWorldPos.x + pivotWorldPos.y + pivotWorldPos.z ) * 1000 ) > 0;

            // randomly flipping uvs 
            float2 uv = lerp( v.uv, float2( 1.0 - v.uv.x, v.uv.y ), flipVertically );
            uv = lerp( uv, float2( uv.x, 1.0 - uv.y ), flipHorizontally ); 

            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(uv, _MainTex);
            UNITY_TRANSFER_FOG(o,o.vertex);
            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);
            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }
        ENDCG
    }
}
}

хотя вам лучше не перемещать эти объекты: D, потому что случайность здесь исходит из положения объекта в мировом пространстве. Дозирование также нарушит случайность.


Если вы хотите перевернуть, используйте alpha = 0 для этих объектов, а для других alpha = 1.

в данных вершин вашего шейдера напишите что-то вроде этого:

half flip = 1 -2*вершина.a;

таким образом, флип будет -1 или 1.

из комментариев:

enter image description here

обратите внимание, однако, что каждый раз, когда вы получаете доступ .mesh, в Unity вы делаете совершенно новую сетку:

public class TestFlipUV : MonoBehaviour
    {
        private void Awake()
        {

            Mesh mesh = GetComponent<MeshFilter>().mesh;
            // NOTE. IN UNITY, ACCESSING .mesh CREATES
            // A NEW INSTANCE OF THAT MESH EVERY TIME

            Vector2[] uv2 = mesh.uv;

            float rnd = Random.RandomRange(0.0f, 1.0f);
            if (rnd > 0.5f)
            {
                for (int i = 0; i < uv2.Length; i++)
                {
                    uv2[i].x *= -1;    
                }
            }

            rnd = Random.RandomRange(0.0f, 1.0f);
            if (rnd > 0.5f)
            {
                for (int i = 0; i < uv2.Length; i++)
                {
                    uv2[i].y *= -1;    
                }
            }
            mesh.uv2 = uv2;
        }
    }

... и затем...

Shader "Unlit/FlipUV"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.uv *= v.uv2;
                return o;
            }

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

я опубликую это как ответ, вы можете попробовать и посмотреть, работает ли это для вас. (Если нет, возможно, кто-то еще найдет это решение полезным). Плитка может быть изменена с SetTextureScale

прикрепить для каждого куба.

public class MatTilingChange : MonoBehaviour {
    MeshRenderer mr;
    int[] vals = new int[] { -1, 1 };

    private void Awake()
    {
        mr = GetComponent<MeshRenderer>();
    }

    void Start () {
        mr.material.SetTextureScale("_MainTex", new Vector2(vals[Random.Range(0, 2)], 
vals[Random.Range(0, 2)]));
    }
}