takumifukasawa’s blog

WebGL, Shader, Unity, UE4

0~1のfloatを32bitRGBAに格納する

デモはこちらにおきました。

デモ https://takumifukasawa.github.io/float-to-rgba-tester/

リポジトリ GitHub - takumifukasawa/float-to-rgba-tester: float to rgba tester


こちらが計算部分のjavascriptのソースになります。

class FloatPacker {
    static packToRGBA(num) {
        const rawR = num * 255;
        const r = Math.floor(rawR);
        const rawG = (rawR - r) * 255;
        const g = Math.floor(rawG);
        const rawB = (rawG - g) * 255;
        const b = Math.floor(rawB);
        const rawA = (rawB - b) * 255;
        const a = Math.floor(rawA);
        return { r, g, b, a };
    }
    static unpackToFloat({ r, g, b, a }) {
        return r / 255 + g / (255 * 255) + b / (255 * 255 * 255) + a / (255 * 255 * 255 * 255);
    }
}

// usage example
const { r, g, b, a } = FloatPackage.packToRGBA(0.87185264);
const unpackedFloat = FloatPackage.unpackToFloat({ r, g, b, a });

概要

jpgやpngなど、普段使うことの多いテクスチャの形式ではRGBAの各チャンネルの表現できる値は8bitなので0~255までの256段階になります。

そのため、VATなど頂点シェーダーであらかじめ用意されたデータをテクスチャから読み込んで取り扱うときは、0~255の256段階でしか使うことができません。

しかし、頂点シェーダーでは0.5145や5.4514など、floatな値を使いたい場面があり、256段階では精度が足りないことがほとんどです。

浮動小数点テクスチャを扱うことができればその限りではないのですが、jpgやpngなどの 8bit x 4 = 32bit/pixel なテクスチャの場合は、浮動小数点的をそのままチャンネルに詰めることができません。チャンネルごとに32bitであればもちろんそのまま浮動小数点を入れることができるのですが、8bitだと浮動小数点を入れるにはbit数が足らないからですね。

そのため、浮動小数点をとある変換式にかけることでRGBAの 8bit x 4 の形式に変換し、多少誤差があるものの 8bit x 4 = 32bit/pixel から浮動小数点に直す方法をとることで、浮動小数点を 8bit x 4 = 32bit/pixel な形式のテクスチャに格納することができます。

ただし、0~1の間であることが条件になります。

変換

まず、0.87185264という値があるとします。

この数値に 255 をかけて、整数部分のみ切り出します。 これがRチャンネルに入る要素になります。

0.87185264 * 255 = 222.3224232 => 222

次に、先ほどの小数部分のみを切り出し、再度255をかけて整数部分のみをきりだします。 これがGチャンネルに入る要素になります。

0.3224232 * 255 = 82.217916 => 82

これを残りのB,Aチャンネル分の2回繰り返します。

0.217916 * 255 = 55.56858 => 55
0.56858 * 255 = 144.9879 => 144

最終的に R=222, G=82, B=55, A=144 という数値が得られました。

それでは、0~1の浮動小数点に復元してみましょう これまでは、255をかけて整数部分を切り出すことをしていたので、この逆の計算をしていきます。

(222 / 255) + (82 / (255 ^ 2)) + (55 / (255 ^ 3)) + (144 / (255 ^ 4)) = 0.8718526397663572

最終的に、差分は 0.8718526397663572 - 0.87185264 = -2.336428e-10 となりました。

この程度の差であれば、場面によってはほぼ誤差に近いと捉えることもできそうです。


参考

www.gamedev.net

【Rider】Windowsで.ideavimrcが読み込まれないとき

おそらく特殊な状況だったと思うのですが、メモしておきます。

経緯としては、IdeaVimのアラート音を消したいためにideavimrcを編集していたのですが反映されない現象に出くわしていました。


RiderでVimキーバインドを使う場合、IdeaVimを使うことになります。 設定は~/.ideavimrcに書くことで適用されます。

Windowsの場合は C:Users/{username}/.ideavimrc に置くことになります。 しかし、.ideavimrc に設定を書いても読み込まれることはありませんでした。

