Depth Edge Detection과 Normal Edge Detection 두가지 방법을 모두 써서 Outline을 만들어보려고 한다
*Depth Edge Detection
: 픽셀 하나를 잡고 이 중심 픽셀과 이 픽셀의 상하좌우에 있는 4개의 픽셀을 이용해서 Depth Variation을 구하는 것이다. 그래서 상하좌우의 픽셀과 중심의 픽셀이 같은 값이면 위의 공식에 따라 Depth Variation이 0이 되겠지만, 오른쪽 그림과 같이 중심 픽셀과 차이가 있게 되면 Variation 값이 생기게 된다. 픽셀의 depth가 클수록 공식에 의한 Variation 값이 커진다.
그리고 이 공식에 의한 Variation 값을 내가 정한 Threshold 값과 비교해서 기준보다 크면, 중심 픽셀 주위에서 depth가 많이 바뀌었다는 뜻이 된다. > 그러면 이 중심픽셀이 오브젝트의 외곽선임을 알 수 있다! 중심픽셀 색을 Black으로 바꿔서 Outline으로 만들면 된당
그러나 이 방법에는 문제가 있다. 이렇게 내부에 있는 것은 Depth Variation 값이 크지 않기 때문에 외곽선으로 인지하지 못하게 되는 현상이 발생한다.
그래서 Normal Edge Detection 방법과 함께 이용할 것이다.
*Normal Edge Detection
You can tell, this will be based on sampling the normals of a pixel and its 4 neighbors. Check the formula below.
: 이 공식(시그마)을 쓰면 normal's variation rate를 구할 수 있다.
In other words, normal's variation rate is the orientation variation rate of the surface around the current pixel. 이것도 마찬가지로 Variation 값이 Threshold보다 크면, 픽셀의 컬러를 검정으로 해서 Outline을 만들면 된다
그래서 이 두개를 합치면 된다
나머지는 첫번째, 두번째 유튜브 영상보고 해라
<Laplacian Filter 이용하여 Outline 만들기>
*Kernal / Neighbor Pixel 구하기
: 위의 두 방법의 기본은 한 중심 픽셀과 그 주위에 있는 픽셀들을 이용하는 것이다. 그러면 이 주변 픽셀은 어떻게 구하는가?
아웃라인만 만들거면 상하좌우 4개만 이용해도 되지만 만약 Gaussian Blur, Sharpen, Horizontal Prewitt, Vertical Sobel 등의 효과를 주고 싶으면 대각선 픽셀까지 포함해서 8개의 픽셀을 이용해야 한다
일단 기본 공식은 각 9개의 픽셀에 특정한 값을 곱하고 그 곱한 값을 모두 더해주는 것이다. 특정한 값에 0을 곱할 거는 아니고, 특정한 값을 내가 지정해주면 된다. 효과마다 곱해줘야 하는 값이 다르다! 위의 사진이 기본 틀이다.
위 사진처럼 기본 틀을 전부 복사해서 총 9개 만들면 된다. 그리고 다 더하면 됌. 이제 SceneTexture의 UV에 각 픽셀의 좌표값을 넣어주면 됌
이건 UV에 들어갈 픽셀의 위치값의 기본 틀이다. 하나는 센터 픽셀이니깐 UV에 위치값 따로 안넣어줘도 됌. 그러니깐 이것도 저 틀을 전부 복사해서 이번엔 8개만 만들어주면 된다. 그다음 저 float2값에는 중심픽셀을 기준으로 해서 각 픽셀의 좌표값을 넣어주면 됌. 예를 들어, 중심 픽셀의 대각선 왼쪽 위에 있는 픽셀의 좌표값은 (-1,-1)이 되는 것. 중심 픽셀 아래 픽셀의 좌표는 (0, -1) 이런 식. 8개의 float2에 각각 넣어주면 됌.
좀 복잡하지만 정리하면 이렇게 된다. 곱할 특정한 값을 저렇게 좌표처럼 만들어주면 보기가 더 편하다.
아까 위에서 말한 곱해야 하는 특정한 값을 Kernal이라고 하는 듯? 이 Kernal을 위의 사진처럼 하면 특정한 효과를 줄 수 있다.
*Laplacian을 이용해서 외곽선 만들기 (라플라시안 필터)
Kernal에 위사진에 있는 Laplacian 값을 넣고 연결해보자.
아까 SceneTexture를 PostProcessInput0으로 해놨기 때문에 이런 결과가 나타난다. 그러나 이건 정확하지 않다. 그래서 이것도 마찬가지로 World Normal과 Scene Depth를 이용할 것이다.
SceneTexture를 SceneDepth로 해주면 내부의 선은 안보이고, WorldNormal로 하면 일부는 외부의 선이 안보이면서 3채널이 이용되기 때문에 컬러로 보인다. 이걸 흑백으로 바꿀려면 최종 Output에서 각 R,G,B 채널을 비교해서 가장 큰 값만 Output에 넣어주면 된다
그래서 어쨋든 두 개를 다 합쳐야 외부 선, 내부 선이 다 잘보일텐데 어떻게 하면 되냐면
일단 전체를 복사해서 하나는 SceneTexture를 SceneDepth로, 하나는 World Normal로 해준다. 여기서 SceneDepth로 한 것의 Output은 컬러가 아니니깐 그냥 SceneTexture뒤의 Mask값을 R로만 해주면 된다! 그다음 SceneDepth의 Output과 WorldNormal의 Output을 Max 연결해서 이걸 최종 Output의 Emissive Color에 연결해주면 된다!
*밝기 낮추기
근데 이렇게 하면 없어도 될만한 Outline까지 전부 나와버려서 복잡해보인다. 에디터에서 EV100를 조절해보면 이 밝기에 따라 Outline이 줄어들었다 늘었다 하는 것을 볼 수 있다. 근데 조절해보면 알겠지만 하얀색 값이 1이상의 값이라는 것을 알 수 있다. 그냥 봐도 아웃라인이 Emissive 흰색으로 보이기도 한다. 그래서 위에서 구한 결과값을 어떤 값으로 나눠서 밝기를 낮추려고 한다.
아무 숫자나 정해서 대충 낮춰도 되지만, 정확하게 해보려고 한다. 알다시피 World Normal의 값은 -1 ~ 1이다. 그리고 여기서 나올 수 있는 밝기의 가장 큰 값은 뭐가 될 지 생각해보자.
Normal Components Range는 -1 ~ 1이고, 라플라시안 필터의 Kernel 값은 저렇다.
그리고 Normal pass Pixels에서 중심 픽셀은 n이고 주변 픽셀이 모두 -n일때 (-1 < n < 1), 혹은 중심픽셀은 -n이고 주변 픽셀이 모두 n일때가 Normal이 갑자기 바뀌는 곳이니깐 여기서 Outline이 그려질 수 있다는 것을 위에서 배웠다.
그중에서도 중심이 1이고 주변이 모두 -1일때가 밝기가 가장 높을 것이다! 왜냐면 위에서 배웠듯이 특정한 값(여기선 Laplacian Kernel)을 픽셀의 값과 곱해서 모두 더한다고 했다. 중심이 1이고 주변이 모두 -1일 때, 라플라시안 Kernel 값과 곱해서 모두 더하면, 가장 큰 값이 나온다고 할 수 있다.
그러니깐 World Normal 패스를 써서 나온 Outline의 밝기 값은 -16 ~ 16인 것이다.
따라서 아까 구한 최종 값을 16으로 나누면 밝기가 좀 줄어든 것을 확인할 수 있다!
*해당 픽셀은 아웃라인인가? 를 자세히 판단해보기 (World Normal에서)
그러나 지금 만들어진 Outline은 0 ~ 1의 그라데이션 값이다. 0이면 아웃라인이 아니고, 1이면 아웃라인으로 간주한다. 이걸 fine-tune하기 위해 Threshold를 InvLerp 노드를 이용해서 만들어 줄 것이다. (InvLerp에서 결과 범위 제한 체크해주기!)
A와 B의 값을 이용해서 이 픽셀이 아웃라인인지 판단하는 Sensibility를 정해주면 된다. 위의 사진같은 경우는 Value의 최솟값은 0.15가 될거고, Value의 최댓값은 0.2가 될 것이다.
*너무 디테일한 Outline은 지워주기 (Scene Depth에서)
카메라에서 픽셀이 있는 위치까지의 거리는 initial range가 없다. 왜냐면 카메라로 어디를 보든 픽셀은 어디에나 존재하니깐. 그래서 normalization도 못함. 그러나 방법은 있다!
라플라시안 필터의 Kernal 값을 다시 보자. 여기서 중심 픽셀을 제외한 모든 neighbors의 weight값은 모두 같은 걸 알 수 있다. 이 weight를 모두 더해서 8로 나눠주면(=neighbor pixel의 갯수) neighbor의 average depth를 구할 수 있다.
Then if we add this average to the depth value of the middle pixel, we get the average distance of the evaluated pixel
그래서 이걸 적용하려면 그냥 간단히 SceneDepth 써서 구했던 Output을 8로 나눠주면 된다고 한다.
그다음에 더 조정하려면 InvLerp를 써서 0 ~ 1의 범위로 remap 해주면 된다 (InvLerp에서 결과 범위 제한 체크!).
어떻게 하냐면 아웃라인으로 판단하기 전에 픽셀과 neighbor pixel의 depth unit이 얼마나 차이나야 하는지 threshold를 정의해주면 된다.
InvLerp의 A와 B 값을 적절하게 조정해보자. 이건 셰이더에게 아웃라인이 되려면 어떻게 해야 하는지 알려주는 것이다.
A가 크다는 것은 중심과 neighbor pixel의 average depth difference가 크다는 것. 그러니깐 더 detail한 Outline을 만들고 싶으면 A를 줄이면 됌.
A : 아웃라인으로 간주되기 위한 중심과 neighbor pixel의 최소 average depth difference
B : 이 값 이상이면 확실히 Outline으로 간주
>>위 사진은 깊이 차이가 적어도 2이상이면 아웃라인으로 간주하고, 10이상이면 확실히 아웃라인으로 간주한다는 것이다.
그다음 거는 더 이상 쓰기 귀찮으니깐 그냥 맨 마지막 유튜브 영상 보고 해라
+참고 : https://youtu.be/9KvUfnrHcqM?si=cknbhYItUOMCsYP0
+참고 : https://youtu.be/Wpsqfpxb55Y?si=J-pAqQ6E6VF-BYGO
+참고 : https://youtu.be/Ptuw9mxekh0?si=coOdHXG46UDX7gSM
'Shader' 카테고리의 다른 글
Sharpen Filter 만들기 (0) | 2024.01.06 |
---|---|
커널(Kernal)과 컨볼루션(Convolution) (0) | 2024.01.05 |
Tone Mapping과 Temporal Anti-Aliasing (0) | 2023.09.22 |
Scene Depth와 Custom Depth (0) | 2023.09.22 |
[Shader Graph Basics] Dot Product Node (Episode 7) (1) | 2023.08.09 |