GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

글리치 전환 효과 9가지 — css 글리치 효과 모음

css 글리치 효과 를 찾아보면 대부분 필터 하나를 얹고 끝나지만, 화면이 갑자기 다음 내용으로 뚝 끊겨 넘어가면 오류처럼 보인다 — 그래서 오프셋 값 하나로 RGB 채널을 어긋냈다 되돌리는 전환, 노이즈 강도 값으로 화이트노이즈를 잠깐 띄웠다 낮추는 전환, 해독 진행률 값으로 무작위

아홉 개 모두 캔버스 2D 컨텍스트 위에서 돌아갑니다. 사진·아이콘·텍스트 소스를 오프스크린 캔버스에 먼저 그려 두고, 그 픽셀을 progress 라는 값 하나로 재배치하거나 섞어 다시 그리는 구조라 라이브러리도, 외부 이미지도, WebGL 도 쓰지 않습니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

코드는 아홉 조각, 계산 한 겹은 값 하나로 요약됩니다

아홉 개를 갈라보면 픽셀을 옮기는 계산은 제각각이지만 진행률을 받는 값 하나(RENDER(p) 의 p)로 전환 전체를 통제한다는 틀은 같습니다. 01·02·03·04·06·07·08 은 getImageData 로 읽은 원본 배열에서 다른 좌표를 골라 출력 버퍼에 옮겨 쓰고, 05 는 좌표 해시로 만든 노이즈 색과 원본 색을 섞으며, 09 는 애초에 픽셀 배열을 읽지 않고 fillText 로 글자만 직접 그립니다.

01채널 어긋남 값으로 갈라졌다 합쳐지는 이미지 전환

getImageData 로 원본 픽셀을 한 번 읽어 두고, 행마다 흔들림 계수(rowJitter)를 고정 시드 LCG 로 미리 계산해 둡니다. RENDER 는 R 은 왼쪽, B 는 오른쪽으로 각각 오프셋만큼 다른 소스 픽셀을 읽어 합성하고, progress 값이 0 에 가까워질수록 오프셋이 줄어 다시 한 장의 또렷한 이미지로 겹칩니다.

  function PRECOMPUTE() {
    var iw = cv.width, ih = cv.height;
    data = bctx.getImageData(0, 0, iw, ih).data;
    outImg = ctx.createImageData(iw, ih);
    var rng = makeLcg(SEED + 7);
    rowJitter = new Float32Array(ih);
    for (var y = 0; y < ih; y++) rowJitter[y] = 1 + (rng() * 2 - 1) * ROW_JITTER;
  }
채널 어긋남 값으로 갈라졌다 합쳐지는 이미지 전환
새 창에서 열기

01 · 채널 어긋남 값으로 갈라졌다 합쳐지는 이미지 전환 — 측정 누적면적 48.733% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

RGB_OFFSET_MAXgetImageDataputImageData

02가로줄 밀림 값이 자리를 되찾는 화면 전환

행마다 미리 계산해 둔 rowOffsets 값을 progress 로 스케일해, 가로줄 단위로 서로 다른 폭만큼 밀어 읽어옵니다. progress 가 줄면 밀림 폭도 함께 줄어 줄마다 제자리를 찾고, 다음 화면이 이어붙습니다.

  function RENDER(p) {
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var shift = Math.round(rowOffsets[y] * p);
      var rowBase = y * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - shift, 0, iw - 1);
        var k = (rowBase + x) * 4;
        var sk = (rowBase + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }
가로줄 밀림 값이 자리를 되찾는 화면 전환
새 창에서 열기

02 · 가로줄 밀림 값이 자리를 되찾는 화면 전환 — 측정 누적면적 28.934% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

rowOffsetsstripeWputImageData

03블록 좌표 값이 흩어졌다 맞춰지는 화면 전환

