"そこ"から這い上がるブログ

プログラミングと電子工作が趣味でそこら辺のことを備忘録的に書きつつ這い上がっていこうといったブログ。そこ這い。

Windows環境でLazygitのdiff表示を変更する

全然ブログを更新しないくらいならこれくらいの内容でも書いたらいいと思ったので書いてみた。

はじめに

最近GitのビューワーとしてLazygitを使ってます。

github.com

それに合わせて前よりPowerShellを使うようになってます。 batやら、ps1やらをこれまでってちょっとしたことも結構調べて作ってましたが、ChatGPTに気軽に聞いて作れるのでかなり楽になりました。

で、Lazygitのdiffの表示をデフォルトからdeltaに変えるといいよってそこかしこに書いてあるのですが、 config.ymlを変更してもWindows環境だと全然反映されなくて、探し回ったのでメモ。

手順メモ

ドキュメンとみるとちゃんと書いてあって。

There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as lazygit-pager.ps1 at a convenient place on your disk:
Windows上でカスタムページャをエミュレートするコツがあります。外部diffコマンドとして設定されたPowerShellスクリプトを使用する方法です。完璧ではありませんが、何もしないよりは確実に優れています。これを行うには、以下のスクリプトをlazygit-pager.ps1としてディスク上の任意の場所に保存してください:

とな、 以下のスクリプトはそのまま使わないので貼らないですが、lagygit-pager.ps1を作った後 %LOCALAPPDATA%\lazygit\config.yml に

git:
  pagers:
    - externalDiffCommand: "C:/wherever/lazygit-pager.ps1"

とファイルの場所を指定してあげればよいようです。

ここでようやくlazygitのdiff表示に変化があり、これまで全然変わらなかったのよりは進歩しました。 が、肝心のdiffが見えなくなってて意味がない。(ただただ自分が間違っているだけなんでしょうが)

そしたらIssueのやり取りの中に答えがありました github.com

リンク先の内容としてはpowershellだと遅いからbatを使ったら高速化できるよって話なんですが、 そもそも表示が出来なかったので超助かりました。 以下のスクリプトをbatファイルとして保存して

@echo off

set "old=%~2"
set "new=%~5"
set "target=%~1"
set "old=%old:\=/%"
set "new=%new:\=/%"

git --no-pager diff --no-index --no-ext-diff "%old%" "%new%"^
 | sed -e "s|%old%|%target%|g" -e "s|%new%|%target%|g"^
 | delta --paging=never --width=%LAZYGIT_COLUMNS%  --side-by-side --line-numbers --hyperlinks

で、config.ymlに

externalDiffCommand: "C:/whatever/git-diff.bat"

でOK

【Unity】良い感じに被写界深度(DoF)を調整できるカメラを作る

この記事は Akatsuki Games Advent Calendar 2025 の13日目の記事です。

はじめに

レコルト良いですよね、具材入れて待っていたら料理が良い感じに出来が上がっている。最高。
持っているのは炊飯器と兼用のやつなので、別にしっかりしたのが欲しいなと思ってきた今日この頃。
こんにちは、奈茶です。

被写界深度(Depth of Field, DOF)、3Dではよく用いられるカメラ演出かと思います。
これが入ることで、画がグッと良くなりますよね。

マグカップにピントが合っている

そんなDOF、URPにも用意されているのですが、これがそのままではちょっと使いづらい
というわけで今回はUnityで被写界深度を良い感じに調整できる機能を作成していきます。
スクリプトも共有しますので、皆さんの環境に合わせて組み込んでみていただけるかと思います。

今回使用したUnityとURPのバージョンは以下です。

  • Unity 6002.10f1
  • Universal Render Pipeline 17.2.0

※今回試してみたくてUnity6を使用していますが、今回の記事の内容はもっと前のUnityとURPのバージョンでも問題ないはずです。

被写界深度

いきなり本題に入る前に、事前の用語の確認です。
用語自体は、物理カメラでも用いられているものと同じです

被写界深度とは、ピントが合っている範囲のことです。 なので、

  • 被写界深度が浅い(ピントが合う範囲が狭い)、とボケやすい
  • 被写界深度が深い(ピントが合う範囲が広い)とボケにくい

被写界深度が浅い
被写界深度が深い

となります。
そしてこれにはいくつかの要素が関係しており、今回必要な部分をかいつまむと以下の表の様な関係になります。

