Unity 3d Sprite Shader (как мне ограничить максимальную яркость до 1 при включении нескольких источников света)

#unity3d #shader #hlsl #fragment-shader #pixel-shader

#unity-игровой движок #шейдер #hlsl #фрагмент-шейдер #пиксельный шейдер

Вопрос:

Я создаю видеоигру в Unity. Каждый спрайт отрисовывается с помощью средства визуализации спрайтов с материалом, содержащим CornucopiaShader.shader. Проблема, с которой я сталкиваюсь, в том, что я хочу ограничить максимальную яркость (или цвет) спрайта, чтобы он был просто обычным изображением спрайта, независимо от мощности того, сколько точечных источников света попадает на него, интенсивности источников света, а также окружающего освещения в сцене unity. Когда интенсивность источников света, попадающих на спрайт, ниже этого максимального уровня яркости, я хочу, чтобы он действовал как обычный спрайт с подсветкой и был черным, если на него не попадает свет, и был наполовину подсвечен, если на него попадает интенсивность 0,5 и т.д., И все, что между ними, как обычно. Проблема 1: Подводя итог, если на спрайт попадают три источника света, скажем, с интенсивностью 5, я хочу, чтобы спрайт выглядел нормальной яркостью, равной 1, а не размытым белым светом.

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

Я пытаюсь решить эти две проблемы 3 дня подряд и не могу в этом разобраться.

Рисунок 1

Рисунок 2

Рисунок 3

Рисунок 4

 Shader "Custom/CornucopiaShader" {

Properties{
    _MainCol("Main Tint", Color) = (1,1,1,1)
    _MainTex("Main Texture", 2D) = "white" {}
    _Cutoff("Alpha cutoff", Range(0,0.5)) = 0.5
}

SubShader{
    Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane"}
    Cull Off
    ZWrite Off
    LOD 200
    ColorMask RGB

    Blend SrcAlpha OneMinusSrcAlpha

    CGPROGRAM
    #pragma surface surf SimpleLambert alphatest:_Cutoff addshadow fullforwardshadows alpha:blend
    #pragma target 3.0

    #include "RetroAA.cginc"

    sampler2D _MainTex;
    float4 _MainTex_TexelSize;
    fixed4 _MainCol;

    half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten)
    {
        half4 c;

        c.rgb = s.Albedo * _MainCol.rgb * (atten)* _LightColor0.rgb;
        c.a = s.Alpha;

        return c;
    }

    struct Input {
        float2 uv_MainTex;
    };

    void surf(Input IN, inout SurfaceOutput o) {

        fixed4 c = RetroAA(_MainTex, IN.uv_MainTex, _MainTex_TexelSize);
        o.Albedo = lerp(c.rgb, c.rgb, c.a);
        o.Alpha = c.a;

    }
    ENDCG
}

Fallback "Transparent/Cutout/VertexLit"
  

}

 #include "UnityCG.cginc"

#pragma target 3.0

fixed4 RetroAA(sampler2D tex, float2 uv, float4 texelSize){
    float2 texelCoord = uv*texelSize.zw;
    float2 hfw = 0.5*fwidth(texelCoord);
    float2 fl = floor(texelCoord - 0.5)   0.5;
    float2 uvaa = (fl   smoothstep(0.5 - hfw, 0.5   hfw, texelCoord -     fl))*texelSize.xy;

return tex2D(tex, uvaa);
}
  

Ответ №1:

На самом деле вы не можете сделать это с помощью поверхностных шейдеров, но вы можете сделать это очень эффективно с помощью шейдеров вершинных фрагментов. Unity хранит 4 ближайших точечных источника света в наборе векторов, которые будут использоваться для каждого вершинного (не важного) источника света. К счастью, они также доступны в шейдере фрагментов, так что вы можете использовать их для затенения всех 4 источников света одновременно за один проход! Когда вы суммируете все источники света, убедитесь, что их интенсивность не может превышать 1. Вот краткий шейдер, который я собрал для вас:

 Shader "Unlit/ToonTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Name "FORWARD"
        Tags { "LightMode" = "ForwardBase" "RenderType" = "TransparentCutout" "Queue"="AlphaTest"}
        Cull Off
        LOD 100

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

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD1;
                float3 ambient : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.ambient = ShadeSH9(mul(unity_ObjectToWorld, float4(v.normal, 0.0 ))); // Ambient from spherical harmonics
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            float3 Shade4Lights (    
                float4 lightPosX, float4 lightPosY, float4 lightPosZ,
                float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
                float4 lightAttenSq,
                float3 pos)
            {
                // to light vectors
                float4 toLightX = lightPosX - pos.x;
                float4 toLightY = lightPosY - pos.y;
                float4 toLightZ = lightPosZ - pos.z;
                // squared lengths
                float4 lengthSq = 0;
                lengthSq  = toLightX * toLightX;
                lengthSq  = toLightY * toLightY;
                lengthSq  = toLightZ * toLightZ;
                // don't produce NaNs if some vertex position overlaps with the light
                lengthSq = max(lengthSq, 0.000001);

                // attenuation
                float4 atten = 1.0 / (1.0   lengthSq * lightAttenSq);
                float4 diff = atten; //ndotl * atten;
                // final color
                float3 col = 0;
                col  = lightColor0 * diff.x;
                col  = lightColor1 * diff.y;
                col  = lightColor2 * diff.z;
                col  = lightColor3 * diff.w;
                return col;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                half3 intensity = Shade4Lights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor[0], unity_LightColor[1], unity_LightColor[2], unity_LightColor[3], unity_4LightAtten0, i.worldPos);
                intensity = min((half3)1, i.ambient   intensity);

                col.rgb *= intensity;

                clip(col.a - 0.5);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
  

Функция «Shade4Lights» является модифицированной версией «Shade4PointLights» Unity, с удаленным рассеянным освещением Ламберта (только ослабление). Вам также нужно будет добавить свою функцию RetroAA для выборки текстуры. Ваше значение отсечки равно «- 0.5» внутри функции «клип» — вы можете указать это, если вам это нужно. Если вам нужно отбрасывание тени для этого шейдера, вы можете скопировать / вставить переход тени из стандартного шейдера Unity (вы можете загрузить исходный код с их страницы). Для получения тени вам нужно добавить несколько строк в шейдер — для этого снова проверьте исходный код.

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

https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html