화면을 BLOCK_COLS x BLOCK_ROWS 격자로 나눠 블록마다 흩어질 좌표(blockDx·blockDy)를 미리 계산해 둡니다. 픽셀마다 자신이 속한 블록의 이동량을 progress 로 스케일해 원본의 다른 위치를 읽어오고, 값이 0 이 되면 블록이 제자리로 돌아와 하나의 화면이 됩니다.

  function RENDER(p) {
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var by = Math.min(BLOCK_ROWS - 1, Math.floor(y / blockH));
      for (var x = 0; x < iw; x++) {
        var bx = Math.min(BLOCK_COLS - 1, Math.floor(x / blockW));
        var bi = by * BLOCK_COLS + bx;
        var sx = clampI(Math.round(x - blockDx[bi] * p), 0, iw - 1);
        var sy = clampI(Math.round(y - blockDy[bi] * p), 0, ih - 1);
        var k = (y * iw + x) * 4;
        var sk = (sy * iw + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }
블록 좌표 값이 흩어졌다 맞춰지는 화면 전환
새 창에서 열기

03 · 블록 좌표 값이 흩어졌다 맞춰지는 화면 전환 — 측정 누적면적 32.552% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

blockDxblockDyputImageData

04밝기 정렬 진행률 값이 되돌리는 이미지 전환

PRECOMPUTE 단계에서 행마다 픽셀을 밝기(brightness) 기준으로 정렬한 인덱스(sortedIdx)를 만들어 둡니다. RENDER 는 progress 값만큼 원래 위치와 정렬된 위치 사이를 보간해 읽어와, 값이 1 에 가까우면 밝기순으로 늘어진 줄무늬가, 0 에 가까우면 원본 이미지가 됩니다.

  function RENDER(p) {
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var row = sortedIdx[y];
      var base = y * iw;
      for (var x = 0; x < iw; x++) {
        var si = row[x];
        var sx = clampI(Math.round(x + (si - x) * p), 0, iw - 1);
        var k = (base + x) * 4;
        var sk = (base + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }
밝기 정렬 진행률 값이 되돌리는 이미지 전환
새 창에서 열기

04 · 밝기 정렬 진행률 값이 되돌리는 이미지 전환 — 측정 누적면적 28.2% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

sortedIdxbrightnessclampI

05노이즈 강도 값이 잦아드는 콘텐츠 전환

픽셀마다 프레임 카운터와 좌표를 섞은 해시(hash01)로 노이즈 색을 새로 만들고, progress 값으로 원본과 섞는 비율을 정합니다. 가장자리 없이 화면 전체가 한 번에 하얗게 튀었다가 값이 0 으로 잦아들며 아래 콘텐츠가 드러납니다.

    frameCounter = (frameCounter + 1) >>> 0;
    var o = outImg.data;
    var fSalt = Math.imul(frameCounter, 2246822519) >>> 0;
    for (var y = 0; y < ih; y++) {
      var ySalt = Math.imul(y, 668265263) >>> 0;
      var base = y * iw;
      for (var x = 0; x < iw; x++) {
        var seed = (Math.imul(x, 374761393) ^ ySalt ^ fSalt) >>> 0;
        var h0 = hash01(seed);
        var h1 = hash01(seed ^ 0x9e3779b9);
        var h2 = hash01(seed ^ 0x85ebca6b);
        var h3 = hash01(seed ^ 0xc2b2ae35);
        var g = 198 + h0 * 57;
        var nr = clamp255(g + (h1 - 0.5) * 60);
        var ng = clamp255(g + (h2 - 0.5) * 60);
        var nb = clamp255(g + (h3 - 0.5) * 60);
        var k = (base + x) * 4;
        o[k] = data[k] * (1 - p) + nr * p;
        o[k + 1] = data[k + 1] * (1 - p) + ng * p;
        o[k + 2] = data[k + 2] * (1 - p) + nb * p;
        o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
노이즈 강도 값이 잦아드는 콘텐츠 전환
새 창에서 열기

05 · 노이즈 강도 값이 잦아드는 콘텐츠 전환 — 측정 누적면적 48.761% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

NOISE_BURST_DURATIONframeCounterhash01

06색수차 진폭 값이 감쇠하는 화면 전환

phase 값을 매 프레임 누적해 사인 곡선으로 R·B 채널의 오프셋 방향을 계속 바꾸고, G 채널은 다른 위상으로 살짝만 흔듭니다. CHROMATIC_DRIFT_AMPLITUDE 에 progress 를 곱한 값이 흔들림 폭을 정해, 값이 줄면 세 채널이 한 겹으로 합쳐지며 안정됩니다.

    var amp = CHROMATIC_DRIFT_AMPLITUDE * dpr * p;
    var dxR = Math.round(amp * Math.sin(phase));
    var dxB = Math.round(amp * Math.sin(phase + Math.PI));
    var dyG = Math.round(amp * 0.4 * Math.sin(phase * 1.3 + 1.1));
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var base = y * iw;
      var gy = clampI(y + dyG, 0, ih - 1);
      var gBase = gy * iw;
      for (var x = 0; x < iw; x++) {
        var rx = clampI(x - dxR, 0, iw - 1);
        var gx = clampI(x, 0, iw - 1);
        var bx = clampI(x - dxB, 0, iw - 1);
        var k = (base + x) * 4;
        var kr = (base + rx) * 4;
        var kg = (gBase + gx) * 4;
        var kb = (base + bx) * 4;
        o[k] = data[kr];
        o[k + 1] = data[kg + 1];
        o[k + 2] = data[kb + 2];
        o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
색수차 진폭 값이 감쇠하는 화면 전환
새 창에서 열기

06 · 색수차 진폭 값이 감쇠하는 화면 전환 — 측정 누적면적 38.372% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

CHROMATIC_DRIFT_AMPLITUDEdxReaseInOut

07트래킹 흔들림 값이 안정을 찾는 화면 전환

wrap() 으로 화면을 위아래로 순환시켜 세로 밀림(TAPE_WOBBLE_AMPLITUDE)을 만들고, 행마다 해시로 얻은 값으로 가로 지터(TAPE_JITTER_AMPLITUDE)를 더합니다. progress 가 줄수록 세로 밀림과 가로 지터가 함께 잦아들어 화면이 안정된 위치를 찾습니다.

  function RENDER(p) {
    phaseRoll += 0.33;
    frameCounter = (frameCounter + 1) >>> 0;
    var rollY = Math.round(TAPE_WOBBLE_AMPLITUDE * dpr * p * Math.sin(phaseRoll));
    var fSalt = Math.imul(frameCounter, 2246822519) >>> 0;
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var srcY = wrap(y - rollY, ih);
      var jh = hash01((Math.imul(srcY, 668265263) ^ fSalt) >>> 0);
      var jitterX = Math.round(TAPE_JITTER_AMPLITUDE * dpr * p * (jh * 2 - 1));
      var base = y * iw;
      var srcBase = srcY * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - jitterX, 0, iw - 1);
        var k = (base + x) * 4;
        var sk = (srcBase + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }
트래킹 흔들림 값이 안정을 찾는 화면 전환
새 창에서 열기

07 · 트래킹 흔들림 값이 안정을 찾는 화면 전환 — 측정 누적면적 44.612% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TAPE_WOBBLE_AMPLITUDErollYwrap

08찢어짐 오프셋 값이 맞물리는 화면 전환

PRECOMPUTE 에서 화면을 가르는 경계선(tearY)을 LCG 로 미리 정해 두고, 경계 위쪽과 아래쪽을 TEAR_OFFSET_MAX 값만큼 서로 반대 방향으로 어긋나게 읽습니다. progress 값이 0 이 되면 오프셋도 0 이 돼 위아래 조각이 다시 맞물려 하나의 화면이 됩니다.

  function RENDER(p) {
    var off = Math.round(TEAR_OFFSET_MAX * dpr * p);
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var dx = y < tearY ? off : -off;
      var base = y * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - dx, 0, iw - 1);
        var k = (base + x) * 4;
        var sk = (base + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }
찢어짐 오프셋 값이 맞물리는 화면 전환
새 창에서 열기

08 · 찢어짐 오프셋 값이 맞물리는 화면 전환 — 측정 누적면적 37.565% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TEAR_OFFSET_MAXtearYclampI

09해독 진행률 값이 실제 글자로 바꾸는 텍스트 전환

getImageData 를 전혀 쓰지 않고 fillText 로 글자를 직접 그리는 방식입니다. drawNoiseBand 가 아직 해독되지 않은 구간을 칸(BLOCK) 단위 노이즈 사각형으로 채우고, charX 로 미리 계산해 둔 각 글자 위치를 기준으로 진행률이 100 에 가까워질수록 왼쪽부터 순서대로 실제 글자가 고정됩니다.

  function drawNoiseBand(p) {
    if (p <= 0.02) return;
    var bandTop = baselineY - fontSize * 1.15;
    var bandH = fontSize * 1.9;
    var rows = Math.max(1, Math.ceil(bandH / BLOCK));
    var cols = Math.max(1, Math.ceil(iw / BLOCK));
    for (var r = 0; r < rows; r++) {
      var by = bandTop + r * BLOCK;
      for (var c = 0; c < cols; c++) {
        var bx = c * BLOCK;
        var s = (Math.imul(c + 1, 668265263) ^ Math.imul(r + 1, 2246822519) ^ Math.imul(frameCounter, 3266489917) ^ SEED) >>> 0;
        var v = hash01(s);
        if (v < 0.34) {
          ctx.fillStyle = v < 0.14 ? BAR_COLOR : GHOST_COLOR;
          ctx.globalAlpha = (0.55 + v) * p * 0.85;
          ctx.fillRect(bx, by, BLOCK - 1, BLOCK - 1);
        }
      }
    }
    ctx.globalAlpha = 1;
  }
해독 진행률 값이 실제 글자로 바꾸는 텍스트 전환
새 창에서 열기

09 · 해독 진행률 값이 실제 글자로 바꾸는 텍스트 전환 — 측정 누적면적 19.26% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

DECODE_SPEEDcharXctx.fillText

왜 픽셀 계산인가 — transform 만으로는 안 되는 이유

CSS transform·filter 는 레이어 전체에 같은 값 하나를 적용하는 데는 강하지만, 행마다 다른 오프셋을 주거나(02·07) 블록마다 다른 좌표로 흩거나(03) 밝기순으로 픽셀을 재배열하는(04) 것처럼 픽셀 하나하나가 서로 다른 값을 갖는 계산은 못 합니다. getImageData 로 배열을 직접 들고 있어야 픽셀 단위로 다른 규칙을 적용할 수 있고, 09 처럼 배열 대신 문자 단위로 계산해도 같은 이유로 CSS 속성 하나로는 흉내 낼 수 없습니다.

항목 누적면적 강도_평균 움직인프레임
01 채널 어긋남 48.7% 45.8 23/23
02 가로줄 밀림 28.9% 105.9 23/23
03 블록 셔플 32.6% 134.4 23/23
04 밝기 정렬 28.2% 149.6 23/23
05 노이즈 버스트 48.8% 29.7 22/23
06 색수차 드리프트 38.4% 74.9 23/23
07 트래킹 흔들림 44.6% 212.2 23/23
08 디지털 티어 37.6% 195.6 21/23
09 텍스트 디코드 19.3% 50.6 22/23

이 아홉 개는 실제로 어디에 붙일까요?

로그인 실패나 결제 오류처럼 「무언가 어긋났다」는 신호를 화면으로 보여줘야 하는 자리에는 01 의 채널 어긋남이나 08 의 티어가 맞습니다. 오류 메시지가 뜨기 직전에 화면이 순간적으로 갈라졌다 맞물리면, 텍스트만으로는 전달하기 어려운 「끊김」의 감각이 먼저 눈에 들어옵니다.

라이브 방송이나 실시간 대시보드가 다음 장면으로 넘어가는 경계에는 05 의 노이즈 버스트나 07 의 트래킹 흔들림이 어울립니다. 옛 방송 장비가 채널을 바꿀 때 잠깐 화면이 흐트러지던 인상을 재현하면서도, 값이 0 으로 잦아드는 순간 다음 콘텐츠가 또렷하게 자리를 잡습니다.

404 페이지나 「접근할 수 없는 콘텐츠」 안내에는 09 의 해독 전환이 맞습니다. 무작위 문자가 빠르게 깜빡이다가 왼쪽부터 실제 글자로 고정되는 흐름은, 보이지 않던 것이 계산을 거쳐 드러난다는 신호를 문구 자체로 보여줍니다. 이런 글리치 효과 텍스트는 히어로 헤드라인이나 로그인 성공 메시지처럼 문구 자체가 핵심인 자리에도 그대로 옮겨 씁니다.

388 편의 리빌 와이프와 진행 방향이 다릅니다

이미지 전환 효과 9종 — 캔버스 픽셀 계산으로 완성되는 리빌 편은 대각 좌표나 노이즈 문턱값을 기준으로 화면 한쪽에서 다른 쪽으로 「완성되어 가는」 방향만 다룹니다. 이 편은 반대로 완성된 화면에서 시작해 값이 커질수록 먼저 무너지고, 값이 다시 0 으로 돌아오면서 맞춰지는 구조라 진행률이 오가는 두 방향을 다 씁니다. reduced-motion 정지 상태도 그래서 다릅니다 — 388 은 progress 를 0.5 부근에 고정하지만, 이 편의 아홉 개는 전부 progress 0(무너짐 없는 원본 상태)에서 멈춥니다.

fit() 을 건너뛰면 리사이즈에서 깨지는 함정

아홉 개 모두 PRECOMPUTE 단계에서 캔버스 크기 기준으로 원본 픽셀·오프셋 배열을 한 번만 만들어 둡니다. resize 리스너가 fit() 을 다시 불러 패널의 실제 픽셀 크기(devicePixelRatio 포함)로 캔버스를 새로 잡고 PRECOMPUTE 를 재실행하는데, 이 재계산을 건너뛰고 CSS 로만 캔버스 요소 크기를 늘리면 저장된 배열과 실제 화면 크기가 어긋나 03 의 블록 경계나 08 의 티어 선이 비뚤어집니다. 아홉 개 코드를 한 번에 내려받는 압축 파일은 8mgtprm2 을 넣어야 열리고, 잠금은 아래 카드에서 풉니다.

reduced-motion 에서는 진행률 0 에 멈추고 클릭으로만 토글합니다

prefers-reduced-motion 이 참이면 아홉 개 모두 requestAnimationFrame 의 자동 왕복 루프(driveTick, 2000ms 주기 삼각파)를 돌리지 않고 progress 를 0 에 고정해 둡니다. 클릭이나 Enter 키를 누르면 그 순간 progress 가 0 과 1 사이를 한 번만 토글해 결과를 보여주고, 다시 0 으로 돌아오면 멈춥니다. 값이 아니라 사람의 입력이 전환을 일으키는 유일한 트리거가 된다는 점에서, 자동으로 계속 움직이는 일반 모드와 구분됩니다.

값 세 개를 바꾸면 같은 항목이 달라 보입니다

항목 값 바꾸면
01 RGB_OFFSET_MAX 24px → 60px 최대치를 24px 에서 60px 로 올리면 로드 순간 R·B 채널이 더 크게 벌어졌다가 0 으로 돌아오는 폭이 커져 갈라짐이 더 거칠게 느껴집니다.
05 NOISE_BURST_DURATION 560ms → 900ms 지속 시간을 560ms 에서 900ms 로 늘리면 노이즈가 사그라드는 데 걸리는 시간이 길어져 화이트아웃이 더 오래 머무는 느낌을 줍니다.
09 DECODE_SPEED 1.7 → 1.0 해독 속도를 1.7 에서 1.0 으로 낮추면 무작위 문자가 실제 글자로 바뀌기까지 걸리는 프레임 수가 늘어 왼쪽부터 고정되는 속도가 눈에 띄게 느려집니다.

이미지 글리치 효과 예제는 어디서 참고하나

픽셀 배열을 직접 읽고 쓰는 계산의 근거는 getImageData 와 putImageData 문서에 정리돼 있고, 프레임마다 다시 그리는 루프의 표준은 requestAnimationFrame 문서를 따릅니다. reduced-motion 분기의 판정 기준은 prefers-reduced-motion 문서를 그대로 씁니다.

화면을 값으로 바꾸는 다른 방식은 CSS 화면 전환 효과 9종 — 화면이 바뀌는 순간 모음 편에서 캔버스 없이 CSS 속성만으로 다룹니다. 노이즈를 값으로 계산해 다른 결과를 만드는 쪽은 펄린 노이즈 지형 9종 — 라이브러리 없이 계산만으로 편에서 지형의 높낮이로, 이 편에서는 05 의 화이트노이즈 강도로 각각 씁니다.

이 아홉 개를 쓰기 전에 자주 나오는 질문

아홉 개 다 canvas 태그가 필요한가요?

네. 이미지·텍스트를 오프스크린 버퍼에 먼저 그려 둔 뒤 그 픽셀을 읽어 재배치하는 구조라, <canvas> 요소와 2D 컨텍스트가 있어야 합니다. DOM 요소에 CSS 로만 적용하는 방식으로는 옮길 수 없습니다. 그래서 직접 글리치 효과 만들기에 나설 때는 대상 DOM 대신 캔버스에 픽셀 데이터를 먼저 마련하는 순서부터 잡아야 합니다.

Math.random() 대신 LCG 를 쓰는 이유는 무엇인가요?

고정 시드 LCG(makeLcg)를 쓰면 같은 시드에서는 매번 같은 흔들림·오프셋 값이 나옵니다. 측정값(누적면적·강도_평균)을 재현 가능하게 재려면 매번 값이 달라지는 진짜 난수 대신 결정적인 값이 필요합니다.

09 는 왜 getImageData 를 쓰지 않나요?

09 는 이미지가 아니라 글자를 다룹니다. 픽셀 배열을 읽어 재배치할 대상이 없고, drawNoiseBand 로 칸을 채우거나 fillText 로 글자를 그리는 것만으로 해독 진행률을 표현할 수 있어 픽셀 읽기 자체가 필요 없습니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.