이론/엔진

[Unity] Line Shader 관련

킹숭이 2025. 11. 23. 18:41

Shader

- Outline 필요함.

- 플레이어를 마우스로 가리킬 때 빨간색 Outline이 생겨야 함.

- 맵에 가려질 때 플레이어가 파란색으로 투영되어야 함. (X-Ray Shader)

 

 

X-Ray Shader

	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry" "RenderPipeline"="UniversalPipeline" }
		LOD 200

		// Pass 1: 가려진 부분
		Pass {
			Name "OccludedPass"
			Tags { "LightMode" = "SRPDefaultUnlit" }
			
			ZWrite Off
			ZTest Greater
			Blend SrcAlpha OneMinusSrcAlpha
			Cull Back
			
			Stencil {
				Ref [_StencilRef]
				Comp NotEqual  
				Pass Keep 
				ReadMask [_StencilReadMask]
			}
			
			HLSLPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

			struct Attributes {
				float4 positionOS : POSITION;
			};

			struct Varyings {
				float4 positionCS : SV_POSITION;
			};

			CBUFFER_START(UnityPerMaterial)
				half4 _OccludedColor;
			CBUFFER_END

			Varyings vert(Attributes input) {
				Varyings output;
				output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
				return output;
			}

			half4 frag(Varyings input) : SV_Target {
				return _OccludedColor;
			}
			ENDHLSL
		}

		// Pass 2: 보이는 부분
		Pass {
			Name "ForwardLit"
			Tags { "LightMode" = "UniversalForward" }
			
			ZWrite On
			ZTest LEqual
			Cull Back
			
			Stencil {
				Ref [_StencilRef]
				Comp [_StencilComp]
				Pass [_StencilOp]
				ReadMask [_StencilReadMask]
				WriteMask [_StencilWriteMask]
			}
			
			HLSLPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

			struct Attributes {
				float4 positionOS : POSITION;
				float3 normalOS : NORMAL;
				float2 uv : TEXCOORD0;
			};

			struct Varyings {
				float2 uv : TEXCOORD0;
				float3 normalWS : TEXCOORD1;
				float3 positionWS : TEXCOORD2;
				float4 positionCS : SV_POSITION;
			};

			TEXTURE2D(_MainTex);
			SAMPLER(sampler_MainTex);
			TEXTURE2D(_1st_ShadeMap);
			SAMPLER(sampler_1st_ShadeMap);
			TEXTURE2D(_Emissive_Tex);
			SAMPLER(sampler_Emissive_Tex);

			CBUFFER_START(UnityPerMaterial)
				float4 _MainTex_ST;
				half4 _BaseColor;
				half4 _1st_ShadeColor;
				half4 _2nd_ShadeColor;
				half4 _Emissive_Color;
				half4 _OverColor;
				half _AddValOffset;
				half _CurrPos;
				half _BaseColor_Step;
				half _BaseShade_Feather;
				half _ShadeColor_Step;
				half _1st2nd_Shades_Feather;
				half _Use_BaseAs1st;
				half _Unlit_Intensity;
				half _GI_Intensity;
			CBUFFER_END

			Varyings vert(Attributes input) {
				Varyings output;
				
				VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
				VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS);
				
				output.positionCS = vertexInput.positionCS;
				output.positionWS = vertexInput.positionWS;
				output.normalWS = normalInput.normalWS;
				output.uv = TRANSFORM_TEX(input.uv, _MainTex);
				
				return output;
			}

			half4 frag(Varyings input) : SV_Target {
				half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * _BaseColor;
				
				Light mainLight = GetMainLight();
				half3 normalWS = normalize(input.normalWS);
				half NdotL = dot(normalWS, mainLight.direction);
				
				// 툰 셰이딩 
				half lightIntensity = NdotL * 0.5 + 0.5;
				half toonStep = step(_BaseColor_Step, lightIntensity);
				
				if (_BaseShade_Feather > 0.0001)
				{
					toonStep = smoothstep(_BaseColor_Step - _BaseShade_Feather * 0.5, 
					                      _BaseColor_Step + _BaseShade_Feather * 0.5, 
					                      lightIntensity);
				}
				
				half4 shadeMap = _Use_BaseAs1st > 0.5 ? baseColor : SAMPLE_TEXTURE2D(_1st_ShadeMap, sampler_1st_ShadeMap, input.uv);
				half4 firstShade = shadeMap * _1st_ShadeColor;
				
				half shade2Step = step(_ShadeColor_Step, lightIntensity);
				if (_1st2nd_Shades_Feather > 0.0001)
				{
					shade2Step = smoothstep(_ShadeColor_Step - _1st2nd_Shades_Feather * 0.5, 
					                        _ShadeColor_Step + _1st2nd_Shades_Feather * 0.5, 
					                        lightIntensity);
				}
				half4 secondShade = shadeMap * _2nd_ShadeColor;
				
				// 셰이드 믹스
				half4 shadedColor = lerp(secondShade, firstShade, shade2Step);
				half4 finalColor = lerp(shadedColor, baseColor, toonStep);
				
				// 조명 적용
				finalColor.rgb *= _Unlit_Intensity;
				
				half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) * _GI_Intensity * 0.3;
				finalColor.rgb += ambient * baseColor.rgb;
				
				// Emissive
				half4 emissive = SAMPLE_TEXTURE2D(_Emissive_Tex, sampler_Emissive_Tex, input.uv) * _Emissive_Color;
				finalColor.rgb += emissive.rgb;
				
				// Overlay Color
				half maskValue = saturate((_CurrPos - _AddValOffset) * 10.0);
				finalColor.rgb = lerp(finalColor.rgb, _OverColor.rgb, _OverColor.a * maskValue);
				
				return finalColor;
			}
			ENDHLSL
		}
	}
	Fallback "Hidden/Universal Render Pipeline/FallbackError"
}

 

 


