본문 바로가기
Opengles 3.0 with Android

9.3 툰 셰이딩: 엣지 디텍팅과 카툰화

by SimonLee 2024. 12. 25.

툰 셰이딩을 구현하기 위해서는 **엣지 디텍팅(Edge Detection)**을 먼저 수행하고, 경계가 아닌 영역을 **카툰화(Cartoonization)**하는 두 단계를 거칩니다.

  1. 엣지 디텍팅:
    1. 소벨 마스크를 사용해 주변 픽셀의 변화량(Gradient)을 계산.
    2. 변화량이 GradientThreshold를 초과하면 경계로 판단.
  2. 카툰화:
    1. 경계가 아닌 영역을 양자화(Quantization)하여 단순화된 색상 표현.
    2. quantizationFactor 값에 따라 보간 정도와 단색화 수준 조정.

 

엣지 디텍팅 + 카툰화

 

 

 

1. 엣지 디텍팅 (Edge Detection)

Input 설정

  1. pixelSize (픽셀 크기):
    •  1 / Texture Size로 계산되며, 텍스처 좌표에서 픽셀 간격을 나타냅니다.
    •  x, y 방향에서 각 픽셀의 크기를 정의하는 데 사용됩니다.
  2. Tex1 (입력 텍스처):
    •  툰 셰이딩을 적용할 원본 텍스처입니다.

 

2. 카툰화 (Cartoonization)

카툰화 과정

  1. 양자화 (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