要素 ボケる ボケない
被写界深度 浅い 深い
撮影距離(被写体とカメラの距離) 近い 遠い
絞り値(F値) 小さい(絞りを開ける) 大きい(絞りを絞る)
焦点距離 長い 短い

URPのDepth of Field ポストプロセス

では、用語の確認をしたところで、URPのDOFの項目を見ていきましょう → 公式ドキュメントはこちら

Bokehモードは実際のカメラ挙動を模して造られているため、
Focus Distance(撮影距離)、Focal Lenght(焦点距離)、Aperture(絞り値)のパラメータが調整できるようになっています。
Bladeの項目はボケの見た目に影響し、被写界深度とはちょっと違うので省略します。

つまり、良い感じにボケた被写界深度を設定するためには
撮影距離をピントを合わせたい距離にして、絞り値と焦点距離をそれに合わせて大きくしたり、小さくして調整すればいいのです。
対象が移動したら、撮影距離が変わるので被写界深度も変わります、そうなるとボケ具合が変わるので絞り値と焦点距離も......めっちゃ大変!!

実際どうなの

という画像を用意しました。 以下のシーンでは3つの写真立てが並んでいますが、それぞれカメラから5m, 7m, 9mと離れた位置に置いてあります。
そして背景の壁は23m程離れています。

DOFなし

これにDOFを設定して、「隣の写真は見えるけど、2つ隣はボケて、背景もボケる」被写界深度前後2mずつくらいの良い感じに設定したいとします。
5mを基準とすると、焦点距離 60, 絞り値 1.4 が良さそうです。
これで他の撮影距離でも確認してみると以下のようになります。

5m(基準) 7m 9m

遠景を見ていただければわかりやすいですが、なーんかボケ具合違くない?となってしまいます。
しかしそれは当然で、撮影距離が遠くなった分被写界深度が深くなり、ボケにくくなるからですね。
なのでその分焦点距離を長くするか、絞り値を小さくする必要があります。
写真数枚なら頑張れるかもしれないですが、動画でキャラクターが移動したりする中で手動はまず無理な上、インタラクティブにフォーカスを変えられそうにはないことが分かります。

Depth of Fieldの実装を見る

そこで、先ほどのパラメーターからどのような計算を経てボケを作成しているのかを見ていきます。
詳しい実装の解説に関しては Colorful Paletteさんの記事が非常に参考になりました。ありがとうございました!

media.colorfulpalette.co.jp

この記事では詳しい実装の解説は行わず、必要な部分だけを見ていきます。

DOFの実装は2か所に分かれます、1つは実際にポストプロセスとして処理を行うshaderと、そこに必要なパラメーターを事前に計算して渡すスクリプトです。

以下は BokehDepthOfFiled.shader内のFragCoC関数で、CoC(Circle of Confuse, 錯乱円)を求める部分になります。

half FragCoC(Varyings input) : SV_Target
{
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord);
    float depth = LOAD_TEXTURE2D_X(_CameraDepthTexture, _SourceSize.xy * uv).x;
    float linearEyeDepth = LinearEyeDepth(depth, _ZBufferParams);

    half coc = (1.0 - FocusDist / linearEyeDepth) * MaxCoC;
    half nearCoC = clamp(coc, -1.0, 0.0);
    half farCoC = saturate(coc);

    return saturate((farCoC + nearCoC + 1.0) * 0.5);
}

錯乱円とはざっくりとボケのサイズのことになります。
ここの処理ではDepthデータから値を変換して、0~1の範囲で値が正規化されますが、0.5が一番ピントが合っており、0.5より大きいと遠景ボケ、0.5より小さいと前景ボケとなっていきます。
つまりできるだけ広い範囲を0.5に近い数値にすることでボケにくく、逆に0や1に持っていくことでボケやすくなります。

ここで重要なのがMaxCoCの値になります。MaxCoCが大きい値ならボケやすく(被写界深度が浅く)、小さい値ならボケにくく(被写界深度が深く)することができ、この値を固定すれば、同じようなボケ具合にできそうです。

そしてMaxCoCはPostProcessPass.cs 内のDoBokehDepthOfField関数内で以下の様に求められています。

// "A Lens and Aperture Camera Model for Synthetic Image Generation" [Potmesil81]
float F = m_DepthOfField.focalLength.value / 1000f;
float A = m_DepthOfField.focalLength.value / m_DepthOfField.aperture.value;
float P = m_DepthOfField.focusDistance.value;
float maxCoC = (A * F) / (P - F);

