Shaderで文字を描く方法
Shader芸をやってると何かと文字を使った表現やりたくなることがあります。私が知っている限りではフォントのテクスチャ、SDF、ドット絵の3つの表現方法があります。
テクスチャを使う
ShadertoyではTexturesの中にFont1というフォント用のテクスチャが存在します。ぱっと見では色んな色が付いていて分かりづらいですが、このテクスチャのRにはフォントそのもの、Gにはxのグラディエント、Bにはyのグラディエント、Aにはフォントの距離関数が割り当てられています。
サンプルとして以下の作品があるため、これを確認すればわかると思います。
単に文字を出したいだけならRの値やAの値を使えば簡単に作ることが出来ます(G,Bを使う例が思いつかない)。
SDFで頑張って書く
Twiglなどのテクスチャが使えない環境で文字を出そうとなると先ほどの手段はできません。そうした環境下で文字を描く方法としてはSDFを使う手法があります。
プリミティブなSDFやカーブSDFをminで組み合わせて頑張って作ったり、デジタル時計のようなワークフレームを用意して線のON、OFFみたいな感じで表現する手法があったりします。
ドット絵で描く
SDFを頑張って変形させればどんな文字も作ろうと思えば作れますが、やっぱり大変ですし重くなりかねません。シェーダー単体で完結してなおかつ新しい文字とか簡単に追加できる手法となるとドット絵による表現があります。
UVをグリッド上に分割し、各グリッドのON,OFFを制御することでドット絵として表現することが出来ます。各グリッドはON,OFFしかないのでそれぞれ1bitを割り振ってあげれば、文字というのはビット列として保存することが出来ます。
作例として以下のコードを作りました。
私の方法では1文字あたり8×8のグリッドを用意しており(8×5ぐらいあると文字がいい感じになる)、各グリッドには以下の様にIDを割り振っています。
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の本を出しました
レイトレ合宿8に参加しました
レイトレ合宿8に参加してきました!参加レポートとしてレンダラー開発記と合宿の様子や感想を記事にまとめました!
続きを読む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の扱い、純パストレにおける実装、重点的サンプリングの手法及び実装について解説していきます。
続きを読む