Common Lisp에서 GPU 벡터 기반 글꼴 렌더링

17364 단어 lispcommon-lispOpenGL
이전에 블로그에 웹 브라우저를 만들려고 했음을 썼습니다.
그때 조금만 쓴 문자열 드로잉 라이브러리의 소개입니다.

배경



브라우저의 렌더링 엔진을 만드는데 있어서, 취급하기 쉬운 묘화 백엔드가 필요했다.
Gecko는 Cairo를 사용하는 것처럼 보였습니다 (과거의 이야기?) 그래서 cl-cffi-gtk을 시도했습니다.
그러나 문자열의 그리기를 세세하게 제어하려면 Pango도 이용해야 했다.
OpenGL의 프리미티브 정도 부담없이 취급할 수 있는 것을 갖고 싶다.

다만 이런 것 를 발견했다.
WebGL과 셰이더로 베지어 곡선을 그리는 응용으로 TTF 폰트를 렌더링한다는 것이다.
이번에는 이것을 기반으로 문자열 그리기 라이브러리로 Common Lisp에 이식했다.

GLisph



Glyph rendering engine using OpenGL shading language for Common Lisp.



GLisph는 OpenGL 셰이더를 사용하여 Common Lisp를위한 글리프 렌더링 엔진입니다.
고정 기능 파이프라인을 사용하고 있지 않기 때문에, 렌더링 처리가 문맥에 의존하지 않습니다.

폰트의 벡터 데이터를 GPU에 전송하고 있기 때문에, 스케일링 등에 의한 오버헤드가 발생하지 않게 되어 있습니다.
커브를 그리는 데는 2차 베지어 커브를 사용하기 때문에 TTF 폰트 이외의 그리기에는 대응하지 않습니다.

이 라이브러리는 ZPB-TTF에 의존합니다.

아래는 GIF 동영상에 표시되는 예제 구현을 보여줍니다.
(require :cl-glut)
(require :glisph)

;; ウィンドウサイズ
(defvar *width* 800)
(defvar *height* 600)

;; フォントとグリフテーブル(キャッシュ)
(defvar *font*)
(defvar *glyph-table*)

(defvar *origin-x* 0.0)
(defvar *origin-y* 0.0)
(defvar *display-x* 0.0)
(defvar *display-y* 0.0)
(defvar *zoom* 1.0)
(defvar *frame-count* 0)

;; ウィンドウクラスの定義
;; ステンシルバッファとマルチサンプリングを利用する(必須)
(defclass test-window (glut:window)
  ()
  (:default-initargs :title "GLisphTest"
                     :width *width* :height *height*
                     :mode '(:stencil :multisample)
                     :tick-interval (round (/ 1000 60))))

(defmethod glut:mouse ((w test-window) button state x y)
  (declare (ignore w state))
  (case button
    (:left-button
      (setf *origin-x* (- x *display-x*)
            *origin-y* (- y *display-y*)))
    (:wheel-down
      (setf *zoom* (/ *zoom* 1.2))
      (glut:post-redisplay))
    (:wheel-up
      (setf *zoom* (* *zoom* 1.2))
      (glut:post-redisplay))))

(defmethod glut:motion ((w test-window) x y)
  (setf *display-x* (- x *origin-x*)
        *display-y* (- y *origin-y*))
  (glut:post-redisplay))

(defmethod glut:reshape ((w test-window) width height)
  (gl:viewport *display-x* (- *display-y*) width height)
  (gl:matrix-mode :projection)
  (gl:load-identity)
  (gl:ortho 0 width height 0 -1 1)
  (gl:matrix-mode :modelview)
  (gl:load-identity)
  (setf *width* width
        *height* height))

;; ウィンドウ表示前に初期化処理を行う
;; フォントを読み込んで、グリフテーブルに文字を登録している
(defmethod glut:display-window :before ((w test-window))
  (setf *font*
    (gli:open-font-loader "/usr/share/fonts/OTF/TakaoGothic.ttf"))
  (setf *glyph-table* (gli:make-glyph-table *font*))
  (loop for ch across "Hello World!
  The quick brown fox jumps over the lazy dog.色は匂へと 散りぬるを"
        do (gli:regist-glyph *glyph-table* ch))
  (gli:init))

(defmethod glut:tick ((w test-window))
  (incf *frame-count*)
  (when (>= *frame-count* 360)
    (setf *frame-count* 0))
  (glut:post-redisplay))

;; 描画処理
(defmethod glut:display ((w test-window))
  (gl:viewport *display-x* (- *display-y*) *width* *height*)
  (gl:clear-color 0 0 0 1)
  (gl:clear-stencil 0)
  (gl:clear :color-buffer-bit :stencil-buffer-bit)
  (gl:color 0.5 0.0 0.0 1.0)
  (gl:with-primitive :quads
    (gl:vertex 0 0)
    (gl:vertex 300 0)
    (gl:vertex 300 300)
    (gl:vertex 0 300))
  ;; グリフの拡大縮小行列を設定
  (gli:gscale (float (* *zoom* (/ 2 *width*)))
              (float (* *zoom* (/ -2 *height*)))
              1.0)
  (let* ((rad (* (coerce pi 'single-float) (/ *frame-count* 180)))
        (sinr (sin rad))
        (cosr (cos rad)))
    ;; グリフの回転をゼロに設定
    (gli:grotate 0.0 0.0 0.0)
    ;; 文字列の描画
    (gli:draw-string *glyph-table* "The quick brown fox jumps over the lazy dog."
      -350.0 0.0 0.0 30.0
      :color '(1 1 1 1))
    (gli:grotate 0.0 0.0 rad)
    (gli:draw-string *glyph-table* "Hello World!"
      -300.0 -150.0 0.0 (+ 45.0 (* 30.0 cosr))
      :color '(1 1 0 1))
    (gli:draw-string *glyph-table* "色は匂へと 散りぬるを"
      -300.0 200.0 0.0 40.0
      :color `(0 1 1 1)
      :spacing sinr))
  (gl:flush))

;; 終了時にfinalizeを実行する
(defmethod glut:close ((w test-window))
  (gli:delete-glyph-table *glyph-table*)
  (gli:finalize)
  (format t "close~%"))

(glut:display-window (make-instance 'test-window))

이 예에서는 draw-string 라는 당의 구문을 이용하고 있습니다만, 이것은 render-glyph 라는 프리미티브 드로잉에 상당하는 함수를 내부적으로 호출하고 있습니다. 이 함수도 내보내므로 필요에 따라 구분할 수 있습니다.

도전


  • 셰이더를 지원하는 GLSL 버전에 맞게 자동 생성

  • cl-annot을 사용하고 있지만 잘 사용할 수없는 것 같습니다.
  • 일부 글꼴 (원 진짜 고딕 등)의 일부 문자를 잘 그릴 수 없습니다
  • cl-glut 이외의 동작 확인
  • 픽셀 기반 렌더링 (freetype)과의 비교
  • 좋은 웹페이지 즐겨찾기