読者です 読者をやめる 読者になる 読者になる

ベクタードリームライン

これはBlender Advent Calendar 2015 16日の記事です。 Cyclesの何かとしか言っていないからOSLの話でもセーフですよね!Cyclesでしか使えないんだし!

まあこう、まったりご覧ください。m(_ _)m

さて、テクスチャの解像度に悩まされることはよくあることと思います。もっと大きく描いておくんだった、みたいな。 しかし解像度に依存しないテクスチャというものも存在します。 例えばBlenderではノイズなどのプロシージャルなテクスチャは何もしなくてもすぐ使えます。 しかもCyclesはノードでマテリアルを作れるので自由度が高いです。 またお馴染みのOpen Shading Language(OSL)を使えばもっと自由度が高くなります。

一方でプロシージャルなテクスチャではパターンのようなものは作れても自由にお絵描きできないじゃないかという不満もあります。 かつてBlenderではSVGをテクスチャとして使う方法もあったのですが、いまでは失われています。

はい、勘のいい人はもう気付いたと思いますが、今のBlenderにはOSLがあります!

というわけで前置きが長かったですがOSLでお絵描きをしようという試みです。

とりあえずこんな素材を用意してみました。

f:id:c5h12:20151216144609j:plain

時間の都合で手動で座標を拾うのであまり複雑ではないものです。 ハートがベジェパス、残りは普通のポリゴンです。

まず左下の星です。 こういう形はわりとどうにかなります。 エッジの外側内側で距離が正と負になるように計算してやれば線も塗りつぶしも一発です。 ここだけ抜き出してどうするんだという気もしますが星を描いている部分。

void starShape(point p, RGBA outrgba) {
    point position = point(2.27121, 2.93069, 0.0);
    float rotation = 0.253427532;
    float majorR = 0.682539;
    float minorR = 0.34352;
    
    float sn = sin(rotation);
    float cs = cos(rotation);
    point lp = coord - position;
    lp = point(lp[0] * cs - lp[1] * sn, lp[0] * sn + lp[1] * cs, 0.0);

    float PENTA_ANGLE = M_PI / 2.5;

    float r = length(lp);
    float a = atan2(lp[0], lp[1]);
    a = mod(a, PENTA_ANGLE);
    a = min(a, PENTA_ANGLE - a);
    lp = point(sin(a), cos(a), 0.0) * r;
    
    point p0 = point(0.0, majorR, 0.0);
    point p1 = point(cos(PENTA_ANGLE * 0.5), sin(PENTA_ANGLE * 0.5), 0.0) * minorR;
    
    vector tv01 = normalize(p1 - p0);
    vector v01 = vector(-tv01[1], tv01[0], 0.0);
    vector v0p = lp - p0;
    float t = dot(v01, v0p);
    
    RGBA strkrgba;
    RGBA fillrgba;
    makePreMulRGBA(strokeColor, (abs(t) < strokeW)? 1.0 : 0.0, strkrgba);
    makePreMulRGBA(fillColor, (t < 0.0)? 1.0 : 0.0, fillrgba);
    mixPreMulRGBA(strkrgba, fillrgba, outrgba);
}

次に稲妻の部分です。 このくらいの形なら三角などを組み合わせれば星と同様に求めることもできます。 しかし今回は直線を組み合わせた図形、いわゆるポリラインとしました。 まず求めたいUVの座標から線分との距離を求めて最小のものをとります。 これだけだと図形の内側か外側かわからないので、あとで内側を決めるという二段構成になっています。 判定は今の位置から+xの方へ直線を伸ばして図形を構成する線分との交点の数を数えて奇数個だったら内側とする、いわゆるeven-oddルールにしています。

void plasma1Shape(point p, RGBA outrgba) {
    // lower part
    point p0 = point(2.800398, 3.308511, 0.0);
    point p1 = point(3.928315, 4.335365, 0.0);
    point p2 = point(4.048409, 4.119161, 0.0);
    
    float d = distanceFromLine(p0, p1, p);
    d = min(d, distanceFromLine(p1, p2, p));
    d = min(d, distanceFromLine(p2, p0, p));
    
    float s = 0.0;
    s += scanLine(p0, p1, p);
    s += scanLine(p1, p2, p);
    s += scanLine(p2, p0, p);
    
    RGBA strkrgba;
    RGBA fillrgba;
    makePreMulRGBA(strokeColor, (abs(d) < strokeW)? 1.0 : 0.0, strkrgba);
    makePreMulRGBA(fillColor, (mod(s, 2.0) > 0.0)? 1.0 : 0.0, fillrgba);
    mixPreMulRGBA(strkrgba, fillrgba, outrgba);
}

