Вращение градиентного шейдера? - PullRequest
0 голосов
/ 13 ноября 2018

У меня есть шейдер, который позволяет мне создавать и вращать 2 или 3 цветовых градиента. Моя проблема заключалась в том, что он был очень тяжелым на GPU, поэтому я переместил эту часть кода из фрагментного шейдера в вершинный шейдер:

 fixed4 frag (v2f i) : SV_Target
            {   
                //STARTS HERE
                float2 uv =  - (i.screenPos.xy / i.screenPos.w - 0.5)*2;

                fixed3 c;                        
                #if _BG_COLOR_GRADIENT2
                    c = lerp(_BgColor1,_BgColor3,clampValue(rotateUV(uv.xy,_BgColorRotation*PI).y,_BgColorPosition));                                            
                #elif _BG_COLOR_GRADIENT3
                    c = lerp3(_BgColor1,_BgColor2,_BgColor3,clampValue(rotateUV(uv.xy,_BgColorRotation*PI).y,_BgColorPosition),_BgColorPosition3);
                #endif 
                //ENDS HERE         

                return fixed4(c, i.color.a);
            }

Теперь мой шейдер выглядит так:

Shader "Custom/Gradient"
{
    Properties
    {
        [KeywordEnum(Gradient2, Gradient3)] _BG_COLOR ("Color Type", Float) = 1
        _Color("Color", Color) = (1, 1, 1, 1)
        _BgColor1 ("Start Color",Color) = (0.667,0.851,0.937,1)
        _BgColor2 ("Middle Color",Color) = (0.29, 0.8, 0.2,1)
        _BgColor3 ("End Color",Color) = (0.29, 0.8, 0.2,1)
        [GradientPositionSliderDrawer]
        _BgColorPosition ("Gradient Position",Vector) = (0,1,0)
        _BgColorRotation ("Gradient Rotation",Range(0,2)) = 0
        _BgColorPosition3 ("Middle Size",Range(0,1)) = 0

    }

    SubShader
    {
        Tags{ "Queue" = "Background" "IgnoreProjectors"="True" }

        Blend SrcAlpha OneMinusSrcAlpha
        AlphaTest Greater .01
        ColorMask RGB
        Cull Off Lighting Off ZWrite Off
        BindChannels {
        Bind "Color", color
        Bind "Vertex", vertex
        Bind "TexCoord", texcoord
    }

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #pragma shader_feature _BG_COLOR_GRADIENT2 _BG_COLOR_GRADIENT3

        #include "UnityCG.cginc"
        #include "GradientHelper.cginc"

        struct appdata
        {
           float4 vertex : POSITION;
           fixed4 color : COLOR;
        };

        struct v2f
        {                
            float4 pos : SV_POSITION;
            float4 screenPos : TEXCOORD4;
            fixed4 color : COLOR;
        };

        fixed4 _BgColor1;
        fixed4 _BgColor2;
        fixed4 _BgColor3;
        float _BgColorRotation;
        float2 _BgColorPosition;
        float _BgColorPosition3;
        float4 _Color;

        v2f vert (appdata v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.screenPos = ComputeScreenPos(o.pos);

            float2 uv =  - (o.screenPos.xy / o.screenPos.w - 0.5)*2;                         

            #if _BG_COLOR_GRADIENT2
                o.color = lerp(_BgColor1,_BgColor3,clampValue(rotateUV(uv.xy,_BgColorRotation*PI).y,_BgColorPosition)) * v.color;                                            
            #elif _BG_COLOR_GRADIENT3
                o.color = lerp3(_BgColor1,_BgColor2,_BgColor3,clampValue(rotateUV(uv.xy,_BgColorRotation*PI).y,_BgColorPosition),_BgColorPosition3) * v.color;
            #endif

            return o;
        }

        fixed4 frag (v2f i) : COLOR {
            return i.color;
        }
        ENDCG
        }
    }
    CustomEditor "Background.Editor.BackgroundGradientEditor"
}

(Вот мой помощник шейдера):

#ifndef PI
#define PI 3.141592653589793
#endif
#ifndef HALF_PI
#define HALF_PI 1.5707963267948966
#endif

// Helper Funtions

inline float clampValue(float input, float2 limit) 
{
    float minValue = 1-limit.y;
    float maxValue = 1-limit.x;
    if(input<=minValue){
        return 0;
    } else if(input>=maxValue){
        return 1;
    } else {
        return (input - minValue )/(maxValue-minValue);
    }                       
}

inline float2 rotateUV(fixed2 uv, float rotation) 
{
    float sinX = sin (rotation);
    float cosX = cos (rotation);
    float2x2 rotationMatrix = float2x2(cosX, -sinX, sinX, cosX);
    return mul ( uv, rotationMatrix )/2 + 0.5;
}

inline fixed4 lerp3(fixed4 a, fixed4 b, fixed4 c, float pos, float size){

    float ratio2 = 0.5+size*0.5;
    float ratio1 = 1-ratio2;
    if(pos<ratio1)
        return lerp(a,b,pos/ratio1);
    else if(pos>ratio2)
        return lerp(b,c,(pos-ratio2)/ratio1);               
    else
        return b;
}    
#endif

Производительность сейчас великолепна, но вращение полностью запутано (особенно заметно при 3-цветном градиенте), и я не могу понять, почему.

1 Ответ

0 голосов
/ 17 января 2019

Я никогда не понимаю, почему люди хотят делать свои градиенты внутри шейдера, он довольно ограничен и не обязательно более производительный, если вы не меняете значения в каждом кадре. Моим лучшим решением для этого было бы сгенерировать градиент в виде текстуры на процессоре, размером 1x128. Используйте класс Gradient, предоставляемый Unity, и цикл:

Texture2D texture = new Texture2D(128, 1);
Color[] pixels = Color[128];
for (int i = 0; i < 128; i++) {
    pixels[i] = gradient.Evaluate(i/127f);
}
texture.SetPixels(pixels);
texture.Apply();

Отправьте его в шейдер, используя:

material.SetTexture("_Gradient", texture)

Затем вы можете вращать и прокручивать эту текстуру по своему усмотрению, используя матрицу 2x2, как вы сделали. Просто убедитесь, что режим переполнения текстуры зафиксирован, а не повторяется. Помните, что вы можете внедрить OnValidate () в свое поведение, чтобы применить обновления значений в редакторе, хотя, если вам нужно обновить его в сборке, вам нужно будет прослушать изменения другим способом.

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

...