【Unity-Shader】#00 レンダリングパイプライン

この記事は…

booth.pm

主に上記の電子書籍で学んだことを自分用にまとめたものです。
Shaderを学ぶのに始めに読むのにかなりお勧めの本でした!

UnlitShader

Un Lighting Shader の略。
陰影や輝きを表現する「ライティング」「シャーディング」といった処理を含まないシェーダのこと。
オブジェクトの設定された色やテクスチャがそのまま描画されるもっともシンプルなシェーダのこと。

つまり、学習しやすい。

3Dオブジェクトを描画するには

「レンダリングパイプライン」と呼ばれる工程を行うことで、3Dオブジェクトが画面に出力される。

レンダリングパイプライン

1:頂点の映る位置を算出する ⇒Vertex Shader
2:ポリゴンの表裏を確認する ⇒Culling
3:描画する画素を決定する ⇒Rasterize
4:ZBufferの値を比較する ⇒Z Test
5:画素を描画する ⇒Fragmet Shader(Pixel Shader)
6:描画する色を合成する ⇒Blending
7:ZBufferの値を更新する ⇒Z Write

1と5、つまりVertex ShaderとFragment Shader の処理部分はプログラムを実装して自由に変更することができる。

プログラマブルシェーダー

実装するプログラムによってオブジェクトの描画方法を任意に実装する仕組みのこと。
フラグメントシェーダや、頂点シェーダはまさにこれ。

1:頂点の映る位置を算出する Vertex Shader

・オブジェクト←複数のメッシュ
・メッシュ←複数のポリゴン
・ポリゴン←3つの頂点

画面上のどの位置に頂点を描画するのか算出する。ポリゴン1つを表示するには3つ頂点があるので3回行う。

2:ポリゴンの表裏を確認する

ポリゴンには表裏が設定されている。
 →unityではその頂点の番号が時計まわりに見える方向を表として定義している

描画負荷を下げるために裏面をむいているポリゴンは描画しないようにしている=culling (カリング)
shaderにおけるcullingを Backface Culling という

というのがデフォルトの動作で設定によって、裏面のみ描画、表・裏どっちも描画、という風にもできる

3:描画する画素を決定する

ポリゴンが画面上のどの画素に描画されるかを決定する=ラスタライズ Rasterize
1で頂点位置が決まり、3でポリゴンがどの画素で描画するかが決まる

4:ZBufferの値を比較する

描画する画素に既に描画されているポリゴンがないか確認する。描画済みのポリゴンが存在するかどうかは「Z Buffer」に書き込まれている
「Z Buffer」は描画する画面と同じ解像度を持った異なる画面
「Z Buffer」=「Depth Buffer」・深度バッファ
「Z Buffer」にはカメラからポリゴンまでの距離を表す値=z座標が書き込まれている

描画しようとしているポリゴンのZ値がすでに描画されているポリゴンのZ値よりも小さい場合は、
描画しようとしているポリゴンの方が手前になる

Z値を比較する処理を「Z Test」、またはDepth Testという
「Z Testに成功する」とは、Z値を比較して、新しいポリゴンが描画できる状態のことをいう

5:画素を描画する Fragmet Shader(Pixel Shader)

画素に描画する色を算出して描画する
色は光源や、他のオブジェクトの影を考慮したり、あるいは、テクスチャを参照するなどして算出する

この処理はラスタライズで決定された画素の数分行われる。

6:描画する色を合成する Blending

半透明なオブジェクトを描画する場合、重なった奥にあるオブジェクトを描画してから、そのあとに半透明なオブジェクトの描画を行う。
半透明なので、先に描画したオブジェクトの色と合成して描画する。

★不透明なオブジェクト→半透明なオブジェクト という順番で描画する

7:ZBufferの値を更新する

画素の描画が終わったら、Z Bufferに新しいZ値を書き込んで更新する。=「Z Write」

【C#】in / out / ref パラメータ修飾子

パラメータとは、引数のことです。

C#の引数には in, out, ref というパラメータ修飾子をつけることができます。

今回の記事は、パラメータ修飾子それぞれの機能ついて

パラメータ修飾子をつけない場合

C#では、値(プリミティブ)型でも参照型でも「値渡し」になります。

詳しくはこちら↓

www.snoopopo.com

ref パラメータ

refをつけると参照渡しになる。

private void Test()
{
    int a = 0;
    Poyo(ref a);
    Debug.Log(a); //出力結果は1になる
}
    
private void Poyo(ref int a){
    a = 1;
}

out パラメータ

outをつけると、呼ばれた側のメソッドで代入しなければいけない=代入されることが保証される。
もちろん、値渡しにするとそれが実現できないので、参照渡しになる。

private void Test(){
    int a; //初期化する必要なし
    Poyo(out a); //outはつけないとだめ。
}

private void Poyo(out int a){
    a = 1; //代入する必要がある
}

渡すときにoutつけるので、渡す側のメソッドでは変数に値が代入されることが呼ばれる側のメソッドの定義を見なくても一目でわかる。

