2018년 9월 디자인과 코딩을 함께 다루는 유튜브 채널을 열었습니다. "타입과 미디어" 사이트는 당분간 유지할 계획이지만, 새로운 정보는 유튜브 비디오를 통해서 소개하겠습니다. 많은 관심 부탁드립니다! — 정대인

변환

세부 목차

포토샵, 애프터 이펙츠와 같은 컴퓨터 그래픽 소프트웨어를 사용하면 translate, rotate, scale과 같이 오브젝트에 변화를 주는 기능을 자주 사용하게 됩니다. 프로세싱도 각각에 해당하는 함수를 제공합니다. 하지만, 프로세싱에서 변환(transformations)을 사용할 때 주의해야 할 것은, 그런 변화의 주체가 오브젝트가 아니라 오브젝트가 놓인 매트릭스(matrix)라는 것입니다.

매트릭스를 스케치북 종이라고 생각하면 이해가 조금 쉽습니다. 우리는 종이 위에 그림을 그리고, 그려진 그림을 직접 변환하는 대신에, 스케치북을 움직이고, 회전하고, 크기 비율에 변화를 줍니다. 프로세싱에서는 pushMatrix()를 사용해서 종이를 얹을 수 있습니다. 그 이후에, 그려지는 도형들은 모두 해당 종이 위에 그려지게 됩니다. puahMatrix()를 여러 번 호출해서 종이 위에 다른 종이를 얹을 수도 있습니다. 종이(매트릭스)를 사용한 후에는, popMatrix() 함수를 이용해서 얹었던 종이 수만큼 제거해주어야 합니다.

이동 (translate)

아래 예제를 살펴보면 원을 (0, 0) 좌표에 그렸으나 결과물은 원점에서 조금 떨어진 곳에 그려진 것을 볼 수 있습니다. 이것은 바로 원을 그리기 이전에 translate(20, 20)을 통해서 원이 그려질 종이(매트릭스)를 옮겨놓았기 때문입니다. 원은 현재 매트릭스의 좌표계에서는 (0, 0)에 그려진 것이 맞습니다.

size(600, 100);
background(255);

translate(20, 20);
ellipse(0, 0, 20, 20);
Your browser does not support canvas.

매트릭스의 변환은 축적됩니다. 아래 예제를 보면 실행창 화면을 기준으로 처음 원은 (20, 20)에, 두번째 원은 (60, 60)에 그려진 것을 볼 수 있습니다.

size(600, 100);
background(255);

translate(20, 20);
ellipse(0, 0, 20, 20);

translate(40, 40);
ellipse(0, 0, 20, 20);
Your browser does not support canvas.

여러 개의 매트릭스를 사용하고자 할 때, pushMatrix()popMatrix()로 매트릭스를 추가하거나 제거할 수 있습니다. 아래의 예제에서, 두 원은 각각 다른 매트릭스 좌표계를 이용합니다.

size(600, 100);
background(255);

pushMatrix();
translate(20, 20);
ellipse(0, 0, 20, 20);
popMatrix();

pushMatrix();
translate(40, 40);
ellipse(0, 0, 20, 20);
pushMatrix();
Your browser does not support canvas.

translate()은 글자를 원하는 위치로 이동할 때에 유용하게 사용할 수 있습니다. 여러 도형으로 이루어진 어떤 글자를 이동하려고 할 때, 글자를 구성하는 모든 도형의 좌표를 일일히 수정해주는 것은 매우 번거로운 일입니다. 일단 원점 (0, 0)을 기준으로 해서 글자를 그려놓으면, 그 좌표를 전혀 수정할 필요없이 translate()를 이용해서 원하는 곳에 배치시킬 수 있습니다. 아래의 예제를 살펴봅시다.

void setup() {
  size(600, 200);
  noFill();
  stroke(0);
}

void draw() {
  background(255);
  // 가
  beginShape();
  vertex(10, 20);
  vertex(40, 20);
  vertex(40, 50);
  endShape();
  line(50, 10, 50, 60);
  line(50, 30, 60, 30);

  pushMatrix();
  translate(100, 90);
  // 가
  beginShape();
  vertex(10, 20);
  vertex(40, 20);
  vertex(40, 50);
  endShape();
  line(50, 10, 50, 60);
  line(50, 30, 60, 30);
  popMatrix();

  pushMatrix();
  translate(120, 30);
  // 가
  beginShape();
  vertex(10, 20);
  vertex(40, 20);
  vertex(40, 50);
  endShape();
  line(50, 10, 50, 60);
  line(50, 30, 60, 30);
  popMatrix();
}
Your browser does not support canvas.

위의 예제에서는, 각 도형의 좌표는 그대로 유지한 채, 그 도형이 그려진 매트릭스를 이동하는 방식을 사용하고 있습니다. 이후에 다루게 될 함수까지 적용하면 깔끔하게 글자들을 그리고 이동시킬 수 있습니다.

회전 (rotate)

회전의 경우, 기준점이 왼쪽 위의 (0, 0)이라는 것을 기억해야 합니다.

size(600, 100);
background(255);

ellipse(50, 0, 20, 20);

rotate(PI/8);
ellipse(50, 0, 20, 20);

rotate(PI/8);
ellipse(50, 0, 20, 20);
Your browser does not support canvas.

중심을 기준으로 회전

도형의 중심을 기준으로 회전하고 싶은 경우에는, 좌표계의 원점, (0, 0)을 중심으로 도형을 그려줘야 합니다. 화면의 중앙에서부터 회전하고 싶은 경우에는, translate()을 이용하여 좌표계의 원점을 원하는 곳으로 이동시키고, 도형을 그 원점을 중심으로 그려주면 됩니다.

size(600, 100);
background(255);
noFill();

