takumifukasawa’s blog

WebGL, Shader, Unity, UE4

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になっている場合はそのままの色(真っ黒か真っ白)になっています。

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

【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系かでコマンドを出し分けています。

【Javascript】素のjsでシングルトンクラスを実装する(ver. 2021/7)

jsでシングルトン的なことを実現する場合、module や import / export を使用できる環境下では例えばこのように export でインスタンスを渡すことでシングルトン的な振る舞いをさせることができます。

module.exports = new Klass();

export default new Klass();

最近、ブラウザ上で実行する素のjsでシングルトン的な機能が欲しい場面があり、クラスを使ったいわゆるシングルトンを作ってみようと思いました。

新しいAPIを試してみたいと思いMDNを漁っていたところ、private class fields が主要ブラウザ(chrome,firefox,safari)の各最新バージョンの「ほとんど」で実装されているみたいだったので使ってみることにしました。

「ほとんど」と書いたのは、 firefoxで private class fields を使うことができるのは v90 からで 2021/7/7時点のfirefox最新版v89系だとまだ使用出来ないためです。ただ、数週間のうちにv90系が出るらしいのと、今回は仕事で使う用途ではないので実験的に組み込んでみる判断にしました。

developer.mozilla.org

class Singleton {
  static #instance;
  num;

  static get instance() {
    if(!Singleton.#instance) {
      throw "no instanced";
    }
    return Singleton.#instance;
  }

  constructor(num = 0) {
    if(Singleton.#instance) {
      return Singleton.#instance;
    }
    Singleton.#instance = this;
    this.num = num;
  }

  up() {
    this.num++;
  }
}

...

const s = new Singleton();
s.up();
s.up();
console.log(s.num); // 2

...

const ss = Singleton.instance;
// ↑ const ss = new Singleton(); でも同じインスタンスが返ってくる

ss.up();
ss.up();
console.log(ss.num); // 4

ちなみに、private class field が # を使って表す理由はこの記事がとてもわかりやすかったです。「# しか使うことが出来なかった」そうです。

blog.jxck.io

【Javascript】ビット演算で2の累乗判定する関数

2の累乗判定をするときに、ビット演算だとかなりシンプルになることを知ったのでそのメモになります。

こちらがその関数です。

const isPowerOfTwo = x => (x & (x - 1)) === 0

原理は以下のようなものです。

数値が2の累乗の場合は必ず最上位ビットのみが1になります。 その数値に1を引くとすべてのビットが反転するので、元の数値-1をした数値論理積 & を使うと 0 になる性質を利用しています。

2 ... (10 & 01) === 0 => 00 === 0 => true
3 ... (11 & 10) === 0 => 10 === 0 => false
4 ... (100 & 011) === 0 => 000 === 0 => true
5 ... (101 & 100) === 0 => 100 === 0 => false
8 ... (1000 & 0111) === 0 => 0000 === 0 => true