이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
스크린 쉐이더를 통해 CRT 모니터의 울렁이는 효과 내는 방법
본 게시글에서는 고도엔진에서 스크린 쉐이더를 통해 CRT 모니터의 울렁이는 효과와 색상이 어긋나는 효과를 표현해보고자 한다. 물론 CRT 모니터 특유 감성의 색 번짐과 블룸 효과까지는 적용하지 않고, 색이 어긋나거나 울렁이는 효과만 적용하는 것을 목표로 한다.
우선 기본적으로 월드에는 게임 요소가 배치되고, 이를 추후 렌더링하는 과정인 PostProcessing 단계에서 울렁 효과와 색상이 어긋나는 효과를 적용해야 한다. 따라서 기본적인 월드 배치를 한 뒤에, 캔버스 레이어를 통해 스크린 좌표계에 고정적으로 표시될 객체를 나두고, 쉐이더로 화면을 그려주면 된다.
위 장면은 그냥 일반적인 게임 월드 구성이다. 캐릭터를 등장시키고, 움직임을 넣고, 밑 바닥 배경을 만든 것이다.
이렇게 게임을 실행하면 아주 평범한 게임 화면이 실행된다.
여기서 이제 우리는 위에 덧대어 화면 변조를 진행해야 한다. 즉 현재 이미 그려진 화면을 가져와 어떤 편집 작용을 거친 뒤 화면위에 다시 그려주면 되는 것이다. 그러기 위해서는 캔버스 레이어를 등록하고, ColorRect 를 하나 추가한다.
캔버스 레이어의 위치는 아무대나 상관 없다. 제일 바깥에 생성한 뒤에 안에 ColorRect를 생성하고, ColorRect 의 Anchors Pres 를 공간 전체로 지정하여 화면 전체 크기에 적용되도록 한다.
공간 전체로 지정하면, 화면 크기만한 공간이 흰색 공간으로 채워지고, 게임을 실행하면 화면에는 아무것도 표시되지 않고 흰색만 표시되는 것을 확인할 수 있다. 즉 CanvasLayer는 현재 월드의 카메라에 상관 없이 스크린 좌표를 의미하는 공간으로 CanvasLayer 안에 배치된 모든 객체는 무조건 스크린 좌표 기준으로 출력된다. 게다가 안에 존재하는 ColorRect를 전체 공간에 맞춰 Anchors 를 지정했기에, 화면 전체는 항상 ColorRect가 채우게 된다. 즉 배경에는 이미 월드 카메라가 그려지는 것이고, 이 위에 스크린 좌표 공간을 기준으로 다시 흰색 ColorRect가 덮어씌워 그려지고 있는 것이다. 이제 이 ColorRect 위에 뒤에 그려진 화면을 다시 그려주면 되는 것이다.
ColorRect에 Shader을 추가하고, 아래와 같은 코드를 작성한다.
shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture;
void fragment() {
vec2 screenUV = SCREEN_UV;
vec3 screenColor = texture(screen_texture, screenUV).rgb;
COLOR = vec4(screenColor, 1.0);
}
위 코드는, 현재 객체가 그려지는 스크린 UV 좌표 (0~1) 사이 값을 가져와 그 위치에 해당하는 배경 스크린의 색상을 가져온다. 이 때 배경 스크린 텍스처는, 본인 객체가 렌더링되기 전의 텍스처라고 보면 된다. 즉 현재 쉐이더가 적용된 ColorRect을 제외한 전체 스크린 이미지의 각 색상을 가져와 ColorRect 위에 뿌리는 것이다. 즉 투명하게 보이게 된다.
색을 +0.3 해주면, 그 만큼 밝아지는 것을 확인할 수 있다.
이제 여기에, 울렁이는 효과를 위해서, UV y 좌표에 대해 sin 값을 넣어 밝아졌다 어두워졌다하는 패턴 무늬를 만든다.
vec3 velocity = vec3(mix(0.9,1.0, abs(cos(screenUV.y * 2.0 * 3.141592 * 30.0 + TIME * 20.0 ))));
위 코드는 screenUV 의 y에 따라 2파이 및 반복할 주기를 곱해 약 30개의 줄무늬가 보이게끔하고, TIME을 더해 약 초에 20 값 정도씩 아래로 내려가게끔 하는 코드이다. 이 렇게 구한 velocity 를 color에 곱해주면 무늬가 적용된다. 원래 cos 값은 -1 ~ 1의 값을 반환하는데 abs 절댓값을 취해 0 ~ 1의 값이 반환되게끔 하며, 그리고 0 ~ 1 의 구간을 0.9 ~ 1.0 으로 선형 보간하여 최종적으로 0.9 ~ 1.0의 세기 값이 나오게끔 한다. 밝을 수록 원본 색상에 가까워지는 것이고 낮을 수록 어두워지는 것이다.
vec3 velocity = vec3(mix(0.9,1.0, abs(cos(screenUV.y * 2.0 * 3.141592 * 30.0 + TIME * 20.0 ))));
COLOR = vec4(texture(screen_texture, screenUV).xyz * velocity, 1.0);
줄무늬가 적용된 것을 확인할 수 있다.
줄무늬 효과에 더해서, 색이 어긋나게 하기 위해 현재 가져온 색상 지점 말고, 약간 오차가 존재하는 UV 지점의 색상 지점을 가져와, R, G, B 을 서로 다르게 지정해주면 된다.
즉 screenUV 는 현재 그려지는 스크린 좌표라고 했는데, +0.01 된 값, +0.02 된 값을 각각 가져와 총 3개의 미세하게 다른 지점에 대해 텍스처를 샘플링하고, 각각 R, G, B 요소만 가져와 색상으로 그려주면 된다.
shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture;
void fragment() {
vec2 screenUV = SCREEN_UV;
vec3 screenColorR = texture(screen_texture, vec2(screenUV.x, screenUV.y+0.005)).xyz;
vec3 screenColorG = texture(screen_texture, vec2(screenUV.x, screenUV.y+0.01)).xyz;
vec3 screenColorB = texture(screen_texture, vec2(screenUV.x, screenUV.y+0.015)).xyz;
vec3 velocity = vec3(mix(0.9,1.0, abs(cos(screenUV.y * 2.0 * 3.141592 * 30.0 + TIME * 20.0 ))));
vec3 resultColor = vec3(screenColorR.r, screenColorG.g, screenColorB.b) * velocity;
COLOR = vec4(resultColor, 1.0);
}
유의할 사항은, 현재 텍스처 스크린 UV 좌표에서 임의로 값을 더했는데, 이 값은 0 ~ 1 의 보간된 너비 정보이기 때문에, 화면 해상도에 따라 정도가 다를 수 있다. 정확하게 계산하기 위해서는 화면 해상도 width와 height를 가져와 한 픽셀당 의미하는 UV 크기를 가져온뒤, 의도하고자 하는 UV 값 정도만 가져와 색상을 섞어야 한다.