outパラメータはint.TryParse()のように渡したパラメータに結果を詰める、
みたいなメソッドでよく使われる。

int result; //初期化の必要はない
int.TryParse(input, out result);
Debug.Log(result);

渡したList型に値を詰めるメソッドとかはよくあると思うので、そういう時もoutパラメータが有効。

List result;
Poyo(out result);

c# 7.0 からは以下のように省略する書き方もできるようになった。

  • c# 7.0以降▼
int.TryParse(input, out int result); //省略した書き方
Debug.Log(result);

in パラメータ

inパラメータはC#7で追加された。

読み取り専用の引数になります。

読み取り専用なので、inパラメータがついたメソッドを呼ぶ前に必ず値を代入しておく必要があります。

  • 呼ぶ側
int a;
Val(a); //aの値が代入されてないのでコンパイルエラー
int b = 1;
Val(b);  //OK
Val(100); //直に定数渡すのもOK

呼ばれるメソッド側では引数に値を代入することはできなります。
これによって勝手に書き換えられるようなことがなくなることが保証される。

  • 値型の場合
private void Val(in int b)
{
    b = b++; //コンパイルエラー
}
  • 参照型の場合
private void Ref(in List<Hoge> list)
{
    list = new List<Hoge>(); //コンパイルエラー
    list.Add(new Hoge()); //これは問題なし
}

参照型の場合は、参照型の変数の値=実体の参照を変えることができない、
つまり新しい参照を代入する=newすることはできません。
これによって、今までrefで書くことができていた以下のような処理を防ぐことができる。

以下のソースはrefで渡された参照型変数にnewすることが呼び出されたメソッド側でできてしまい、
呼び出し側のメソッドが知らないうちに参照が書き換えることができる状態だった。
inを使えばnewされることがないのでこれを防ぐことができる。

private void Ref(ref List<Hoge> list)
{
    list = new List<Hoge>();
}

inパラメータがもっている参照が示す参照先のオブジェクトを操ることはできるので、

C#7を使うには

unityでC#7を使うには、

Edit > ProjectSettings > Player > Scripting Runtime Version

.NET 4.x Equivalentにすることで使えるようになる。

f:id:snoopopo:20190324224630p:plain

【C#】「C#は参照型でも値(プリミティブ)型でも値渡し」

はじめに

・「参照型渡すときにrefパラメータ修飾子つける意味あんの?」

・「参照型でも値(プリミティブ)型でも値渡し」→「??」

C#のパラメータ修飾子について学んでいるときに、
上記の疑問がわいたので調べた内容をまとめた記事です。



これ↓って参照渡しじゃないよ。

void Test()
{
    List<Hoge> a = new List<Hoge>();
    Val(a);
    Debug.Log("count = " + a.Count);
}

private void Val(List<Hoge> b)
{
    b.Add(new Hoge());
}

▼実行結果

count = 1

上記のソースで「countが増えてる=参照渡しだ」と判断していたのですが、
参照渡しではなく値渡しです。

参照型の値渡し

上記のソースでは、
・参照型変数aはListの参照先を保持した変数です。
・参照型変数aを値渡しします。
値渡しをするということは、参照型変数aの値=Listの参照 をコピーします
コピーされた変数が参照型変数bです。
Val()の中で、参照型変数bの値=Listの参照 を元にListにAddします。Listのカウント数は1になりました。
Test()の中で、参照型変数aの値=Listの参照 を元にListのカウント数をデバッグ出力します。

ということが行われています。

参照型変数abが持つ参照の参照先のListは同じなのですが、それぞれで参照をもっているということです。

以下のようなイメージです。

f:id:snoopopo:20190317095633p:plain

参照型の「参照渡し」

一方、refを用いて「参照渡し」した場合は、
同じ参照を参照型変数abも持つことになります。

以下のようなイメージです。

f:id:snoopopo:20190317100231p:plain

結果が異なるパターン

先程のソースでは実行結果が同じだったので、実行結果が異なる例を挙げます。

void Test()
{
    List<Hoge> a = new List<Hoge>();
    Ref(ref a);
    //Val(a); RefかValのどちらか呼ぶ
    Debug.Log("count = " + a.Count);
}

private void Val(List<Hoge> b)
{
    b = new List<Hoge>();
    b.Add(new Hoge());
}

private void Ref(ref List<Hoge> b)
{
    b = new List<Hoge>();
    b.Add(new Hoge());
}

Ref()(参照渡し)を呼んだときの実行結果

count = 1

Val()(値渡し)を呼んだときの実行結果

count = 0

このように結果が異なります。


先程、値渡しをした場合はコピーされ、
変数a,bそれぞれで参照をもっていると書きました。

‘Val()‘ の中で変数bが持っている参照の参照先が新しくnewされたListに変わります。
変数aの参照先は変わりません。
Test()では変数aの参照先をデバッグ出力しているので、カウント数は0のままになります。

以下のようなイメージです。

f:id:snoopopo:20190317101426j:plain


