Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 13개 있습니다.)
Graphics: 2. Unity로 실습하는 Shader
; https://www.sysnet.pe.kr/2/0/11607

Graphics: 3. Unity로 실습하는 Shader (1) - 컬러 반전 및 상하/좌우 뒤집기
; https://www.sysnet.pe.kr/2/0/11608

Graphics: 4. Unity로 실습하는 Shader (2) - 고로 셰이딩(gouraud shading) + 퐁 모델(Phong model)
; https://www.sysnet.pe.kr/2/0/11609

Graphics: 5. Unity로 실습하는 Shader (3) - 고로 셰이딩(gouraud shading) + 퐁 모델(Phong model) + Texture
; https://www.sysnet.pe.kr/2/0/11610

Graphics: 6. Unity로 실습하는 Shader (4) - 퐁 셰이딩(phong shading)
; https://www.sysnet.pe.kr/2/0/11611

Graphics: 7. Unity로 실습하는 Shader (5) - Flat Shading
; https://www.sysnet.pe.kr/2/0/11613

Graphics: 8. Unity Shader - Texture의 UV 좌표에 대응하는 Pixel 좌표
; https://www.sysnet.pe.kr/2/0/11614

Graphics: 9. Unity Shader - 전역 변수의 초기화
; https://www.sysnet.pe.kr/2/0/11616

Graphics: 10. Unity로 실습하는 Shader (6) - Mosaic Shading
; https://www.sysnet.pe.kr/2/0/11619

Graphics: 11. Unity로 실습하는 Shader (7) - Blur (평균값, 가우스, 중간값) 필터
; https://www.sysnet.pe.kr/2/0/11620

Graphics: 12. Unity로 실습하는 Shader (8) - 다중 패스(Multi-Pass Shader)
; https://www.sysnet.pe.kr/2/0/11628

Graphics: 13. Unity로 실습하는 Shader (9) - 투명 배경이 있는 텍스처 입히기
; https://www.sysnet.pe.kr/2/0/11631

Graphics: 19. Unity로 실습하는 Shader (10) - 빌보드 구현
; https://www.sysnet.pe.kr/2/0/11641




Unity로 실습하는 Shader (7) - Blur (평균값, 가우스, 중간값) 필터

지난 글에서,

Unity로 실습하는 Shader (6) - Mosaic Shading
; https://www.sysnet.pe.kr/2/0/11619

for 루프를 이용한 처리를 봤는데, 그렇다면 texture를 본연의 이미지 데이터라 보고 OpenCV를 통해했던 이미지 프로세싱까지 - 가령 mask 처리 같은 것도 가능하다는 시나리오가 나옵니다. 그중에서 3가지 스무딩 효과를 Unity shader로 구현해 보겠습니다.




우선, OpenCV의 box filter인 평균값 필터를 다음과 같이 구현해 볼 수 있습니다.

fixed4 frag(v2f i) : SV_Target
{
    float maskWidth = 5; // 외부 변수 처리 권장
    float maskOffset = -floor(maskWidth / 2.0);
            
    float homogeneousBlurFilter = 1.0 / (maskWidth * maskWidth);
    float2 texelSize = _MainTex_TexelSize;

    float2 xy = UVtoXY(i.uv, texelSize);
    float4 color = float4(0.0, 0.0, 0.0, 0.0);

    for (float x = 0; x < maskWidth; x++)
    {
        float offsetX = x + maskOffset;

        for (float y = 0; y < maskWidth; y++)
        {
            float offsetY = y + maskOffset;

            float2 newXY = float2(xy.x + offsetX, xy.y + offsetY);
            float2 newUV = XYtoUV(newXY, texelSize);

            color += (tex2D(_MainTex, newUV) * homogeneousBlurFilter);
        }
    }

    color = color * i.diffuse + i.specular;
    return color;
}

위의 코드는 maskWidth == 5이므로 5x5 크기의 1.0 / 25 평균값을 지정한 것과 같습니다.

${
\begin{split}K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}\end{split}
}$


이것을 적용해 보면, 지구본의 이미지 texture가 너무 커서(2048 * 1024) 별로 차이가 안 납니다.

[그림 1: 왼쪽은 원본, 오른쪽은 5x5 box filter가 적용된 셰이딩]
blur_effect_shader_1.png

변수를 조정해서 25로 늘리면,

maskWidth = 25;

즉, 25x25 크기의 1.0 / 625 필터를 적용하면 제법 눈에 띄는 blurring 효과가 나오는 것을 확인할 수 있습니다.