rectMode(CENTER);
translate(width/2, height/2);
rect(0, 0, 60, 60);

rotate(PI/4);
rect(0, 0, 60, 60);
Your browser does not support canvas.

여러 도형의 회전

만약, 여러 도형들을 각각의 중심점을 축으로 해서 회전하고 싶다면 어떡해야 할까요? 우리가 사용하는 After Effects와 같은 애니메이션 소프트웨어에서는 너무나 쉽게 해결할 수 있는 문제이지만, 프로세싱에서는 조금 다른 관점으로 생각해야 합니다. 먼저 아래의 결과물과 코드를 봅시다.

Your browser does not support canvas.
void setup() {
  size(600, 200);
  noFill();
  stroke(0);
}

void draw() {
  background(255);

  pushMatrix();
  translate(200, 100);
  rotate(frameCount/100.0); 
  translate(-70, -70); 
  bezier(37, 66, 16, 98, 53, 144, 72, 133);
  bezier(72, 133, 90, 121, 86, 103, 98, 80);
  bezier(98, 80, 111, 57, 140, 60, 121, 43);
  bezier(121, 43, 102, 27, 81, 22, 53, 22);
  bezier(53, 22, 24, 21, 52, 43, 37, 66);
  popMatrix();

  pushMatrix();
  translate(400, 100);
  rotate(-frameCount/10.0);
  translate(-185, -195);
  bezier(167, 193, 131, 190, 113, 189, 114, 216);
  bezier(114, 216, 114, 243, 160, 270, 185, 257);
  bezier(185, 257, 209, 244, 232, 222, 243, 193);
  bezier(243, 193, 254, 164, 215, 124, 198, 124);
  bezier(198, 124, 180, 125, 125, 109, 123, 128);
  bezier(123, 128, 121, 146, 184, 194, 167, 193);
  popMatrix();
}

본문의 시작 부분에서 말한 것처럼, 변환은 좌표계를 움직이는 것입니다. 그러나 이렇게 생각하는 것은 직관적이지 않아서 이해하기 어려운 경우가 많습니다. 특히, 여러 번 변환을 해야할 경우에 더욱 그렇습니다. 변환을 오브젝트의 관점에서 생각할 때에는 변환이 적용된 순서를 거꾸로 올라가면서 생각하면 이해하기가 쉽습니다. 위의 코드에서 첫번째 도형은 먼저 translate(-70, -70)이 적용된다고 보면 됩니다. 그렇다면 이 숫자 (-70, -70)은 어디서 나온 것일까요? 이 도형은 일러스트레이터에서 그리고 drawscript 익스텐션을 이용해서 코드로 변환해준 것입니다. 일러스트레이터의 info 패널을 사용하여 도형의 중심이 되는 곳을 찾아보니 70, 70픽셀 위치라는 것을 알 수 있었습니다. 따라서 -70, -70만큼 움직여주면 우리는 도형의 중심을 좌표계의 중심에 위치시킬 수 있습니다. 그 이후 rotate(frameCount/100.0)을 적용하면, 이제 좌표계의 중심점으로 옮겨진 도형은 중심축에서부터 회전하게 됩니다. 그리고 나서 옮기고 싶은 위치인 (200, 100)으로 다시 이동해줍니다.

두번째의 도형도 마찬가지의 원리입니다. 일러스트레이터에서 도형의 애초 위치는 오른쪽 아래부분이었고, 중심점은 (185, 195)였습니다. 따라서 그만큼 반대로 이동해주면 좌표계의 원점 (0, 0)으로 도형의 중심을 이동시킬 수 있습니다. 그 후 회전을 한 뒤, 최종적으로 이동하고 싶은 (400, 100)으로 이동해주었습니다.

이런 식으로 여러 번의 변환과정을 거치는 것이 번거롭다면 애초에 도형을 그릴 때, 좌표계의 원점 (0, 0)을 중심으로 그리면 추가적인 이동을 생략할 수 있습니다.

만약 마우스를 따라다니면서 회전하는 도형을 만들고 싶다면 어떡해야할까요?

마우스를 따라다니며 회전하는 도형

void setup() {
  size(600, 200);
}

void draw() {
  background(255);

  // 원래 위치로 다시 이동시키는 대신에
  // 마우스의 현재위치만큼 이동하여 마우스를 따라다니게 한다.
  pushMatrix();
  translate(mouseX, mouseY);
  rotate(frameCount / 100.0);
  translate(-88, -84);

  bezier(60, 48, 71, 64, 9, 56, 17, 80);
  bezier(17, 80, 24, 104, 30, 121, 44, 121);
  bezier(44, 121, 58, 120, 68, 124, 68, 138);
  bezier(68, 138, 68, 152, 124, 156, 124, 136);
  bezier(124, 136, 124, 116, 130, 83, 141, 77);
  bezier(141, 77, 153, 70, 138, 42, 127, 40);
  bezier(127, 40, 116, 37, 95, 24, 92, 19);
  bezier(92, 19, 90, 15, 46, 26, 60, 48);
  popMatrix();
}

Your browser does not support canvas.

비례 (scale)

크기 변화는 비율로 표시합니다. 기본값은 1이며, 두배로 크기를 늘리려면 scale(2), 반으로 줄이려면 scale(0.5)로 표현할 수 있습니다. rotate()과 마찬가지로 왼쪽 위 원점을 기준으로 합니다. 화면 중심에서부터 크기를 변화하려면, 마찬가지로 translate()을 통해서 좌표계를 원하는 곳으로 이동시켜야 합니다.

size(600, 200);
background(255);
noFill();

translate(width/2, height/2);
ellipse(0, 0, 50, 50);

for (int i = 0; i < 12; i++) {
  scale(1.2);
  ellipse(0, 0, 50, 50);
}
Your browser does not support canvas.