문제점 1 

- 맵이나 특정 오브젝트에 대해서만 X-Ray가 적용되어야 하는데 무기에 가려져 함께 X-Ray 영향을 받아버린다.

- 스탠실 버퍼를 배운 적이 있는데, x-ray에 영향을 받지 않는 플레이어, 무기의 경우 오브젝트에 대해서 스탠실 번호를 100으로 지정한 후 제외하고자 한다.

 [Header("X-Ray Settings")]
 [SerializeField] private int xRayIgnoreStencilID = 100;
 
 #region Shader
 void InitializeXRay()
 {
     SetupPlayerWeaponXRay();
 }

 void SetupPlayerWeaponXRay()
 {
     SetXRayGroup(gameObject, xRayIgnoreStencilID);

     if (_eqipWeapon != null)
         SetXRayGroup(_eqipWeapon, xRayIgnoreStencilID);
 }
 public void SetxRayFromPlayer(GameObject player)
 {
      SetXRayGroup(player, xRayIgnoreStencilID);
 }
 void SetXRayGroup(GameObject root, int stencilID)
 {
     Renderer[] renderers = root.GetComponentsInChildren<Renderer>(true);

     foreach (Renderer renderer in renderers)
     {
         foreach (Material mat in renderer.materials)
         {
             if (mat.shader.name.Contains("Toon_DoubleShadeWithFeather"))
             {
                 if (mat.HasProperty("_StencilRef"))
                 {
                     mat.SetInt("_StencilRef", stencilID);
                     mat.SetInt("_StencilComp", (int)UnityEngine.Rendering.CompareFunction.Always);
                     mat.SetInt("_StencilOp", (int)UnityEngine.Rendering.StencilOp.Replace);
                 }
             }
         }
     }
 }

 

 

문제점 2

- 기본 Scene에서는 X-Ray의 영향을 안 받는데, 다른 Scene에서는 Black Outline이 X-Ray의 영향을 받는다.

- 살펴보니까 모델 내에 Black Outline만 영향을 받는듯 하다

- 그래서 Black Outline Shader 내에서 스탠실 버퍼를 지정해줬다.

Shader "Custom/Outliner"
{
    Properties
    {
        _Color ("Outline Color", Color) = (1,0,0,1)
        _Width ("Outline Width", Range(0.0, 0.1)) = 0.03
        
        [Header(Stencil)]
        _StencilRef ("Stencil Reference", Float) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comparison", Float) = 8
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1" "RenderPipeline"="UniversalPipeline" }
        
        Pass
        {
            Name "Outline"
            Tags { "LightMode" = "SRPDefaultUnlit" }
            
            Cull Front
            ZWrite On
            ZTest LEqual
            
            Stencil
            {
                Ref [_StencilRef]
                Comp [_StencilComp]
                Pass Keep
            }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            half4 _Color;
            float _Width;
            
            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };
            
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };
            
            Varyings vert(Attributes input)
            {
                Varyings output;
                float3 normalOS = normalize(input.normalOS);
                float3 expandedPos = input.positionOS.xyz + normalOS * _Width;
                output.positionCS = TransformObjectToHClip(expandedPos);
                return output;
            }
            
            half4 frag(Varyings input) : SV_Target
            {
                return _Color;
            }
            ENDHLSL
        }
    }
}

- Comp = Always: XRay와 관계없이 항상 표시

 


결과물

 ✅상대 씬에서도 X-Ray가 잘 구성된다.

 ✅Black Outline에 영향을 받지 않는다.

 ✅X-Ray 영향 받지 않는 오브젝트는 스탠실로 구분했다.

 

 

+추가

Before > After

- 겸사 겸사 Smooth/Emissive를 Shader에 추가해 조금 더 예쁘게 만들어 주었음.