← 과제 목록

[과제] 2주차) Basic Programming 강주희

@zurdhva
  • #과제
목차

Basic Programming 파트에서 배운 기능들을 한 장면 안에서 자연스럽게 써 보고 싶었다. 여러 소재를 고민하다가 평소 좋아하는 오리를 그리기로 결심했다. 오리는 단순한 형태처럼 보이지만 몸통, 목, 부리, 물결, 반사상까지 넣으면 Shape / Path / Fill / Stroke / Scene & Transformation을 모두 활용하기 좋다고 생각했다.

그래서 물 위에 떠 있는 오리를 그리는 Duck 예제를 추가했다.튜토리얼 Basic Programming 파트의 Shape / Path / Fill / Stroke / Scene & Transformation을 한 장면 안에서 모두 사용하도록 구성했다.

[1] Example 구동 화면

[2] 과제 코드

https://github.com/zurdhva/thorvg.example/commit/1f31f5ef283c4b52bcca0365d576282721e9cd16

[3] 예제 설명/의도

1. stroke를 테두리가 아니라 지오메트리로 사용했다.

처음에는 오리의 목을 닫힌 path로 그리려고 했다. 그런데 목과 몸통이 이어지는 어깨 부분, 머리 아래 턱 곡선까지 자연스럽게 만들려면 제어점을 여러 개 조정해야 했다. 작은 형태인데도 곡선이 조금만 어긋나면 목이 딱딱해 보이거나 몸통과 따로 떠 보였다.

그래서 방향을 바꿔 곡선 하나를 굵은 stroke로 그렸다.

neck->moveTo(40, -80);
neck->cubicTo(46, -140, 64, -172, 94, -186);
neck->strokeFill(250, 208, 82);
neck->strokeWidth(76);
neck->strokeCap(tvg::StrokeCap::Round);

StrokeCap::Round를 사용하니 stroke 양 끝이 둥글게 마감되었다. 덕분에 어깨와 턱 형태를 별도로 만들지 않아도 자연스러운 목 실루엣이 나왔다.

이 과정에서 ThorVG의 fill과 stroke가 서로 독립적으로 동작한다는 점도 확인했다. fill을 지정하지 않고 stroke만 지정하면 도형 내부는 채워지지 않고 선만 렌더링되었다. 이 성질은 이후 물결 링을 그릴 때도 그대로 활용했다.

2. 반사상은 transform(Matrix)가 아니면 표현하기 어려웠다.

수면 아래에 오리 반사상을 만들기 위해 y축으로 뒤집힌 장면이 필요했다. 처음에는 translate(), rotate(), scale() 같은 편의 메서드 조합으로 처리하려고 했다.

하지만 현재 예제에서 사용하는 scale()은 등방 배율 방식이라 y축만 음수로 뒤집는 표현을 만들기 어려웠다. 단순히 크기를 줄이거나 키우는 것은 가능했지만, 수면을 기준으로 세로 방향만 반전시키는 반사 효과는 나오지 않았다.

결국 행렬을 직접 넣는 방식으로 해결했다.

tvg::Matrix m = {1, 0, DUCK_X, 0, -1, WATER_Y + 16 - bob, 0, 0, 1};
mirror->transform(m);
mirror->opacity(64);

이 부분을 구현하면서 튜토리얼이 편의 메서드들을 소개한 뒤에도 transform()을 따로 다루는 이유를 이해했다. 단순한 이동, 회전, 확대/축소는 편의 메서드로 충분하지만, 축 반전처럼 조금 더 직접적인 변환이 필요한 경우에는 Matrix를 써야 했다.

3. 물에 잠긴 표현은 Composition 대신 레이어 순서로 해결했다.

처음에는 오리 아랫부분을 수면 아래로 잘라내기 위해 마스킹이나 composition을 떠올렸다. 하지만 튜토리얼에서 composition은 오프스크린 버퍼를 추가로 사용할 수 있다는 설명이 있었고, 이 예제에서는 꼭 필요한 기능인지 다시 생각해 보았다.

결과적으로 클리핑을 쓰지 않고 레이어 순서만 조정했다. 오리를 먼저 그린 뒤, 반투명한 물 밴드를 그 위에 덮었다. 그러면 오리의 배 부분이 물속에 잠긴 것처럼 보였다.

렌더링 순서는 다음과 같이 구성했다.

하늘 → 태양 → 반사상 → 오리 → 물(반투명) → 물결 → 윤슬

이 방식은 실제로 도형을 자르는 것은 아니지만, 시각적으로는 충분히 물에 잠긴 느낌을 만들 수 있었다. “디자인 접근을 바꾸면 composition을 피할 수 있다”는 문서의 힌트가 이런 상황에 해당한다고 느꼈다.

4. gradient의 마지막 stop 알파를 0으로 설정했다.

태양 주변의 glow 효과를 처음에는 별도의 blur처럼 생각했다. 하지만 Basic Programming 예제 안에서 너무 복잡한 효과를 추가하기보다는, gradient만으로 해결하는 쪽이 더 적절하다고 판단했다.

그래서 원형 gradient의 마지막 stop 알파를 0으로 두었다.

sunStops[2] = {1.0f, 255, 226, 140, 0};   // offset, r, g, b, a

중앙은 밝게 채우고 바깥쪽으로 갈수록 투명해지게 하니, 별도 필터 없이도 태양 주변이 은은하게 퍼지는 효과를 만들 수 있었다.

5. 물결은 dash와 Round cap 조합으로 만들었다.

물결은 닫힌 타원을 stroke로만 그리고, strokeDash()를 적용해 끊어진 링처럼 보이게 했다. 처음에는 단순한 실선 타원으로 그렸는데, 물 위의 파문이라기보다는 도형 테두리처럼 보였다.

그래서 dash를 적용하고, 각 dash 조각의 끝을 StrokeCap::Round로 둥글게 처리했다. 이렇게 하니 짧은 물결 조각들이 이어진 것처럼 보여서 훨씬 자연스러웠다.

또한 동심 타원 3개를 만들고, 각각의 크기와 알파를 조금씩 다르게 두었다. 프레임마다 위상을 어긋나게 확장시키면서 알파를 줄이니 오리가 흔들릴 때 주변 수면도 같이 반응하는 느낌이 났다.

댓글

Discussion 원문