[그림 1: 왼쪽은 원본, 오른쪽은 25x25 box filter가 적용된 셰이딩]
blur_effect_shader_2.png

그나저나, 실습을 위해 texture 크기를 2048 * 1024 크기에서 512 * 256으로 해상도를 낮춘 지구본 이미지를 사용하겠습니다. 다음은 512 * 256이었을 때 고로 셰이딩(왼쪽)과 5x5 평균값 필터를 적용했을 때의 결과를 보여줍니다.

blur_effect_shader_3.png

위의 코드를 기반으로 하면, 가중 평균 값 필터(weighted mean filter)도 어렵지 않게 적용할 수 있습니다.




두 번째로, blurring 효과 하면 유명한 가우스 필터를 안 해볼 수 없습니다. ^^ 우선, 멋있게 1차원과 2차원 가우스 분포 함수를 써 두지만,

${ G(x) = \frac{1}{{\sigma \sqrt {2\pi } }}e^{{{ -{x}^2 } \mathord{\left/ \right. } {2\sigma ^2 }}}
}$


${ G(x, y) = \frac{1}{{\sigma ^2 {2\pi } }}e^{{{ - \left( x^2 + y^2 \right) } \mathord{\left/ \right. } {2\sigma ^2 }}}
}$


tex2D 함수는 2차원 texture를 대상으로 하기 때문에 2차원 가우스 분포 함수가 적용됩니다. 따라서, σ 값에 따라 적절한 gaussian kernel을 생성하는데,

Gaussian Kernel Calculator
; http://dev.theomader.com/gaussian-kernel-calculator/

일례로 σ = 1.0으로 5x5 크기의 커널을 사용해 다음과 같이 코딩을 할 수 있습니다.

fixed4 frag(v2f i) : SV_Target
{
    // sigma = 1.0
    float gaussian5x5BlurFilter[25] =
    {
        0.003765, 0.015019, 0.023792, 0.015019, 0.003765,
        0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
        0.023792, 0.094907, 0.150342, 0.094907, 0.023792,
        0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
        0.003765, 0.015019, 0.023792, 0.015019, 0.003765,
    };

    float maskWidth = 5;
    float maskOffset = -floor(maskWidth / 2.0);
            
    float2 texelSize = _MainTex_TexelSize;

    float2 xy = UVtoXY(i.uv, texelSize);
    float4 color = float4(0.0, 0.0, 0.0, 0.0);

    for (float y = 0; y < maskWidth; y++)
    {
        float offsetY = y + maskOffset;

        for (float x = 0; x < maskWidth; x++)
        {
            float offsetX = x + maskOffset;

            float2 newXY = float2(xy.x + offsetX, xy.y + offsetY);
            float2 newUV = XYtoUV(newXY, texelSize);

            fixed filterPos = y * 5 + x;

            float filter = gaussian5x5BlurFilter[filterPos];
            color += (tex2D(_MainTex, newUV) * filter);
        }
    }

    color = color * i.diffuse + i.specular;
    return color;
}

아래의 그림은 좌측부터, 원본, 5x5 평균 값 필터링, 5x5 가우스 필터링 효과를 보여줍니다. (3개의 이미지를 함께 출력하느라 이미지가 이지러지는 것을 볼 수 있는데 그 부분은 무시하시면 됩니다.)

blur_effect_shader_4.png

참고로, sigma = 25.0으로 9x9의 mask를 적용하면,

// sigma = 25.0
float gaussian9x9BlurFilter[81] =
{
    0.012162, 0.012231, 0.01228 , 0.012309, 0.012319, 0.012309, 0.01228 , 0.012231, 0.012162,
    0.012231, 0.012299, 0.012349, 0.012378, 0.012388, 0.012378, 0.012349, 0.012299, 0.012231,
    0.01228 , 0.012349, 0.012398, 0.012428, 0.012438, 0.012428, 0.012398, 0.012349, 0.01228 ,
    0.012309, 0.012378, 0.012428, 0.012458, 0.012468, 0.012458, 0.012428, 0.012378, 0.012309,
    0.012319, 0.012388, 0.012438, 0.012468, 0.012478, 0.012468, 0.012438, 0.012388, 0.012319,
    0.012309, 0.012378, 0.012428, 0.012458, 0.012468, 0.012458, 0.012428, 0.012378, 0.012309,
    0.01228 , 0.012349, 0.012398, 0.012428, 0.012438, 0.012428, 0.012398, 0.012349, 0.01228 ,
    0.012231, 0.012299, 0.012349, 0.012378, 0.012388, 0.012378, 0.012349, 0.012299, 0.012231,
    0.012162, 0.012231, 0.01228 , 0.012309, 0.012319, 0.012309, 0.01228 , 0.012231, 0.012162,
};

