성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Unity로 실습하는 Shader (7) - Blur (평균값, 가우스, 중간값) 필터</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unity로 실습하는 Shader (6) - Mosaic Shading ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11619'>http://www.sysnet.pe.kr/2/0/11619</a> </pre> <br /> for 루프를 이용한 처리를 봤는데, 그렇다면 texture를 본연의 이미지 데이터라 보고 OpenCV를 통해했던 이미지 프로세싱까지 - 가령 mask 처리 같은 것도 가능하다는 시나리오가 나옵니다. 그중에서 3가지 스무딩 효과를 Unity shader로 구현해 보겠습니다. <br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, OpenCV의 box filter인 평균값 필터를 다음과 같이 구현해 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fixed4 frag(v2f i) : SV_Target { float maskWidth = 5; // 외부 변수 처리 권장 float maskOffset = -floor(maskWidth / 2.0); <span style='color: blue; font-weight: bold'>float homogeneousBlurFilter = 1.0 / (maskWidth * maskWidth);</span> 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; } </pre> <br /> 위의 코드는 maskWidth == 5이므로 5x5 크기의 1.0 / 25 평균값을 지정한 것과 같습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> ${<br /> \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}<br /> }$<br /> </div><br /> <br /> 이것을 적용해 보면, 지구본의 이미지 texture가 너무 커서(2048 * 1024) 별로 차이가 안 납니다.<br /> <br /> [그림 1: 왼쪽은 원본, 오른쪽은 5x5 box filter가 적용된 셰이딩]<br /> <img alt='blur_effect_shader_1.png' src='/SysWebRes/bbs/blur_effect_shader_1.png' /><br /> <br /> 변수를 조정해서 25로 늘리면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > maskWidth = 25; </pre> <br /> 즉, 25x25 크기의 1.0 / 625 필터를 적용하면 제법 눈에 띄는 blurring 효과가 나오는 것을 확인할 수 있습니다.<br /> <br /> [그림 1: 왼쪽은 원본, 오른쪽은 25x25 box filter가 적용된 셰이딩]<br /> <img alt='blur_effect_shader_2.png' src='/SysWebRes/bbs/blur_effect_shader_2.png' /><br /> <br /> 그나저나, 실습을 위해 texture 크기를 2048 * 1024 크기에서 512 * 256으로 해상도를 낮춘 지구본 이미지를 사용하겠습니다. 다음은 512 * 256이었을 때 고로 셰이딩(왼쪽)과 5x5 평균값 필터를 적용했을 때의 결과를 보여줍니다.<br /> <br /> <img alt='blur_effect_shader_3.png' src='/SysWebRes/bbs/blur_effect_shader_3.png' /><br /> <br /> 위의 코드를 기반으로 하면, 가중 평균 값 필터(weighted mean filter)도 어렵지 않게 적용할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 두 번째로, blurring 효과 하면 유명한 가우스 필터를 안 해볼 수 없습니다. ^^ 우선, 멋있게 1차원과 2차원 가우스 분포 함수를 써 두지만,<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> ${ G(x) = \frac{1}{{\sigma \sqrt {2\pi } }}e^{{{ -{x}^2 } \mathord{\left/ \right. } {2\sigma ^2 }}}<br /> }$<br /> </div><br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> ${ G(x, y) = \frac{1}{{\sigma ^2 {2\pi } }}e^{{{ - \left( x^2 + y^2 \right) } \mathord{\left/ \right. } {2\sigma ^2 }}}<br /> }$<br /> </div><br /> <br /> tex2D 함수는 2차원 texture를 대상으로 하기 때문에 2차원 가우스 분포 함수가 적용됩니다. 따라서, σ 값에 따라 적절한 gaussian kernel을 생성하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Gaussian Kernel Calculator ; <a target='tab' href='http://dev.theomader.com/gaussian-kernel-calculator/'>http://dev.theomader.com/gaussian-kernel-calculator/</a> </pre> <br /> 일례로 σ = 1.0으로 5x5 크기의 커널을 사용해 다음과 같이 코딩을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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; } </pre> <br /> 아래의 그림은 좌측부터, 원본, 5x5 평균 값 필터링, 5x5 가우스 필터링 효과를 보여줍니다. (3개의 이미지를 함께 출력하느라 이미지가 이지러지는 것을 볼 수 있는데 그 부분은 무시하시면 됩니다.)<br /> <br /> <img alt='blur_effect_shader_4.png' src='/SysWebRes/bbs/blur_effect_shader_4.png' /><br /> <br /> 참고로, sigma = 25.0으로 9x9의 mask를 적용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 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; </pre> <br /> 다음과 같이 좀 더 blurring된 것을 볼 수 있습니다.<br /> <br /> [좌: 원본, 중간: 평균값, 우: σ = 25.0, 9x9 gaussian filter]<br /> <img alt='blur_effect_shader_5.png' src='/SysWebRes/bbs/blur_effect_shader_5.png' /><br /> <br /> <span style='text-decoration: line-through'>그런데 2차원 가우시안 함수는 x와 y 방향에 대해 각각 1차원의 가우시안 함수의 곱으로 나타낼 수 있으므로 NxN 마스크가 아닌, 1xN 마스크를 x축과 (2 pass를 이용해) y 축 방향으로 사용할 수 있습니다.</span>(단일 객체로 2 pass를 지정해 blurring 처리하는 것이 가능할까요?)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, 중간값 필터(Median Filter)를 적용해 보겠습니다. 중간값이 의미하는 것처럼, mask에 속하는 영역 중 중간값에 해당하는 컬러를 선택하는 것이기 때문에 정렬이 필요한 기능이기도 합니다. 중간값을 반환하기 위한 정렬은 간단하게 다음과 같이 해결할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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]; // 중간값 반환 } </pre> <br /> 아쉬운 것은 함수의 인자로 배열을 전달할 때 반드시 크기를 명시해야 한다는 점입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 반드시 배열 크기 명시 float4 getMedian(float4 <span style='color: blue; font-weight: bold'>buf[25]</span>, fixed len) // 아래와 같이 전달하면 (컴파일 오류 없이) 동작하지 않음 float4 getMedian(float4 <span style='color: blue; font-weight: bold'>buf[]</span>, fixed len) </pre> <br /> 그래서 getMedian과 같이 함수를 만들면 배열 크기에 따른 함수를 별도로 만들어줘야 합니다. 이하 나머지 코드는 평균값 필터와 크게 다르지 않습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fixed4 frag(v2f i) : SV_Target { <span style='color: blue; font-weight: bold'>float maskWidth = 5; float4 colorArray[5 * 5];</span> 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); <span style='color: blue; font-weight: bold'>colorArray[y * maskWidth + x] = tex2D(_MainTex, newUV);</span> } } <span style='color: blue; font-weight: bold'>float4 col = getMedian(colorArray, maskWidth * maskWidth);</span> color = col * i.diffuse + i.specular; return color; } </pre> <br /> 실행 결과는 다음과 같습니다.<br /> <br /> [좌측부터 원본, 5x5 평균값 필터, σ = 25.0 and 9x9 가우스 필터, 5x5 중간값 필터]<br /> <img onclick='toggle_img(this)' class='imgView' alt='blur_effect_shader_6.png' src='/SysWebRes/bbs/blur_effect_shader_6.png' /><br /> <br /> 참고로, 아래의 소스 코드를 보면 min, max를 활용해 (정렬 없이) 3x3 크기의 중간값 필터를 구현하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 3x3 Median - Morgan McGuire and Kyle Whitson ; <a target='tab' href='http://casual-effects.com/research/McGuire2008Median/median.pix'>http://casual-effects.com/research/McGuire2008Median/median.pix</a> </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1320&boardid=331301885'>첨부 파일은 이 글에서 구현한 3가지 shader 소스 코드를 모두 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> shader에서 컬러를 비교하려고 다음과 같이 코딩을 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fixed4 frag(v2f i) : SV_TARGET { float4 color1 = tex2D(_MainTex, uv1); float4 color2 = tex2D(_MainTex, uv2); if (color1 < color2) { } } </pre> <br /> 컴파일 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if statement conditional expressions must evaluate to a scalar </pre> <br /> 사실 단색이 아닌 color 값 비교라는 게 좀 이상하긴 하지만, 다음과 같이 우회해 볼 수는 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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)) { } } </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1727
(왼쪽의 숫자를 입력해야 합니다.)