maxCoCを求めるためにfocusDistance, focalLength, apertureが使用されています。
maxCoCから逆算してこれらのパラメーターを決めるようにしてみましょう。

focusDistanceは被写体によって決まるので固定値となります。
apertureの方が幅が狭い(0~32)となっている都合で、apertureも固定して、focalLengthを求めたいと思います。

ソースコードから

maxCoC=(A*F)/(P-F)

これらを各パラメーターに戻すと

maxCoC=\frac{(focalLength/aperture)(focalLength/1000)}{(focusDistance- focalLength/1000)}

focalLengthをx、apertureをa、focusDistanceをd、maxCoCをmとすると

m=\frac{(\frac{x^2}{1000a})}{(d-\frac{x}{1000})}

この式をなんやかんやして

x^2+amx-1000amd = 0

と二次方程式で表せます。

2つの解の内、負の値は不要なのでfocalLengthは

focalLength=\frac{-aperture \cdot maxCoC+\sqrt{aperture^2maxCoC^2+4000aperture \cdot maxCoC \cdot focusDistance}}{2}

で求めることが出来ます。

実装

先ほどの式をもとに以下の様な関数を作成しました。 この記事で一番重要なのがここです。

static float CalcFocalLength(float focusDistance, float aperture, float maxCoc)
{
    var A = aperture * maxCoc; 
    return (-A + Mathf.Sqrt(A*A + 4000.0f * A * focusDistance)) / 2.0f;
}

これを組み込んだコンポーネントを作成しました。

アタッチするとこんな感じです。 MaxCoCの値と、Apertureを決めればFocusTargetにピントが合うように良い感じにしてくれます。

結果

では、先ほどの関数を使用するとどうなるのか見ていきましょう。 また撮影距離5m、焦点距離 60, 絞り値 1.4 を基準に、他の距離でどうなるか見てみます。 maxCoCの値は0.5206に設定しています。

5m(基準) 7m 9m
焦点距離は約60
焦点距離は約71
焦点距離は約81

どうでしょう?背景のボケ具合はそのままに、写真だけピントの合い方が変わっているように見えないでしょうか?

maxCoCの値を変えることでボケの強さを変えることが出来ます。
以下のGIFはマグカップにフォーカスを合わせて、maxCoCの値だけ変更しています。

オートフォーカスみたいなことも簡単にできます

終わりに

maxCoCの値自体を見てもボケ具合は想像できないので、見た目ありきの調整方法ではあります。
それでも素のパラメーター調整よりはかなり直感的に調整できるようになったのではないでしょうか。

今回は紹介しきれなかったのですが、解像度が変わると被写界深度のボケ具合も変わってしまいます。
基準解像度を設定して、それに合わせる等何かしら解像度に基づいた対応を入れる必要があります。
書けたら来年その記事も書くかもしれません、来年に期待。

ここまで書いてRAYNOSちゃんでポートレート写真みたいなのを撮ればよかったなーと思いました。

参考

GBCの液晶を交換した

先日、GB USB SMART CARD 64M という書き込みができるゲームボーイのカセットの存在を知りまして、これを使えば自分でゲームボーイ用のソフトが作れるらしい。

ということで、押し入れから当時使っていたゲームボーイカラーを引っ張り出したところ……

ブックオフで100円だったグランデュエル

全然画面見えないし、なんか割れてるような状態でした。実際には割れてはいなくてフィルムがシワシワになって周りが黒くなっている感じです。
どうやらこれはビネガーシンドロームという現象で、液晶の偏光フィルムが加水分解してこうなってしまうようです。湿気たところにずっと放置されてましたからね……

偏光板を剥がして交換することで復活するらしいのですが、どうせならバックライト化してみたくて、Aliexpress でバックライト付き液晶を買いました。

まずはY字型のドライバーでケースを開けて、液晶を取り外します。

一枚目の写真左(本体の頭側)のリボンケーブルが液晶と繋がっている

新しい液晶は元のやつよりサイズが大きいので、それに合わせてケースを削る必要があります。

削る前とあと

彫刻刀で傷をつけてペンチで割っていきました。
フレームを広げるのと画面下の出っ張りを取り除きます。

新しい液晶。写真右の基盤は中心のリボンケーブルを折って液晶の上に重ねます。

そうして新しい液晶を取り付けた結果……

綺麗になったグランデュエル

