[개발 일지] 외곽선 강조 Shader 코드

scarleter99·2024년 9월 29일

[Unity] Unity Skill

목록 보기
6/6
post-thumbnail

외곽선 강조 Shader 코드

OutlineShader/Objects

  • 객체 단위로 외곽선을 그리는 Shader이다.
Shader "OutlineShader/Objects"
{
    Properties
    {
		_ContourColor("Contour Color", Color) = (1,1,1,1) // 외곽선 색상
        _MaxDistance("Max Distance", float) = 15 // 카메라와의 최대 거리
    }

    HLSLINCLUDE

    #pragma target 4.5
    #pragma only_renderers d3d11 ps4 xboxone vulkan metal switch // 셰이더가 작동할 플랫폼 제한
    
    #pragma multi_compile_instancing // GPU 인스턴싱을 활성화하여 성능 최적화
    #pragma instancing_options renderinglayer // GPU 인스턴싱 시 렌더링 레이어도 적용

    ENDHLSL

    SubShader
    {
        Pass
        {
            Name "FirstPass"
            Tags { "LightMode" = "FirstPass" }

            Blend Off // 알파 블렌딩 비활성화
            ZWrite On // 깊이 버퍼 쓰기 활성화
            ZTest LEqual // 깊이 테스트가 현재 픽셀의 깊이 값이 같거나 작은 경우에만 통과

            Cull Back // 후면 컬링 활성화

            HLSLPROGRAM

            // Vertex Shader에 전달할 데이터
            #define ATTRIBUTES_NEED_TEXCOORD0
            #define ATTRIBUTES_NEED_NORMAL
            #define ATTRIBUTES_NEED_TANGENT

            // Fragment Shader에 전달할 데이터
            #define VARYINGS_NEED_TEXCOORD0
            #define VARYINGS_NEED_TANGENT_TO_WORLD
            #define VARYINGS_NEED_POSITION_WS

			// Unity HDRP의 Custom Pass용 헤더 파일 포함
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassRenderers.hlsl"
			
            // 셰이더에 사용할 변수들 정의
            float _MaxDistance;
			float4 _ContourColor;
            float _AlphaThreshold;

            // 커스텀 패스에서 표면과 기본 데이터 처리 함수 정의
            void GetSurfaceAndBuiltinData(FragInputs fragInputs, float3 viewDirection, inout PositionInputs posInput, out SurfaceData surfaceData, out BuiltinData builtinData)
            {
            	// 카메라와 객체의 거리가 _MaxDistance보다 크면 해당 픽셀을 그리지 않음
                if( length(fragInputs.positionRWS) > _MaxDistance ) discard;

                // 표면과 기본 데이터 초기화 (조명 계산 없음)
                ZERO_INITIALIZE(BuiltinData, builtinData); // No call to InitBuiltinData as we don't have any lighting
                ZERO_INITIALIZE(SurfaceData, surfaceData); // No call to InitBuiltinData as we don't have any lighting
                
                builtinData.opacity = 1;  // 객체 불투명하게 설정
                builtinData.emissiveColor = float3(0, 0, 0); // 발광 색상 설정 (발광 없음)
                surfaceData.color = float3(1,1,1) * _ContourColor.rgb; // 표면 색상을 외곽선 색상으로 설정
            }
			
            // HDRP의 비조명(Unlit) 패스 포함
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassForwardUnlit.hlsl"
			
            #pragma vertex Vert // Vertex Shader 선언
            #pragma fragment Frag // Fragment Shader 선언

            ENDHLSL
        }
    }
}

OutlineShader/Fullscreen

  • 화면 전체에 외곽선 효과를 적용하는 후처리 셰이더이다.
Shader "OutlineShader/Fullscreen"
{
    properties
    {
        _Iterations ("Iterations", Range(1,3) ) = 1 // 외곽선 효과의 반복 횟수
        _OutlineWidth ("Outline Width", Float ) = 5 // 외곽선 너비
        _InsideColor ("Inside Color", Color) = (1, 1, 0, 0.5) // 내부 색상
    }

    HLSLINCLUDE

    #pragma vertex Vert // Vertex Shader 선언

    #pragma target 4.5
    #pragma only_renderers d3d11 ps4 xboxone vulkan metal switch

    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl"

    #define v2 1.41421 // √2
    #define c45 0.707107 // cos(45°)
    #define c225 0.9238795 // cos(22.5°)
    #define s225 0.3826834 // sin(22.5°)

	// 외곽선 탐지를 위한 Offset (화면 상의 다양한 방향으로 샘플링)
    #define MAXSAMPLES 16
    static float2 offsets[MAXSAMPLES] = {
        float2( 1, 0 ),
        float2( -1, 0 ),
        float2( 0, 1 ),
        float2( 0, -1 ),

        float2( c45, c45 ),
        float2( c45, -c45 ),
        float2( -c45, c45 ),
        float2( -c45, -c45 ),

        float2( c225, s225 ),
        float2( c225, -s225 ),
        float2( -c225, s225 ),
        float2( -c225, -s225 ),
        float2( s225, c225 ),
        float2( s225, -c225 ),
        float2( -s225, c225 ),
        float2( -s225, -c225 )
    };

    int _Iterations;
    float _OutlineWidth;
    float4 _InsideColor;
	
    // Fragment Shader: 화면 전체 패스를 처리
    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        // 스테레오 렌더링 설정
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);

        // 깊이 정보를 로드
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        // 화면 좌표와 카메라 정보를 기반으로 PositionInputs 계산
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        // 뷰 방향 벡터 계산 (카메라 방향)
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        // 현재 픽셀의 컬러를 로드
        float4 c = LoadCustomColor(posInput.positionSS);
        // 각 픽셀당 UV 오프셋 계산
        float2 uvOffsetPerPixel = 1.0/_ScreenSize.xy;
        // 샘플링할 픽셀 수 계산 (최대 MAXSAMPLES까지)
        uint sampleCount = min( 2 * pow(2, _Iterations ), MAXSAMPLES );
        // 외곽선 정보를 저장할 변수 초기화
        float4 outline = float4(0,0,0,0);
        // 각 오프셋 방향으로 샘플링하여 외곽선을 탐색
        for (uint i=0 ; i<sampleCount ; ++i )
        {
            outline = max( SampleCustomColor( posInput.positionNDC + uvOffsetPerPixel * _OutlineWidth * offsets[i] ), outline );
        }

        // 결과 색상 초기화
        float4 o = float4(0,0,0,0);
        // 샘플링된 외곽선 색상을 적용
        o = lerp(o, float4(outline.rgb, 1), outline.a);
        // 외곽선이 없는 부분에 내부 색상 적용
        o = lerp(o, _InsideColor, c.a);
        return o;
    }

    ENDHLSL

    SubShader
    {
        Pass
        {
            Name "Custom Pass 0"

            ZWrite Off // 깊이 쓰기 비활성화
            ZTest Always // 항상 렌더링 (깊이 테스트 없음)
            Blend SrcAlpha OneMinusSrcAlpha// 알파 블렌딩 설정
            Cull Off // 컬링 비활성화 (양면 렌더링)

            HLSLPROGRAM
                #pragma fragment FullScreenPass // Fragment Shader 선언
            ENDHLSL
        }
    }
    Fallback Off
}

0개의 댓글