Unity, изменить стандартный шейдер, чтобы случайно перевернуть текстуру? - PullRequest
0 голосов
/ 29 августа 2018

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

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

Если перейти к материалу и настроить плитку,

enter image description here

enter image description here

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

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

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

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

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

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

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

Примечание,

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

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

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

Вот о чем я здесь спрашиваю.

Ответы [ 4 ]

0 голосов
/ 10 сентября 2018

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 Потому что случайность здесь исходит из положения объекта в мировом пространстве. Пакетирование также нарушит случайность.

0 голосов
/ 31 августа 2018

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

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

half flip = 1 -2 * vertex.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
        }
    }
}
0 голосов
/ 31 августа 2018

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

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

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

(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. Просто прикрепите его только к одному пустому 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);
    }
}
0 голосов
/ 29 августа 2018

Я опубликую это как ответ, вы можете попробовать и посмотреть, работает ли он для вас. (Если нет, возможно, кто-то найдет это решение полезным). Плитка может быть изменена с 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)]));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...