void plasma2Shape(point p, RGBA outrgba) {
    // upper part
    point p0 = point(5.70311, 5.95113, 0.0);
    point p1 = point(7.52196, 7.521223, 0.0);
    point p2 = point(7.52949, 6.553924, 0.0);
    point p3 = point(9.49229, 7.449265, 0.0);
    point p4 = point(7.097784, 5.330662, 0.0);
    point p5 = point(7.086442, 6.101661, 0.0);
    point p6 = point(6.069538, 5.431992, 0.0);
    
    float d = distanceFromLine(p0, p1, p);
    d = min(d, distanceFromLine(p1, p2, p));
    d = min(d, distanceFromLine(p2, p3, p));
    d = min(d, distanceFromLine(p3, p4, p));
    d = min(d, distanceFromLine(p4, p5, p));
    d = min(d, distanceFromLine(p5, p6, p));
    d = min(d, distanceFromLine(p6, p0, p));
    
    float s = 0.0;
    s += scanLine(p0, p1, p);
    s += scanLine(p1, p2, p);
    s += scanLine(p2, p3, p);
    s += scanLine(p3, p4, p);
    s += scanLine(p4, p5, p);
    s += scanLine(p5, p6, p);
    s += scanLine(p6, p0, p);
    
    RGBA strkrgba;
    RGBA fillrgba;
    makePreMulRGBA(strokeColor, (abs(d) < strokeW)? 1.0 : 0.0, strkrgba);
    makePreMulRGBA(fillColor, (mod(s, 2.0) > 0.0)? 1.0 : 0.0, fillrgba);
    mixPreMulRGBA(strkrgba, fillrgba, outrgba);
}

そしてベジェ曲線のハートです。やっぱり形の自由度は高いですね。 これも一番近い距離と内外判定の二段構成です。 距離は確かstack overflowあたりでみたこれを元にしつつ複雑なパスじゃなければ十分だろうとニュートン法だけにした手抜き実装。 内外判定は時間の関係で分割して直線として判定するカッコ悪い代物。

void heartShape(point p, RGBA outrgba) {
    point p00 = point(3.840327, 3.201623, 0.0);
    point p01 = point(4.287171, 4.800179, 0.0);
    point p02 = point(2.895957, 4.861297, 0.0);
    point p10 = point(3.310679, 6.025796, 0.0);
    point p11 = point(3.541543, 6.674042, 0.0);
    point p12 = point(4.737912, 6.794761, 0.0);
    point p20 = point(5.025951, 5.891862, 0.0);
    point p21 = point(5.247099, 6.641434, 0.0);
    point p22 = point(6.523427, 6.857496, 0.0);
    point p30 = point(6.787172, 5.868671, 0.0);
    point p31 = point(7.13217, 4.575211, 0.0);
    point p32 = point(4.928161, 3.561558, 0.0);
    
    float d = distanceFromCubicBezier(p00, p01, p02, p10, p);
    d = min(d, distanceFromCubicBezier(p10, p11, p12, p20, p));
    d = min(d, distanceFromCubicBezier(p20, p21, p22, p30, p));
    d = min(d, distanceFromCubicBezier(p30, p31, p32, p00, p));
    
    float s = 0.0;
    s += scanCubicBezier(p00, p01, p02, p10, p);
    s += scanCubicBezier(p10, p11, p12, p20, p);
    s += scanCubicBezier(p20, p21, p22, p30, p);
    s += scanCubicBezier(p30, p31, p32, p00, p);
    
    RGBA strkrgba;
    RGBA fillrgba;
    makePreMulRGBA(strokeColor, (abs(d) < strokeW)? 1.0 : 0.0, strkrgba);
    makePreMulRGBA(fillColor, (mod(s, 2.0) > 0.0)? 1.0 : 0.0, fillrgba);
    mixPreMulRGBA(strkrgba, fillrgba, outrgba);
}

最後に色はブレンドが楽なので乗算済みアルファの形で求めて最後に戻す方法で。

RGBA rgba, tmprgba;
    
makePreMulRGBA(color(0.0, 0.0, 0.0), 0.0, rgba);

starShape(coord, tmprgba);
mixPreMulRGBA(tmprgba, rgba, rgba);

plasma1Shape(coord, tmprgba);
mixPreMulRGBA(tmprgba, rgba, rgba);

heartShape(coord, tmprgba);
mixPreMulRGBA(tmprgba, rgba, rgba);

plasma2Shape(coord, tmprgba);
mixPreMulRGBA(tmprgba, rgba, rgba);

outColor = (rgba.a > 0.0)? (rgba.rgb / rgba.a) : color(0.0);
outAlpha = rgba.a;

これをUV展開したポリゴンに貼り付けます。 パスのデータを適当につくったためサイズが10x10くらいになっていたのでUV座標は10倍して使うことにしました。

f:id:c5h12:20151217032345j:plain

というわけでできました。

f:id:c5h12:20151216144716j:plain

f:id:c5h12:20151216144802j:plain

寄っても縁がパキッとしてるのはいいですね!

このOSLの全貌はブログに書くには多いのでこちらに。

また、Blenderのシーンファイルはこちら。

もっと複雑なものは1ファイルにするのは無理があると思うので工夫が必要ですが、SVGをこんな感じで変換するスクリプトを誰かが書けばいいと思うよ!(他力本願) 丸以外の線のつなぎとかも考えないといけませんが。

明日はMiSaTo kashitani さんです。

それではHave A Nice Blendering!

(2015/12/17 シーンの設定の画像、シーンファイルへのリンクを追加)