Предотвращение затенения прозрачных областей проекционным шейдером

#unity3d #shader #hlsl #cg

#unity3d #шейдер #hlsl #cg

Вопрос:

Я пытаюсь создать шейдер деколи для использования с проектором в Unity. Вот что я собрал:

 Shader "Custom/color_projector"
{
    Properties {
        _Color ("Tint Color", Color) = (1,1,1,1)
        _MainTex ("Cookie", 2D) = "gray" {}
    }
    Subshader {
        Tags {"Queue"="Transparent"}

        Pass {

            ZTest Less
            ColorMask RGB
            Blend One OneMinusSrcAlpha
            Offset -1, -1

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

            struct v2f {
                float4 uvShadow : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            float4x4 unity_Projector;
            float4x4 unity_ProjectorClip;

            v2f vert (float4 vertex : POSITION)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (vertex);
                o.uvShadow = mul (unity_Projector, vertex);
                return o;
            }

            sampler2D _MainTex;
            fixed4 _Color;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 tex = tex2Dproj (_MainTex, UNITY_PROJ_COORD(i.uvShadow));
                return _Color * tex.a;

            }
            ENDCG
        }
    }
}
  

Это хорошо работает в большинстве ситуаций:

введите описание изображения здесь

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

введите описание изображения здесь

Я перепробовал множество вариантов наложения и все варианты ZTesting. Это лучшее, что я могу заставить его выглядеть.

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

 Pass {
   ZWrite On
   ColorMask 0
}
  

Но ни то, ни другое не имело никакого эффекта вообще.

Как можно изменить этот шейдер, чтобы он проецировал текстуру только один раз на ближайшие геометрии?

Желаемый результат (фотошоп):

введите описание изображения здесь

Ответ №1:

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

Сначала добавьте следующее к тегам прозрачного (grass) шейдера:

 "IgnoreProjector"="True"
  

Затем измените очередь рендеринга вашего проектора с «Прозрачного» на «Прозрачный 1». Это означает, что сначала будет отображаться земля, затем края травы, и, наконец, проектор будет проецироваться на землю (за исключением появления сверху, поскольку он отображается последним).

Что касается смешивания, я думаю, вам нужно обычное альфа-смешивание:

 Blend SrcAlpha OneMinusSrcAlpha
  

Другой вариант, если вы используете отложенный рендеринг, — использовать отложенные деколи. Они дешевле и обычно проще в использовании, чем проекторы.

Комментарии:

1. Сработало отлично, спасибо. Я еще раз проверю отложенные надписи, хотя я только бегло взглянул, когда Unity впервые выпустила его.