툰 셰이딩을 구현하기 위해서는 **엣지 디텍팅(Edge Detection)**을 먼저 수행하고, 경계가 아닌 영역을 **카툰화(Cartoonization)**하는 두 단계를 거칩니다.
- 엣지 디텍팅:
- 소벨 마스크를 사용해 주변 픽셀의 변화량(Gradient)을 계산.
- 변화량이 GradientThreshold를 초과하면 경계로 판단.
- 카툰화:
- 경계가 아닌 영역을 양자화(Quantization)하여 단순화된 색상 표현.
- quantizationFactor 값에 따라 보간 정도와 단색화 수준 조정.
1. 엣지 디텍팅 (Edge Detection)
Input 설정
- pixelSize (픽셀 크기):
- 1 / Texture Size로 계산되며, 텍스처 좌표에서 픽셀 간격을 나타냅니다.
- x, y 방향에서 각 픽셀의 크기를 정의하는 데 사용됩니다.
- Tex1 (입력 텍스처):
- 툰 셰이딩을 적용할 원본 텍스처입니다.
2. 카툰화 (Cartoonization)
카툰화 과정
- 양자화 (Quantization):
- 경계가 아닌 영역의 색상을 단순화.
- **quantizationFactor**를 사용하여 RGB 값을 특정 단계로 나눔:
vec3 rgb = texture(Tex1, TexCoord).rgb * quantizationFactor;
rgb += vec3(0.5); // 반올림 보정
ivec3 intrgb = ivec3(rgb); // 정수 변환
rgb = vec3(intrgb) / quantizationFactor; // 양자화 후 복원
quantizationFactor 값:
- 값이 작을수록 (예: 2.0): 색상이 단조로워지고, 강한 단색화.
- 값이 클수록 (예: 32.0): 색상 변화가 부드러워지고 자연스러운 보간 유지
경계와 내부 영역 구분:
- 경계로 판단된 픽셀은 검은색으로 처리
if (distance > GradientThreshold) {
outColor = vec4(0.0, 0.0, 0.0, 1.0); // 경계는 검은색
}
경계가 아닌 영역은 양자화된 색상으로 처리:
else {
outColor = vec4(rgb, 1.0); // 내부 영역은 단순화된 색상
}
3. 주요 파라미터
GradientThreshold
- 경계 감지의 민감도를 조정.
- 값이 클수록 경계가 명확하며, 약한 경계는 무시.
quantizationFactor
- 색상 양자화의 세밀도를 조정.
- 값이 작을수록 강한 카툰 효과(단색화), 값이 클수록 자연스러운 보간.
전체 코드
#version 300 es
precision mediump float;
in vec2 TexCoord; // 텍스처 좌표 입력
uniform vec2 pixelSize; // 픽셀 크기 (텍스처 좌표 단위)
uniform sampler2D Tex1; // 텍스처 샘플러
layout(location = 0) out vec4 outColor; // 출력 색상 (Fragment Shader 결과)
uniform float ScreenCoordX; // 화면상의 기준 X 좌표
uniform float GradientThreshold; // Sobel 경계 감지 임계값
uniform float quantizationFactor; // 양자화 단계 값 (Quantization Factor)
float p00, p10, p20, p01, p21, p02, p12, p22, x, y, px , py, distance;
vec3 lum = vec3(0.2126, 0.7152, 0.0722); // RGB → Luminance 변환 가중치 (사람의 시각 민감도 기반)
void main()
{
// 특정 X 좌표 근처의 픽셀을 빨간색으로 표시 (디버깅용 또는 하이라이트)
if(gl_FragCoord.x < ScreenCoordX + 1.0 && gl_FragCoord.x > ScreenCoordX - 1.0){
outColor = vec4(1.0, 0.0, 0.0, 1.0); // 빨간색 출력
return;
}
// 픽셀 간격 설정
x = pixelSize.x; // x 방향 픽셀 간격
y = pixelSize.y; // y 방향 픽셀 간격
// 화면상의 특정 X 좌표 이후에만 Sobel 연산 수행
if(gl_FragCoord.x > ScreenCoordX){
// Sobel 연산을 위한 3x3 주변 픽셀의 밝기 계산
p00 = dot(texture(Tex1, TexCoord + vec2(-x, y)).rgb, lum); // 왼쪽 위 픽셀
p10 = dot(texture(Tex1, TexCoord + vec2(-x, 0.)).rgb, lum); // 왼쪽 중간 픽셀
p20 = dot(texture(Tex1, TexCoord + vec2(-x, -y)).rgb, lum); // 왼쪽 아래 픽셀
p01 = dot(texture(Tex1, TexCoord + vec2(0., y)).rgb, lum); // 위쪽 픽셀
p21 = dot(texture(Tex1, TexCoord + vec2(0., -y)).rgb, lum); // 아래쪽 픽셀
p02 = dot(texture(Tex1, TexCoord + vec2(x, y)).rgb, lum); // 오른쪽 위 픽셀
p12 = dot(texture(Tex1, TexCoord + vec2(x, 0.)).rgb, lum); // 오른쪽 중간 픽셀
p22 = dot(texture(Tex1, TexCoord + vec2(x, -y)).rgb, lum); // 오른쪽 아래 픽셀
// Sobel 연산: x, y 방향의 Gradient 계산
px = p00 + 2.0 * p10 + p20 - (p02 + 2.0 * p12 + p22); // x 방향 변화량
py = p00 + 2.0 * p01 + p02 - (p20 + 2.0 * p21 + p22); // y 방향 변화량
// Gradient 크기 계산 (L2 노름, 제곱근 생략으로 성능 향상)
distance = px * px + py * py;
// Gradient 크기가 임계값을 넘으면 경계로 판단
if (distance > GradientThreshold ){
outColor = vec4(1.0, 0.0, 0.0, 1.0); // 경계를 빨간색으로 표시
}
else {
// 텍스처 색상 값을 양자화(Quantization)하여 내부 색상 처리
vec3 rgb = texture(Tex1, TexCoord).rgb * quantizationFactor; // 값을 확대하여 정수화 준비
// 반올림 효과를 위한 0.5 추가 (버림 방지)
rgb += vec3(0.5, 0.5, 0.5);
ivec3 intrgb = ivec3(rgb); // 정수 변환 (버림)
// 양자화 팩터를 제거하여 0~1 범위로 복원
rgb = vec3(intrgb) / quantizationFactor;
// 최종 색상 출력 (양자화된 색상)
outColor = vec4(rgb, 1.0);
}
}
else{
// 특정 X 좌표 이전의 픽셀은 원본 텍스처 색상 사용
outColor = texture(Tex1, TexCoord);
}
return;
}
'Opengles 3.0 with Android' 카테고리의 다른 글
10.1 Scene Graph (Transformation Graph) (0) | 2025.01.30 |
---|---|
9_4 엠보싱 효과 (0) | 2024.12.29 |
Chapter 9.2 Gawsian Blur ( 2 step ) (0) | 2024.12.01 |
Chapter 7.5. displace map (0) | 2024.11.18 |
Chapter 7.4 FBO (0) | 2024.11.17 |