次に参照渡しの場合です。
参照渡しの場合は変数abがもっている参照は同一です。

‘Val()‘ の中で変数bが持っている参照の参照先が新しくnewされたListに変わります。
変数aと変数bが持っている参照は同一なので、変数aの参照先も新しくnewされたListに変わります。

以下のようなイメージです。

f:id:snoopopo:20190317102337j:plain

最後に

このように、参照型の値渡しと参照渡しとでは動きが異なります。

最初に疑問に思ったことの回答・・・

「参照型渡すときにrefパラメータ修飾子つける意味あんの?」

→ある。無駄なコピーはなくせるのでrefはつけた方がいい。

だけど無駄なコピーなくすってメリットぐらいしか思いつかん。。(※)
ほかにメリットある??

(※)今回例であげたような、
呼び出し先のメソッドでnew されるのが前提のソースの場合は、
呼び出し元でnewせずパラメータ修飾子もrefじゃなくてoutでもらうべきだと思うからです。

参考

参考にさせてもらいました、あざしたー★!

teratail.com

sorasorasora.hatenablog.com

UnityのDotweenよく使うものまとめ

ドキュメント

dotween.demigiant.com

namespace

using DG.Tweening;

移動

(RectTransform)(gameObject.transform).DOLocalMove(new Vector3(8, 0, 0), 0.5f); //8,0,0の位置に移動

DOMoveはワールド座標での移動。DOLocalMoveはローカル座標。

絶対座標ではなく相対座標で移動したい。

(a,b,c)に移動したい、という場合ではなく、今いる位置から(d,e,f)移動したいという場合は、.SetRelativeをつけてあげれば良い。

(RectTransform)(gameObject.transform).DOLocalMove(new Vector3(8, 0, 0), 0.5f).SetRelative(); //今いる場所から右へ8移動

シーケンス

Sequence seq = DOTween.Sequence();
seq.Play();

ループ数を指定する

Sequence seq = DOTween.Sequence();
seq.SetLoops(-1); //-1なら無限ループ
seq.Play();

Append

Sequence seq = DOTween.Sequence();
seq.Append(((RectTransform)scrollArrowDown.transform).DOLocalMoveY(10, 0.5f).SetRelative());
seq.Append(((RectTransform)scrollArrowDown.transform).DOLocalMoveY(-10, 0.5f).SetRelative())
seq.Play();

AppendInterval

シーケンス中にxx秒待つ

Sequence seq = DOTween.Sequence();
sequence.AppendInterval(0.3f);
seq.Play();

AppendCallback

CallBackを設定

Sequence seq = DOTween.Sequence();
sequence.AppendCallback (()=>{
    if(onFinish != null){
        onFinish();
    }
});
seq.Play();

Fade

Image.DOFade

CrossFadeAlpha は unityの仕組みでdotweenじゃないけどおなじようなことできる

DoKill

今行っているアニメを消す。 component は uGUIの親のComponent。

component.DOKill()

onComplete

↓の例だと、スケールで小さくしてからゲームオブジェクトをDestroyするみたいな感じ

transform.DOScale(0, this.effectTime).onComplete = () =>
{
    Destroy(gameObject);
};

xx秒後にする

SetDelay を使うことで、xx秒後にtweenの処理をさせることができる。 ↓の例は、0.5秒後に0.6秒かけてサイズを200x200にする。シーケンス作るまでもない動きならこっちの方がいい。

rectTransform.DOSizeDelta(new Vector2(200, 200), 0.6f, true).SetDelay(0.5f)

To 〇〇の値をxxにする

以下は、CanvasGroupのalpha の値を0.5f秒かけて、0にする例

CanvasGroup canvasGroup = effect.gameObject.AddComponent<CanvasGroup>();
DOTween.To(() => canvasGroup.alpha, (n) => canvasGroup.alpha = n, 0, 0.5f).onComplete = () =>
{
    Destroy(effect.gameObject);
};

xxの方向に向きを変える DOLookAt

Vector3 targetPos = targetObj.transform.position; //
transform.DOLookAt(targetPos, 0.25f);

f:id:snoopopo:20210803153142g:plain
▲白と黒のキャラが、赤いキャラの方向を向きます

別のブランチのファイルを取り込みたい

複数ブランチで開発していると、別ブランチのファイルを取り込みたいときがある

merge じゃなくて、そのまま上書きたい場合です

取り込み先(ファイルをもらう側)のブランチに切り替えた状態で、

git checkout <取り込み元ブランチ名> -- <取り込みたいファイル名>

取り込み元ファイルがあるブランチとファイル名を指定すると取り込める

リモートのブランチを取得する

リモートのブランチの一覧は-aオプションをつけることで表示される。

 git branch -a

これで表示されない場合は、リモートの情報を取得していないのでfetchをする

git fetch

意図したリモートブランチが表示されたら、
checkoutしてローカルに新しいブランチを作成する

 git checkout -b test_branch origin/test_branch

-bしておくと新しくできたブランチに切り替えまで行ってくれる