4/10

日記

2026/04/10

Three.js

フレームレートに依存させない

glslで(通常の時間を用いてアニメーションさせる場合でも)、直前フレームからの経過時間deltaを使うと、30fpsでも60fpsでも同じ用に見せることができる。

ただ、そのままdeltaを使うと、調整用の半端な値を、アニメーションの画面を見ながら調整することになるかもしれない。

このとき、60fpsなら1、30fpsなら2をとるようなdeltaRatioという変数を取ると便利かも。

tick(dtMs: number) { // dtMsは直前フレームからの経過時間(ミリ秒)
 const deltaRatio = dtMs / (1000 / 60);
}

1000 / 60は60fpsのとき1フレームの長さ(約16ms)。
なのでdeltaRatioは今のフレームが、60fps基準の1フレームの何倍かを表す。

これをglsl側で使うも良し、毎フレーム数値を送るときに speed * deltaRatioとするもよし。

Three.cameraから、視線レイの方向ベクトルを求める

視線の方向ベクトルを求めるには、視線の始点(カメラのポジション)と、視線上のある一点(カメラのポジションと同じ座標。ここではワールド座標)が必要だよね。
それらの座標から方向ベクトルを求めて、正規化すると完成。

tmpOrigin: THREE.Vector3
tmpDir: THREE.Vector3

tick() {
 // まず、カメラのポジションを求める
 this.tmpOrigin.setFromMatrixPosition(camera.matrixWorld);

 // 視線上の適当な座標を求める(this.mouseはndc)
 this.tmpDir.set(this.mouse.x, this.mouse.y, 0.5); //0.5は適当な値で良い(必要なのは方向ベクトルだから)
 this.tmpDir.unproject(camera);

 // カメラの位置から求めた適当な座標へのベクトルを求め、正規化し、レイディレクションゲット!
 this.tmpDir.sub(this.tmpOrigin);
 this.tmpDir.normalize();
}

注意点

カメラのポジションを求める時、this.tmpOrigin.copy(camera.position); としても、大体は問題ないと思う。
「大体は」というのは、camera.positionがローカル座標を表しているから。
カメラに親があり、その親が変形していると、不正な値になる。
ワールド上のカメラの位置が欲しいなら、以下の方法がある。

this.tmpOrigin.setFromMatrixPosition(cam.matrixWorld);
// または
cam.getWorldPosition(this.tmpOrigin);

レイを伸ばして、z = 0に固定した時のxy平面の座標を特定する。

tmpOriginと、tmpDirの角度は、tmpDir.angleTo(tmpOrigin)で求められる。
ほぼ直角だと3.14に近い値、端のほうだと2.9位になる。
今求めたいのは、カメラから見てz=0の時の距離。

? = l / cosθなので、

const dist = this.tmpOrigin.length() /
      Math.cos(Math.PI - this.tmpDir.angleTo(this.tmpOrigin));

となる。あとは、tmpOriginから、tmpDirをdist倍したところが目的の値になる!感動。

this.tmpOrigin.add(this.tmpDir.multiplyScalar(dist));

パーティクルではなく、三角形を使うということ

  • view空間の理解が少し深まる。
  • bufferGeometryを利用する解像度が上がる。

パーティクル描画段階(GPGPUからtexturePositionが発信されている前提)のvertexShaderにて、
fboUVにはその点がtextureのどのUVを読むかが入る。
texturePositionにはxyzにparticleのpositionが入る。
positionには、三角形のbufferGeometryに送った頂点座標が入っている。

attribute vec2 fboUV;
uniform sampler2D texturePosition;

void main() {
  vec4 positionInfo = texture2D(texturePosition, fboUV);
  vec3 pos = positionInfo.xyz;

  vec4 modelPosition = modelMatrix * vec4(pos, 1.0);
  vec4 mvPosition = viewMatrix * worldPosition;
 
  gl_Position = projectionMatrix * mvPosition;
}

