LIFE LOG(MochiMochi3D)

MochiMochi3D

3D関係の記事を書きます

Shaderで文字を描く方法

Shader芸をやってると何かと文字を使った表現やりたくなることがあります。私が知っている限りではフォントのテクスチャ、SDF、ドット絵の3つの表現方法があります。

テクスチャを使う

ShadertoyではTexturesの中にFont1というフォント用のテクスチャが存在します。ぱっと見では色んな色が付いていて分かりづらいですが、このテクスチャのRにはフォントそのもの、Gにはxのグラディエント、Bにはyのグラディエント、Aにはフォントの距離関数が割り当てられています。

Font1

サンプルとして以下の作品があるため、これを確認すればわかると思います。

www.shadertoy.com

単に文字を出したいだけならRの値やAの値を使えば簡単に作ることが出来ます(G,Bを使う例が思いつかない)。

SDFで頑張って書く

Twiglなどのテクスチャが使えない環境で文字を出そうとなると先ほどの手段はできません。そうした環境下で文字を描く方法としてはSDFを使う手法があります。

プリミティブなSDFやカーブSDFをminで組み合わせて頑張って作ったり、デジタル時計のようなワークフレームを用意して線のON、OFFみたいな感じで表現する手法があったりします。

ArcとBoxを組み合わせて制作した文字

ドット絵で描く

SDFを頑張って変形させればどんな文字も作ろうと思えば作れますが、やっぱり大変ですし重くなりかねません。シェーダー単体で完結してなおかつ新しい文字とか簡単に追加できる手法となるとドット絵による表現があります。

UVをグリッド上に分割し、各グリッドのON,OFFを制御することでドット絵として表現することが出来ます。各グリッドはON,OFFしかないのでそれぞれ1bitを割り振ってあげれば、文字というのはビット列として保存することが出来ます。

作例として以下のコードを作りました。

www.shadertoy.com

私の方法では1文字あたり8×8のグリッドを用意しており(8×5ぐらいあると文字がいい感じになる)、各グリッドには以下の様にIDを割り振っています。

Girdとbit列

8×8なので1文字あたり64ビットのビット列が必要で2つのint(32bit)変数で保存しています。16進法表記なのでちょっと分かりづらいですが、各文字はこんな感じになっています。(0xを付けてあげると16進法で数値を描くことが出来ます)

ivec2 font_data[37] = ivec2[](
    ivec2(0x00000000,0x00000000), //space
    ivec2(0x7e91897e,0x00000000), //0
    ivec2(0x01ff4121,0x00000000), //1
    ivec2(0x71898543,0x00000000), //2
    ivec2(0x6e919142,0x00000000), //3
    ivec2(0x08ff4838,0x00000000), //4
    ivec2(0x8e9191f2,0x00000000), //5
    ivec2(0x0e91916e,0x00000000), //6
    ivec2(0xc0b08f80,0x00000000), //7
    ivec2(0x6e91916e,0x00000000), //8
    ivec2(0x6e919162,0x00000000), //9
    ivec2(0x7f88887f,0x00000000), //A
    ivec2(0x6e9191ff,0x00000000), //B
    ivec2(0x4281817e,0x00000000), //C
    ivec2(0x7e8181ff,0x00000000), //D
    ivec2(0x919191ff,0x00000000), //E
    ivec2(0x909090ff,0x00000000), //F
    ivec2(0x4685817e,0x00000000), //G
    ivec2(0xff1010ff,0x00000000), //H
    ivec2(0x0081ff81,0x00000000), //I
    ivec2(0x80fe8182,0x00000000), //J
    ivec2(0x413608ff,0x00000000), //K
    ivec2(0x010101ff,0x00000000), //L
    ivec2(0x601060ff,0x000000ff), //M
    ivec2(0x0c1060ff,0x000000ff), //N
    ivec2(0x7e81817e,0x00000000), //O
    ivec2(0x609090ff,0x00000000), //P
    ivec2(0x7f83817e,0x00000001), //Q
    ivec2(0x619698ff,0x00000000), //R
    ivec2(0x4e919162,0x00000000), //S
    ivec2(0x80ff8080,0x00000080), //T
    ivec2(0xfe0101fe,0x00000000), //U
    ivec2(0x0e010ef0,0x000000f0), //V
    ivec2(0x031c03fc,0x000000fc), //W
    ivec2(0x340834c3,0x000000c3), //X
    ivec2(0x300f30c0,0x000000c0), //Y
    ivec2(0xe1918d83,0x00000081) //Z
);