よくよく C:Users/{username}/ を見てみると、.vimrcが存在していませんでした。

そこで、空の.vimrcを作成し.ideavimrcから読み込むようにすることで、設定が反映されました。

ファイルの中身は以下のようになりました。


.ideavimrc

source ~/.vimrc
set visualbell
set noerrorbells

.vimrc は空

WebWorkerではプライベートフィールドを渡すことができない

ちょっとハマったのでメモです。

WebWorkerのpostMessageでデータを送るとき、渡すことのできるデータとそうでないデータがあります。

これは構造化複製アルゴリズムが適用されているからです。たとえばFunctionオブジェクトは送ることができません。

developer.mozilla.org

その中にプライベートフィールドも含まれていました。

chromeで以下のコードを実行すると Object で { hoge: "hogehoge" } が出力されます。

class Hoge {
  hoge;
  #fuga;
  constructor(hoge, fuga) {
    this.hoge = hoge;
    this.#fuga = fuga;
  }
  echo() {
    console.log(this.hoge);
    console.log(this.#fuga);
  }
}

const hoge = new Hoge("hogehoge", "fugafuga");

const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = (e) => {
  console.log(e.data[0]); // Object { hoge: "hogehoge" }
}
`])));

worker.postMessage([hoge]);

【Photoshop】レイヤーモードのオーバーレイの原理

Photoshopのレイヤーモードには、加算、乗算など様々な種類があります。

下のレイヤーに対して上のレイヤーの色をどう重ねるかの方式の設定になります。

WebGLで言うところのBlendModeに近い処理ですね。

例えば加算を設定した結果どういう色になるかは、感覚的にも、BlendModeと比較した場合にもイメージがつきやすいのですが、オーバーレイに関してはどういうことをやっているのか良くわからなかったので、計算方法を調べてみました。

計算式

おそらく、各レイヤーモードの計算式はこちらにまとまっているものになるかと思います。

stackoverflow.com

stack overflow の記事に載っている計算式を一部抜粋します。

#define ChannelBlend_Overlay(A,B)    ((uint8)((B < 128) ? (2 * A * B / 255):(255 - 2 * (255 - A) * (255 - B) / 255)))
...
#define ChannelBlend_Multiply(A,B)   ((uint8)((A * B) / 255))
#define ChannelBlend_Screen(A,B)     ((uint8)(255 - (((255 - A) * (255 - B)) >> 8)))

どうやら、下の色が128以上の場合はスクリーンを、128未満の場合は乗算を適用しているのがオーバーレイであるようです。

スクリーンの計算方法については今回は言及せず、オーバーレイについてのみ焦点を当てます。


オーバーレイを使った時の見た目のサンプルと、計算結果のサンプルを2パターン作ってみたので、それぞれ記載していきます。

計算は、オーバーレイの式をjavascript向けに編集したものを使いました。

// Aが下のレイヤーのピクセル色、Bが上のレイヤーのピクセル色(オーバーレイで重ねる色)
const overlay = (A, B) =>
  ((A < 128) ?
  (2 * B * A / 255) :
  (255 - 2 * (255 - B) * (255 - A) / 255));

1. 白黒のグラデにべた塗のレイヤーをオーバーレイで重ねる

中央の矩形が重なっている部分です。

f:id:takumifukasawa:20210926120503j:plain

// 白黒のグラデに真っ白のべた塗レイヤーをオーバーレイで重ねる(図上)

overlay(255, 255);
// 255
overlay(192, 255);
// 255
overlay(128, 255);
// 255
overlay(64, 255);
// 128
overlay(0, 255);
// 0

// 白黒のグラデのレイヤーに真っ黒のべた塗をオーバーレイで重ねる(図下)

overlay(255, 0);
// 255
overlay(192, 0);
// 129
overlay(128, 0);
// 1
overlay(64, 0);
// 0
overlay(0, 0);
// 0

傾向として、

  • 真っ白をオーバーレイで重ねる場合、下のピクセル色が、

    • 128以上: 255になる
    • 128未満: 0~128の範囲が0~255に変換される
  • 真っ黒をオーバーレイで重ねる場合、下のピクセル色が、

    • 128以上: 128~255の範囲が0~255に変換される
    • 128未満: 0になる

ということが分かりました。

見た目的には、白いレイヤーをオーバーレイで重ねると明るい部分の幅が広がり、黒いレイヤーをオーバーレイで重ねると暗い部分の幅が広がっていますね。

ざっくり書くと、全体をより明るく、より暗くする処理の出し分け、としてオーバーレイを使うことになるのかなと思いました。

(例えば、コントラストを部分的に強く、彩度を強くする使い方など?)

2. べた塗のレイヤーの上に白黒のグラデをオーバーレイで重ねる

f:id:takumifukasawa:20210926120514j:plain

// 真っ白のべた塗レイヤーに白黒のグラデをオーバーレイで重ねる(図上)

overlay(255, 255);
// 255
overlay(255, 192);
// 255
overlay(255, 128);
// 255
overlay(255, 64);
// 255
overlay(255, 0);
// 255

// 真っ黒のべた塗レイヤーに白黒のグラデをオーバーレイで重ねる(図下)

overlay(0, 255);
// 0
overlay(0, 192);
// 0
overlay(0, 128);
// 0
overlay(0, 64);
// 0
overlay(0, 0);
// 0

結果を見ると、オーバーレイで重ねる色に関わらず、下のピクセル色が0,255になっている場合はそのままの色(真っ黒か真っ白)になっています。

前述のように、「より明るく」「より暗く」することが目的のレイヤーモードだとすると、重ねる元の色によっては全く変わらない見た目になる場合があることは正しいと言えそうです。

【ffmpeg】ディレクトリ内の複数webmファイルを一括でmp4に変換

$ for i in *.webm; do ffmpeg -i "$i" "${i%.*}.mp4"; done

を叩くことで一括変換できます。

もし mov にしたい場合は mp4mov に変更すればフォーマットが mov になります。

【Unity】ディゾルブシェーダーの境界からパーティクルを出す

f:id:takumifukasawa:20210826112609g:plain

※ 「どういうアプローチで実装したか」を中心としたメモになるので、恐縮ですが具体的な実装はリポジトリを見ていただけると早いかもしれません。

↓ 作ったものをアップしたリポジトリになります。static な mesh と skinned mesh のどちらにも対応しています。

github.com

f:id:takumifukasawa:20210824233111g:plain

f:id:takumifukasawa:20210824233114g:plain

紙に火をつけると、どんどん炎が広がり紙が燃えていき、やがて消えてなくります。

ディゾルブシェーダーの境界にあたる部分は、燃えている場所・焦げている場所にあたると思います。

その境界からなにかしらのパーティクルを出してみたいなと考えていて、今回その仕組みを実装してみました。

f:id:takumifukasawa:20210824233119p:plain

環境

  • unity version: 2020.3.6f1

  • pipeline: URP

ポイント

VFX Graph でパーティクルを放出する位置はメッシュの頂点ではなく、ディゾルブの境界のワールド座標になるようにCompute Shaderで計算をしています。

放出する位置をメッシュの頂点位置にすると、頂点数が少ないときに「放出されている位置はメッシュの頂点」というのが分かりやすくなってしまう場合があります。

今回は「ディゾルブの境界からパーティクルを放出する」ことが目的なので、Compute Shaderで「ディゾルブの境界のワールド座標」を求める形にしました。

流れ

  1. ディゾルブの境界から放出する VFX Graph パーティクルは、Position from Mapを使って放出位置をテクスチャから参照する

  2. Compute Shader で、ランダムにサンプルしたポリゴン内における、ランダムな位置のワールド座標・「ディゾルブの境界率(後述)」を計算

  3. ワールド座標・「ディゾルブの境界率」をテクスチャに格納

  4. テクスチャを VFX Graph に渡す

  5. 「ディゾルブの境界率」を透明度として用い、境界から放出されるパーティクルは見えるように、境界外から放出されるパーティクルは見えないように

Compute Shader の中身はこちらになります。

github.com

デモでは法線情報もテクスチャに格納して渡しています。

テクスチャのデータ

もしディゾルブの境界になっている位置の情報だけをすべてテクスチャに格納していく形だと、格納されるデータは必ずディゾルブの境界情報になっている必要があるので、再帰的な処理が必須です。

そこで、テクスチャに格納するデータはメッシュのランダムな位置におけるパーティクル用の情報としてしまいます。デモでは「ワールド座標」「法線」「ディゾルブの境界率」が格納された3つのテクスチャを作成しています。

こうすることでテクスチャのデータのすべてがディゾルブの境界ではなくなるデメリットはありますが、再帰的な計算をしなくてよくなるという計算負荷的なメリットがあります。

例えば各種テクスチャのサイズが64x64でもピクセル数が4096あるので、ディゾルブの境界情報が必ず埋まるようにはなっていなくとも、ある程度の数のディゾルブの境界情報が埋まる状態を保つことができれば十分なはずと考えました。

実際、こちらのキャプチャは各種テクスチャのサイズは32x32になっていて、情報の少なさ(データの足りなさ)は感じられませんでした。

f:id:takumifukasawa:20210824233111g:plain

ディゾルブの境界率

では、これらのテクスチャを使ってどうやって境界からパーティクルを放出するように見せるのか。

まず、ディゾルブの境界率は一言でいうと「境界かどうか」です。今回の実装では「境界かどうか」ではなく「どれぐらい境界に近いか」を計算していて実際格納されるデータは0~1の間なので「境界率」という言い方をしています。

境界率がなぜ必要かというと、境界からのみパーティクルを放出するように見せるためです。

実は、パーティクルはディゾルブの境界以外からも放出されていて、ディゾルブの境界率をテクスチャに格納してVFX Graphに渡し、境界率に応じて透明度を調整しています。

例えば下の画像のように、メッシュは消えているにも関わらず見えていないパーティクルがたくさん放出されています。

f:id:takumifukasawa:20210825200505p:plain

境界率によって透明度を調整することで、境界から放出されているパーティクルは見えて、境界以外から放出されているパーティクルは見えないというのを実現しています。

しかし、この方法には欠点があります。画像のように見えないパーティクルも放出されてしまっているのが無駄な点です。

テクスチャとディゾルブの度合いの関係

前述のように、テクスチャに格納するデータはメッシュのランダムな位置におけるパーティクル用の情報になります。

どういうデータが格納されるかを可視化してみました。左から「ワールド座標」「法線」「ディゾルブの境界率」のテクスチャになります。

↓ 全体のディゾルブ度合い: 見えている状態 f:id:takumifukasawa:20210825190337p:plain

↓ 全体のディゾルブ度合い: 半分ぐらい消えている状態 f:id:takumifukasawa:20210825190330p:plain

↓ 全体のディゾルブ度合い: 完全に消えている状態 f:id:takumifukasawa:20210825190334p:plain

このように、メッシュが消えているかどうかに関わらずワールド座標と法線は必ずテクスチャに格納されています。


最後に改めてリポジトリのリンクを貼っておきたいと思います。

github.com

【Shell】Python2, Python3 のどちらのバージョンでも HTTP Server を立ち上げる Shell Script

簡易サーバーを立ち上げる際は python の HTTP Server を使うことが多いのですが、python2系とpython3系で微妙にコマンドが違います。

それぞれのバージョンで HTTP Server 用のモジュールが異なるためです。

具体的には、python3系だと python -m http.server 8000、python2系だと python -m SimpleHTTPServer 8000 で立ち上げることになります。

pyenv などの関係で立ち上げたいディレクトリごとに python のバージョンが違うと上記のコマンドも変える必要があり、少しややこしいです。

そこで、pythonのバージョン(2系か3系)に関わらず HTTP Server を立ち上げるスクリプトをつくりました。Mac限定です。

これを起動したいルートに配置し、ダブルクリックで起動します。すると http://localhost:8000 でアクセスできるようになります。

メジャーバージョンを取得して、2系か3系かでコマンドを出し分けています。