float maskWidth = 9;

다음과 같이 좀 더 blurring된 것을 볼 수 있습니다.

[좌: 원본, 중간: 평균값, 우: σ = 25.0, 9x9 gaussian filter]
blur_effect_shader_5.png

그런데 2차원 가우시안 함수는 x와 y 방향에 대해 각각 1차원의 가우시안 함수의 곱으로 나타낼 수 있으므로 NxN 마스크가 아닌, 1xN 마스크를 x축과 (2 pass를 이용해) y 축 방향으로 사용할 수 있습니다.(단일 객체로 2 pass를 지정해 blurring 처리하는 것이 가능할까요?)




마지막으로, 중간값 필터(Median Filter)를 적용해 보겠습니다. 중간값이 의미하는 것처럼, mask에 속하는 영역 중 중간값에 해당하는 컬러를 선택하는 것이기 때문에 정렬이 필요한 기능이기도 합니다. 중간값을 반환하기 위한 정렬은 간단하게 다음과 같이 해결할 수 있습니다.

float4 getMedian(float4 buf[25], fixed len)
{
    float4 temp;

    // 버블 정렬
    for (float i = 0; i < len - 1; i ++)
    {
        for (float j = i + 1 ; j < len; j ++)
        {
            if (colorToFloat(buf[i]) < colorToFloat(buf[j]))
            {
                temp = buf[i];
                buf[i] = buf[j];
                buf[j] = temp;
            }
        }
    }

    return buf[len / 2]; // 중간값 반환
}

아쉬운 것은 함수의 인자로 배열을 전달할 때 반드시 크기를 명시해야 한다는 점입니다.

// 반드시 배열 크기 명시
float4 getMedian(float4 buf[25], fixed len)

// 아래와 같이 전달하면 (컴파일 오류 없이) 동작하지 않음
float4 getMedian(float4 buf[], fixed len)

그래서 getMedian과 같이 함수를 만들면 배열 크기에 따른 함수를 별도로 만들어줘야 합니다. 이하 나머지 코드는 평균값 필터와 크게 다르지 않습니다.

fixed4 frag(v2f i) : SV_Target
{
    float maskWidth = 5;
    float4 colorArray[5 * 5];

    float maskOffset = -floor(maskWidth / 2.0);
            
    float2 texelSize = _MainTex_TexelSize;

    float2 xy = UVtoXY(i.uv, texelSize);
    float4 color = float4(0.0, 0.0, 0.0, 0.0);

    for (float x = 0; x < maskWidth; x++)
    {
        float offsetX = x + maskOffset;

        for (float y = 0; y < maskWidth; y++)
        {
            float offsetY = y + maskOffset;

            float2 newXY = float2(xy.x + offsetX, xy.y + offsetY);
            float2 newUV = XYtoUV(newXY, texelSize);

            colorArray[y * maskWidth + x] = tex2D(_MainTex, newUV);
        }
    }

    float4 col = getMedian(colorArray, maskWidth * maskWidth);
    color = col * i.diffuse + i.specular;

    return color;
}

실행 결과는 다음과 같습니다.

[좌측부터 원본, 5x5 평균값 필터, σ = 25.0 and 9x9 가우스 필터, 5x5 중간값 필터]
blur_effect_shader_6.png

참고로, 아래의 소스 코드를 보면 min, max를 활용해 (정렬 없이) 3x3 크기의 중간값 필터를 구현하고 있습니다.

3x3 Median - Morgan McGuire and Kyle Whitson
; http://casual-effects.com/research/McGuire2008Median/median.pix

(첨부 파일은 이 글에서 구현한 3가지 shader 소스 코드를 모두 포함합니다.)




shader에서 컬러를 비교하려고 다음과 같이 코딩을 하면,

fixed4 frag(v2f i) : SV_TARGET
{
    float4 color1 = tex2D(_MainTex, uv1);
    float4 color2 = tex2D(_MainTex, uv2);

    if (color1 < color2)
    {
    }
}

컴파일 오류가 발생합니다.

if statement conditional expressions must evaluate to a scalar

사실 단색이 아닌 color 값 비교라는 게 좀 이상하긴 하지만, 다음과 같이 우회해 볼 수는 있습니다.

float colorToFloat(float4 color)
{
    // tex2D가 반환하는 컬러는 0 ~ 1 사이의 값임.
    return color.r + color.g + color.b;
}