パーティクルの場合、上のようにすると描画されるが、今回は三角形。

positionInfoにはもちろんpositionが入っているが、三角形の3頂点すべて同じ座標になる。(面積0で何も描画されない状態)
なので、ビュー空間にローカルのpositionを足してやる必要がある。

mvPosition += vec4(position, 0.0);

これで、テクスチャの中心 + 三角形の頂点ずれになり、パーティクル(三角形)が見えるようになる。

注意点

これでいいんでない?って思った。

pos += position;

実際、これでも普通に見えたが、ビュー空間に足すと、カメラから見たスクリーンに近い向きで三角形が広がるので、
例えば横から見た時には上の方法だと細く見えてしまうが、ビュー空間だと平常に見える。

ロジカル・シンキング

自分の中の基準をもつ

数(全国)

図書館

3400

マクドナルド

3000

スターバックス

2100

コメダ

1000

歯医者

68000

コンビニ

55000

IT企業

38000

学校

小:20000
中:10000
高:5000

学校は、小中高と半分ずつになっているのが面白い。

また、よく言われている話だが、歯医者の数がコンビニの数より多い。多いことは知っていたが、1万以上もあるとは。
でも、ここで考えをやめてはならない。今だけの事実であって、過去のデータを見て、傾向をみたい。
調べてみると、昔から歯医者は多かった。昭和には、歯医者40000、コンビニ10000弱で、コンビニが追いついてきているというのが実情らしい。
「歯医者の数がコンビニの数より多い」だけ聞くと、なんだか歯医者が急増しているように感じる。私も実際感じていたけど。
一つの指標や一つの時点だけで物事を判断せず、視野を広くもちたい

率と数

データ化するときは、率と数をみる。率が教えてくれない情報を、数は教えてくれる。逆もまた然り。

学生の時つまらなかった標準偏差

平均値、中央値、と勉強することある。その最後に登場したのが標準偏差だった記憶がある。
なんだか名前が分かりづらいし、使い所わからないし、計算めんどくさいしすごく嫌いだった覚えがある。
でも今見ると楽しかった。

平均点が60点の英語のテストで80点をとり、
平均点が60点の国語のテストで80点をとったして、
どちらが良い成績だろう。

同じ成績です!ではない。生徒ごとに点数の分散があるので、こういうときに標準偏差。
標準偏差はばらつきを考える。標準偏差が大きいほど点数がばらついているし、小さいとばらつきが少ない。

英語の標準偏差が10として、国語の標準偏差が30とすると、国語のテストでは高得点を取った人がある程度いたということになる。式でいうと、平均との差20点/標準偏差10 = 2  平均との差20点/標準偏差30 = 0.66

よって、英語の点数の方がいい成績である。
こういう、平均との差を標準偏差で割るのを標準化という。

会社で、すべての人の成績(売上というか、どれだけ仕事をこなしたかという指標)が見れるようなことがあり、他の部署の方々と比べられるようになっていたが、よく考えればこの部署ごとの標準偏差が必要だった。

内省

図書館に通って1か月、ずっと目の前に併設されていたカフェをスルーして館内に入っていたが、ついに我慢できなくなり、コーヒーを一杯飲みにいった。カフェで本を読む時間が好きになってきている。
ありきたりな表現だが、読書をすると自分の中の語彙や世界が広がる感じがする。
YouTubeを見たり、ラジオを聞いたりしているだけでは広がらないんだ、と25年生きてようやくわかった。これはおそらく周りと比べて発見遅め。残念なことだ。
でも遅いは問題ではない!一位と最下位の差は、ゴールした者とそうでない者の差に比べれば、大したことないのだ。

そして新たな発見。読書をした後勉強すると、捗る。集中力が長持ちするし、質も良い。
最初に読書をするか、集中力が落ちてきたら読書という風に交互に取り組むと、効率よく学習できるかもしれない。
これは試さないと。