めっちゃ綺麗に映るようになりました。バックライト付きなので明るい。
タッチセンサが液晶の基盤についていて、本体の右上辺りを触ると明るさの調整もできます。

肝心のGBCソフト作りはHelloWorldを出すまでやった後、開発は止まってしまっていますが……

 

・・・

3年ぶりのブログ更新、といってもあまり中身のないものですが。
3年前も1月に書いたきりなので、今年こそはブログを更新していきたいー
それとゲーム制作も進めているのでそちらも出せるように頑張ります。

2023年もよろしくお願いいたします。

 

ポケモソの近づくと透明になるシェーダーを再現してみた

明けましておめでとうございます!


……というわけでポケモンの近づくと透明になるシェーダーを再現してみました。

f:id:knasa:20200114224925g:plain

 

ちなみに元ネタ

f:id:knasa:20191226004800j:plainf:id:knasa:20191226004803j:plain

ゲームボーイカラー以来のポケモンに「3Dだー!」「捕まえるだけでみんな経験値もらっていいんですか⁈」なんて思いながら遊んでたのですが、カメラに近づくと透明になる柱に気づきましてこれは作れそうじゃないかと思った次第です。

まずは分析

カメラが近づくと半透明になる代わりに、徐々に透明になるピクセルがあるパターンで消えています。透明になるピクセルは形状に関係なくスクリーンスペースで決められているっぽい。というのはわかったんですが、この透明にしていくパターンがわからない……ググってどうやらこれはBayerマトリックスと呼ばれるパターンのようです。

 

これは一体......?

ディザ抜きといわれる半透明に見せかけるための手法で、実際に半透明にするよりも処理負荷が軽いとのこと。ドットをわざと見せてるあたりポケモンでは処理負荷より演出の意味合いが強そう。

 作ったコードはこんな感じ
名前をDitheringTransparentにしようと思ったら、どうやら英語ではScreenDoorっぽい

gist.github.com

 

そしてマテリアルの設定

f:id:knasa:20200114230156p:plain

BayerTexのところにはBayerMatrixをもとにした4ⅹ4ピクセルのグレースケール画像を入れます。
{\displaystyle {\frac {1}{16}}\times {\begin{bmatrix}0&8&2&10\\12&4&14&6\\3&11&1&9\\15&7&13&5\\\end{bmatrix}}}

画像はWikipediaさんより
この行列に255をかけてあげればおk
画像は

f:id:knasa:20200114231000p:plain←これ(4x4なのでとても小さい)

ただこの時注意なのが、ImportSettingsでCompressionを"High Quality"にすること。
そうしないと、量子化されてしまうようでちょっと物足りなくなります。

f:id:knasa:20200114231538p:plain

f:id:knasa:20200114231638p:plain←"Normal Quality"だと四色ぐらいになってしまう
それとこれは結果には関係ないですが、Filter Modeを"Point(no filter)"にしておくと見やすいです。
そうじゃないと拡大時にボケてしまってInspectorでみるときにちょっとわかりにくくなります。

後のパラメータは
BlockSize : この数で割った分のピクセルがBayerMatrix一枚分になる
Radius : 透明化を始める距離
BlockSizeは見やすいようにちょっと大きめにしてます。

おしまい

何とか今年初記事を書きました。(ほんとは去年書くつもりだった)
シェーダーをいじる機会が増えてて、ちょうどこれはいい勉強になりそうだったのでやってみた次第です。
カメラに近づくと透明になるシェーダーってなんて名前なんですかね?
「カメラに近づくと透明になるシェーダー」なんですかね?

 

と、こんなところでガラルに帰ろうかと思います。
まだバッジ集め終わってないんですよ……

参考サイト様




 

ベクトルを同じ向きにするための回転行列の求め方

ある方向を示すベクトルAがあって、他のベクトルBをその方向に向けたい場合に

回転行列の求め方に悩んだのでメモ。

やり方としては

  1. AとBの内積からなす角( 回転量 )を求める。
  2. AとBの外積から 回転軸 を求める。
  3. 回転量 と 回転軸 から回転行列を作る。

といった感じです。

// toVec, fromVecは正規化されているVector3f
// 二つのなす角( 内積を求める )
float angle = acos( fromVec.dot( toVec ) );
// 回転軸( 外積を求めて正規化 )
Eigen::Vector3f axis = fromVec.cross( toVec ).normalized();
// 回転量と回転軸から回転行列を生成
Eigen::Matrix3f rotateMat = Eigen::AngleAxisf( angle, axis ).matrix();