fixed4 frag(v2f i) : SV_TARGET
{
    float4 color1 = tex2D(_MainTex, uv1);
    float4 color2 = tex2D(_MainTex, uv2);

    if (colorToFloat(color1) < colorToFloat(color2))
    {
    }
}




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/15/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2023-04-18 08시43분
Very Fast Image Blur in C# using Direct Bit Manipulation in Pure .NET
; https://www.codeproject.com/Tips/5359189/Very-Fast-Image-Blur-in-Csharp-using-Direct-Bit

당연히 shader보다는 느립니다.
정성태

... 181  182  183  184  185  186  187  188  189  190  191  192  [193]  194  195  ...
NoWriterDateCnt.TitleFile(s)
111정성태3/7/200515315    답변글 VS.NET IDE: 28.1. [추가] SQL 2005 / VS.NET 2005 2005-02 CTP 버전이 올라왔네요. [1]
112정성태11/14/200516552        답변글 VS.NET IDE: 28.2. [추가] VS.NET 2005 2005-02 CTP 버전에서 달라진 점 ( VC++ )
127정성태3/29/200514599        답변글 VS.NET IDE: 28.4. [추가] SQL 2005 2005-02 CTP 버전에서 달라진 점
123정성태3/25/200518515    답변글 .NET Framework: 28.3. Uninstalling software without using Add Remove Programs...
108정성태3/4/200517962.NET Framework: 27. 시스템 이벤트 로그에 쌓이는 {00020906-0000-0000-C000-000000000046} 보안에러
107정성태3/1/200518173COM 개체 관련: 15. COM: Control 유형인 경우, IObjectWithSite 를 구현해도 SetSite/GetSite 가 호출이 안됨
106정성태2/28/200517487COM 개체 관련: 14. 탐색기 "처럼" 파일 열기
105정성태2/28/200516539.NET Framework: 26. VS.NET 2005 : 설치 프로젝트 - .NET Framework 설치 강제화
139정성태11/14/200514730    답변글 .NET Framework: 26.1. ^^ 역시, 배려가 되어 있네요. 제가 못 찾은 것이었습니다.
104정성태2/27/200517421VS.NET IDE: 23. MSI 설치 중에 GetLocalTime / GetSystemTime API 사용
132정성태3/30/200517040    답변글 VS.NET IDE: 23.1. [추가]: MSI 설치 동작 원리
102정성태2/16/200519700.NET Framework: 25. Verify that you are a member of the 'Debugger Users' group on the server. [2]
101정성태2/15/200517421.NET Framework: 24. WMI Win32_NTLogEvent 관리 이벤트를 Windows 2000 에서는 "Access Denied" 가 발생하는 문제파일 다운로드1
100정성태2/15/200523517VS.NET IDE: 22. 방화벽 환경에서의 WMI 연결을 위한 포트 설정 [2]
99정성태2/15/200521528COM 개체 관련: 13. 비동기 Drag & Drop 구현 : IAsyncOperation
103정성태2/23/200517740    답변글 COM 개체 관련: 13.1. [관련 자료] 그외 Drag & Drop 링크파일 다운로드1
97정성태2/14/200520695VS.NET IDE: 21. 설치된 Platform SDK 버전확인 방법
96정성태2/14/200522708기타: 9. http://www.google.com/webhp?complete=1&hl=en 검색에 관해서.
95정성태2/14/200532061VS.NET IDE: 20. Win32 특권 정리 [1]
94정성태1/29/200523342VC++: 13. VS.NET 2005 VC++ 컴파일러 에러 : cannot instantiate abstract class
93정성태1/29/200519247VS.NET IDE: 19. 혹시 VS.NET 2005 Beta2(2004/12 CTP Team System 버전)이 다운이 자주 되나요? [2]
92정성태1/29/200518062.NET Framework: 23. Unmanaged 환경에서 Managed DLL에 정의된 메서드 호출 시 오류 확인하는 방법
91정성태11/14/200518681VC++: 12. VS.NET 2005 VC++ Debug: Expression: ( (state != ST_INVALID ) )
90정성태1/27/200519485.NET Framework: 22. Debug: The underlying connection was closed: Unable to connect to the remote server.
89정성태1/26/200523980VC++: 11. Delay Loaded DLL
87정성태1/23/200517567VS.NET IDE: 18. VS.NET 2005 Beta 1 - VC++ 프로젝트에서 Connection Point 구현시 버그
... 181  182  183  184  185  186  187  188  189  190  191  192  [193]  194  195  ...