ここから文字を出すまでの流れを見ていきましょう。ここの実装ではどの文字を使うかはfont_dataのindexで指定する形で、引数におけるidがそれになります。また、UVは0~1が来るものと仮定して制作しています。

vec3 font(vec2 uv,int id){
    vec2 uv1 = uv;
    uv = uv * 8.0;
    ivec2 texel = ivec2(uv);
    int bit_offset = texel.x * FontWidth + texel.y;

    int s,t;
    s = font_data[id].x;
    t = font_data[id].y;

    int tex = 0;
    
    if(bit_offset <= 31){
        s = s >> bit_offset;
        s = s & 0x00000001;
        tex = s;
    }
    else{
        t = t >> (bit_offset - 32);
        t = t & 0x00000001;
        tex = t;
    }

    tex = (abs(uv1.x - 0.5) < 0.5 && abs(uv1.y - 0.5) < 0.5) ? tex : 0;
    return vec3(tex); 
}

最初の部分ではUVから8×8のグリッドを作り、今いる場所がグリッドにおいてどの位置にいるかを求めています。bit_offsetが先ほど見せた画像におけるグリッドのIDです。後々の意味が分かりやすいように何番目のbitを見るのかという意味のbit_offsetという名前にしています。

    vec2 uv1 = uv;
    uv = uv * 8.0;
    ivec2 texel = ivec2(uv);
    int bit_offset = texel.x * FontWidth + texel.y;

対応する文字のビット列をここで受け取ります。下位32bitはsに、上位32bitはtに格納しています。

    int s,t;
    s = font_data[id].x;
    t = font_data[id].y;

現在いるグリッドに対応するbitの場所はbit_offsetに入っているのでbit_offset番目のbitだけを抜き出します。ここではビット列を上位、下位32bitで分けてるのでif分でどちらに属しているか判定しています。その後はシフト演算とアンド演算によって対応するビットを抜き出して、その結果をtexという変数に代入し白黒の色として最終的に返しています。(下から2番目のやつは単にUVが0~1の範囲外は消すという処理なので気にしなくて良いです)

    int tex = 0;
    
    if(bit_offset <= 31){
        s = s >> bit_offset;
        s = s & 0x00000001;
        tex = s;
    }
    else{
        t = t >> (bit_offset - 32);
        t = t & 0x00000001;
        tex = t;
    }

    tex = (abs(uv1.x - 0.5) < 0.5 && abs(uv1.y - 0.5) < 0.5) ? tex : 0;
    return vec3(tex); 

こんな感じに簡単に文字をshaderで書くことが出来ます。好きな絵文字とかもドット絵なら書けちゃいますので是非やってみてください

コミケC101でBSDFの本を出しました

3か月ほど前の話なのですが、コミックマーケットC101にサークルとして参加しBSDFの本というニッチな本を配布してきました。(電子書籍版がありますので興味がある方はぜひ)

motimoti3d.booth.pm

即売会もよく知らない人間だったのでC101の参加はとても刺激的な体験になりましたが、配布までの道のりには色々苦労がありました。なんせ製本なんて一回も考えたことすらないのでどうやって技術書なんか書くのかどうやって印刷するのかなど全くわからないままにのらりくらりやっていきました。備忘録もかねてどんな感じに今回の本を制作、配布まで行ったのかをまとめておこうと思います(あと配布した本の裏話)。

続きを読む

PathTracingでのCausticsレンダリング手法

初めに

この記事はレイトレ合宿8アドベントカレンダー7/17の2個目の記事です。

パストレーシングにおいて効率的にCausticsをレンダリングすることができるManifold Next Event Estimation(MNEE)という手法とそれの発展手法である Specular Manifold Sampling(SMS)を先行手法のManifold Exploration(Manifold Walk)と共に大まかに解説する記事となっています。

続きを読む

レイトレーシングにおけるIBL(Image Based Lighting)の実装

初めに 

この記事はレイトレ合宿8アドベントカレンダー7/17の1個目の記事として書かれたものです。 この記事ではレイトレーシングにおけるImage Based Lightingの扱い、純パストレにおける実装、重点的サンプリングの手法及び実装について解説していきます。

続きを読む

Blenderでのanisotropic GGXのパラメーターを再現する

Blenderにおける異方性のパラメーター

 anisotropicGGXでは\alpha_x,\alpha_yという粗さのパラメータが必要ですが実用上ではそれぞれを直接指定することは多分ありません。実際に、BlenderのPrincipled BSDFではRoughnessという粗さのパラメータが1つだけで済まされており、別途で異方性を作るためのパラメーターとして「Anisotropic」と「Anisotropic Rotate」の2つが用意されています。

続きを読む