LIFE LOG(MochiMochi3D)

MochiMochi3D

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

同次座標系の同次(Homogeneous)って何?

3DCGでよく使われている同次座標系の同次の意味について気になったので、調べてなんとなくまとめた記事です。著者は数学をちゃんと学んでいない人間なのであまり言葉遣いが正確ではないかもしれないのでご留意ください。

続きを読む

Multiple Scatteringの実装と近年の動向

初めに

レイトレ(Raytracing)のカレンダー | Advent Calendar 2023 - Qiitaの23日の記事です。通常のMicrofacet BSDFは何度も反射する多重散乱光(Multiple Scatteirng)を無視しているため、実はエネルギー損失が起きていることが知られています。この記事はこうしたMultiple Scatteringを考慮した[Heitz et al. 2016]の手法と最近発表されたFast Multiple Scattering Approximationという手法の実装について解説していきます。また、最近のMultiple Scatteringの動向について簡単に話します。

続きを読む

4K Executable Graphics (exegfx)入門

続きを読む

レイトレ合宿9 参加レポート

9月1日から9月3日にかけて開催されたレイトレ合宿9に参加してきました。去年は何ともラグジュアリーな合宿だった反動で、今年は千葉県の大本山 清澄寺というお寺で慎ましやかに開催されました。

続きを読む

光は本当に光線(Ray)として使っていいのか ~幾何光学近似~

レイトレ合宿9アドベントカレンダー6/25の記事です。

レンダリングにおける光の挙動は全て光線(Ray)として考えているが、現実の光はこれと相反する電磁波という形で伝播してしています。なぜ光をRayと扱ってもいいのかというと、光学における幾何光学近似という近似によって「レンダリングのスケールでは光をRayとして扱ってよい」という保証が存在するためです。当記事はレイトレの物理学的根拠である幾何光学近似について幾何光学と波動光学の原理から証明と解説を行います。

続きを読む

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で書くことが出来ます。好きな絵文字とかもドット絵なら書けちゃいますので是非やってみてください