Eigenを使った例ですが、考え方自体はUnityなどでも使えるはずです。

回転量と回転軸から行列を求めるのにEigen::AngleAxisfクラスを使用して楽をしてしまっていますが、

ロドリゲスの回転公式 を使うと求められるはず

EigenではAffineクラスを使って移動(+拡大)も含めたアフィン変換の行列の生成もできます。

// クォータニオン
Eigen::Quaternionf quat( Eigen::AngleAxisf( angle, axis ) );
// アフィンクラス
Eigen::Affine3f affine;
// 移動量
Eigen::Translation3f translation( Eigen::Vector3f( 0.5f, 1.5f, 0 ) );
// 移動と回転の合成
affine = translation * quat;
// 行列の取得
Eigen::Matrix4f affineMat = affine.matrix();

にしてもベクトルってのはすごい便利だなぁ

参考先

自作DLLのリンカエラーについて

本題と関係ないところで長時間詰まったので、もうしないように(何度かしてる)メモ

DLLを作成し、それを別のプロジェクトで利用しようとした際に、リンカエラー(LNK2019)が出てしまう。

DLLが配布されているようなもの場合はまず自分のプロジェクト設定を疑って、対象DLLの.libファイルがちゃんとリンクされているかを確認する。

ちゃんとリンクはされている&自作の場合疑うべきは
__declspec(dllexport)  の付け忘れ
自分の場合は大体これが原因

そして、それをつけてリンカエラーは消えるけども、別のエラー(C2280)が出る場合があり、
今回はそれも当たってしまった。

std::unique_ptrを使ったstd::vectorなどのコンテナクラスを含んでいる場合に起きるエラーの様で
以下のリンク先によると

c++ - DLL exporting causing issues with unique pointers - Stack Overflow

【機械翻訳】

declspec(dllexport)でクラスを宣言する場合、コンパイラは、インポートモジュールに必要なものがわからないため、デフォルトのコンストラクター、コピー割り当てなどの関数を含む、クラスのすべてのメンバー関数を生成する必要があります。

これについては、C ++クラスでのdllimportおよびdllexportの使用で説明されています。 unique_ptrはコピーできないため、そのコピーコンストラクターとコピー割り当て演算子は削除され、ベクターオブジェクトがそれらを使用しようとすると、C2280エラーが発生します。

declspec(dllexport)を含めない場合、コンパイラは実際に使用される関数のみを生成し、問題のあるコピーは回避されます。この問題を回避する1つの方法は、個々のクラスメンバー関数をエクスポートすることです。

これは、それらの一部をデフォルトとして指定することを意味する場合があります。仮想関数はvtableによって処理されるため、エクスポートする必要はありません。

別の回避策は、コピーコンストラクタとコピー割り当て演算子を明示的に削除することです。これにより、デフォルトコンストラクターの作成が妨げられ、コンストラクター/割り当て関数が移動されるため、これらをデフォルト設定する必要がある場合があります。

 

class UNIQUEPTRISSUE_API ClassB {
public:
    ClassB(const ClassB &) = delete;
    ClassB &operator=(const ClassB &) = delete;
    // You may need to explicitly default these if they are used
    ClassB() = default;
    ClassB &operator=(ClassB &&) = default;
    ClassB(ClassB &&) = default;
private:
    std::vector<std::unique_ptr<ClassA>> x;
};

説明にある二つ目の方法が以上のコードで、これで自分の場合は無事解決しました……
よくわかってないで使うからこういうことになるんだなぁとしみじみ

PCLで関数の実行速度を測りたい時の便利関数

新年初記事です。明けましておめでとうございます。
Looking Glassがですね去年届きまして、動画の作り方でも記事にしようと思っていましたが、
それはまた別の機会に(早く書こうという気持ち)

毎度わすれるので、備忘録として
特に大した説明もないのでコードを

#include <pcl/common/time.h>

void hoge()
{
    pcl::ScopeTime scopetime( "hoge_func" );
    // なんか処理
}

出力

hoge_func took 〇ms

pcl::ScopeTime scopetime( string );
これだけでその関数内の実行時間がわかっちゃいます。一行で済むの便利。
確認してないですが、名前的にスコープ内の時間がわかる感じなので
正確には関数内というよりは、スコープ内の時間測定ですかね。