前回までの日記で、OpenGLでテクスチャ(2D画像)を平面に描画する方法と、ベジエ曲面を描画する方法について記述した。
この2つを組み合わせて、テクスチャの変形を行う。
テクスチャの変形には、自由形状変形(FFD: Free-Form Deformation)という手法を使う。
FFDは、ベジエ曲面を用いて座標系を変形する。
FFDによる座標系(u, v)の変形は、制御点をとすると、以下の式で表される。
これは、前回の日記で説明したベジエ曲面の式と同じである。
FFDを使用してテクスチャを変形するには、テクスチャを格子状の頂点で構成される面に貼り付けた後、頂点をFFDで変形する。
なお、テクスチャを貼り付ける面は格子状でなくてもよい。この記事では簡単なため格子状とした。
Live2Dの曲面デフォーマーは、このFFDを使用していると思われる。
これによりLive2Dと同じような変形が一部実現できる。
Pythonで記述すると以下のようになる。
import sys from OpenGL.GL import * from OpenGL.GLU import * from OpenGL.GLUT import * from PIL import Image import numpy as np import scipy.misc as scm def bernstein(n, i, t): return scm.comb(n, i) * t**i * (1 - t)**(n-i) def bezier_patches(m, n, u, v, q): return np.dot([bernstein(n, j, v) for j in range(n + 1)], np.tensordot([bernstein(m, i, u) for i in range(m + 1)], q, axes=1)) def load_texture(): img = Image.open("sample2.png") w, h = img.size glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.tobytes()) def display(): #P = np.array([ # [[ 0. , 0. ], [ 0. , 1/3], [ 0. , 2/3], [ 0. , 1. ]], # [[ 1/3, 0. ], [ 1/3, 1/3], [ 1/3, 2/3], [ 1/3, 1. ]], # [[ 2/3, 0. ], [ 2/3, 1/3], [ 2/3, 2/3], [ 2/3, 1. ]], # [[ 1. , 0. ], [ 1. , 1/3], [ 1. , 2/3], [ 1. , 1. ]], # ]) P = np.array([ [[ 0. , 0. ], [ 0. , 0.3], [ 0. , 0.6], [ 0. , 1. ]], [[ 0.3, 0.1], [ 0.3, 0.4], [ 0.3, 0.8], [ 0.3, 1.1]], [[ 0.6, 0.1], [ 0.7, 0.4], [ 0.7, 0.7], [ 0.6, 1. ]], [[ 1. , 0. ], [ 1.1, 0.3], [ 1.1, 0.6], [ 1. , 1. ]], ]) X = np.zeros((11 * 11, 2)) T = np.zeros((11 * 11, 2)) # texture for i in range(11): for j in range(11): u = i / 10.0 v = j / 10.0 X[i * 11 + j] = bezier_patches(3, 3, u, v, P) # FFD T[i * 11 + j] = [i / 10.0, 1.0 - j / 10.0] V = np.zeros((11 * 11, 3)) for i in range(11): for j in range(11): V[i * 11 + j] = [X[i * 11 + j, 0] - 0.5, X[i * 11 + j, 1] - 0.5, 0] IDX = np.zeros((10, 11 * 2)) for i in range(10): for j in range(11): IDX[i, j * 2] = i * 11 + j IDX[i, j * 2 + 1] = (i + 1) * 11 + j glClear(GL_COLOR_BUFFER_BIT) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glEnable(GL_BLEND) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) glEnable(GL_TEXTURE_2D) glVertexPointerf(V) glTexCoordPointerf(T) for idx in IDX: glDrawElementsui(GL_TRIANGLE_STRIP, idx) glDisable(GL_TEXTURE_2D) glDisable(GL_BLEND) glColor3f(1.0, 1.0, 1.0) glPointSize(2.0) glBegin(GL_POINTS) for p in P.reshape((-1, 2)): glVertex2f(p[0] - 0.5, p[1] - 0.5) glEnd() glFlush() def main(): glutInit(sys.argv) glutInitDisplayMode(GLUT_RGBA) glutInitWindowSize(300, 300) glutCreateWindow(b"FFDSample1") glutDisplayFunc(display) glClearColor(0.0, 0.0, 1.0, 1.0) load_texture() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glutMainLoop() if __name__ == '__main__': main()
1.0×1.0の大きさの面を10分割してテクスチャを貼り付けている。
4×4の制御点を等間隔の格子から少しずらしている。
その制御点を使用して、FFDで、テクスチャを貼り付けた頂点を変形している。
テクスチャを貼り付けた面の中心が(0, 0)になるように(-0.5, -0.5)だけ移動して表示している。
実行結果
- 変形前
- 変形後
白い点は制御点を示している。
参考文献
- Thomas W. Sederberg, Scott R. Parry, Free-form deformation of solid geometric models
- コンピュータグラフィックス (新世代工学シリーズ), 前川 佳徳 (著), オーム社