48.4K Views
February 26, 19
スライド概要
2019/2/25に開催されたUnity道場 2月~シェーダを書けるプログラマになろう~の講演スライドです。
講師:安原 祐二 (ユニティ・テクノロジーズ・ジャパン合同会社)
Unityのイベント資料はこちらから:https://www.slideshare.net/UnityTechnologiesJapan/clipboards
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
シェーダを書けるプログラマになろう ユニティ・テクノロジーズ・ジャパン 安原 祐二
Part I シェーダを理解しよう
理解 とは?
理解 とは? 境界 の把握
〜〇〇を理解したい〜 それが できる できる できる できない ことは何か? できる できる できる
〜〇〇を理解したい〜 それが できない ことは何か? できない できない できる できる できる できる できる できる できない できる できる できない できない できる できない できる できる できる できない できない できない できない
こんなことにも使えるよ! ・・・という話はまたこんど
道具の特徴を抑えて 持ち手 接合部 刃 紙が切れる理由を知ろう
Part I シェーダを理解しよう
8ステップで描画する
ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 ステップ4 表裏を調べ、裏なら描画しない ステップ5 描画点を確定 ステップ6 描画点をデプスバッファと比較 ステップ7 描画点に打つべき色を確定 ステップ8 点を打つ
シェーダ とは 頂点シェーダ フラグメントシェーダ GPU搭載の基本シェーダはこのふたつ
描画 とは モニタ上の画素に色付きの 点 を打つこと
ステップ1
ステップ1 3Dモデルを準備
ステップ2
Transformの値を4x4行列に変換 Rotation & Scale 固定 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 モデル行列という Position ステップ2
ステップ3
ステップ3 頂点ごとに描画位置を算出 モデル行列 1 0 0 0 カメラ情報 0 1 0 0 0 0 1 0 0 0 0 1
ステップ4
ステップ4 表裏を調べ、裏なら描画しない 3点そろったら
ステップ5
ステップ5 描画点を確定 ピクセルの中心が三角形の内側にあるかどうか
ステップ6
ステップ6 描画点を深度バッファと比較 描画済みの点が手前にあるなら描画しない 深度バッファ ※GPUごとに実装が異なります
ステップ7
ステップ7 描画点に打つべき色を確定 テクスチャ、ライティング、シャドウ、フォグなどなど考慮
ステップ8
ステップ8 点を打つ ブレンド関数を指定可 深度バッファも更新
ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 ステップ4 表裏を調べ、裏なら描画しない ステップ5 描画点を確定 ステップ6 描画点をデプスバッファと比較 ステップ7 描画点に打つべき色を確定 ステップ8 点を打つ
CPU GPU ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 ステップ4 表裏を調べ、裏なら描画しない ステップ5 描画点を確定 ステップ6 描画点をデプスバッファと比較 ステップ7 描画点に打つべき色を確定 ステップ8 点を打つ
CPU GPU ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 頂点シェーダ ステップ4 表裏を調べ、裏なら描画しない ステップ5 描画点を確定 ステップ6 描画点をデプスバッファと比較 ステップ7 フラグメント 描画点に打つべき色を確定 シェーダ ステップ8 点を打つ
シェーダで 対応できない例
線を引きたい
CPU GPU ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 頂点シェーダ ステップ4 表裏を調べ、裏なら描画しない ステップ6 描画点をデプスバッファと比較 ステップ7 フラグメント 描画点に打つべき色を確定 シェーダ ステップ8 点を打つ ステップ1で対応が必要 ステップ5 描画点を確定
3Dモデル(Mesh)の構造
・頂点列 頂点座標 頂点座標 頂点座標 頂点座標 法線 法線 法線 法線 UV UV UV UV 0 1 ・三角形列 0 2 4 6 5 7 1 3 1 1 5 5 0 0 4 4 2 3 6 7 7 2 3 6 線を引くには線分用のデータが必要 2 3
シェーダで 対応できない例
半透明描画をしたい 窓ガラス
CPU GPU ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 頂点シェーダ ステップ4 表裏を調べ、裏なら描画しない ステップ6 描画点をデプスバッファと比較 ステップ7 フラグメント 描画点に打つべき色を確定 シェーダ ステップ8 点を打つ ステップ8で対応が必要 ステップ5 描画点を確定
半透明に必要な計算例 + 2 =
計算にはすでに打ってある色が必要 フラグメント シェーダの出力 + 2 = 打つべき色
半透明はBlend で設定可能 Shader "Custom/minimum" { SubShader { Tags { "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" … プログラマブルではない 演算は書けない ステップ8に対する設定 ピクセルごとに変えられない
余談 半透明&深度バッファ問題
不透明を半透明の後に描画する困難
不透明を半透明の後に描画する困難 透明部の深度バッファを更新
不透明を半透明の後に描画する困難 透明部の深度バッファを更新 透明部の深度バッファを 更新しない 現代における対策:半透明は全ての不透明のあとに描画
改めて シェーダコードを観察
最小のシェーダ
Shader "Custom/minimum" {
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
}
}
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(1,0,0,1);
}
ENDCG
最小のシェーダ
Shader "Custom/minimum" {
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
}
}
}
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
ほぼ一般的な
シェーダコード
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
頂点シェーダ
fixed4 frag(v2f i) : SV_Target {
return fixed4(1,0,0,1);
}
ENDCG
フラグメント
シェーダ
最小のシェーダ
Shader "Custom/minimum" {
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
Unityが用意している
便利な記述
様々な設定が可能
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
}
}
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(1,0,0,1);
}
ENDCG
超重要ポイントふたつ セマンティクス 補間
最小のシェーダ
Shader "Custom/minimum" {
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
セマンティクス付き構造体
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
}
}
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(1,0,0,1);
}
ENDCG
頂点シェーダ
フラグメント
シェーダ
セマンティクス struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; 変数の内容の 意味 GPUに扱い方を伝える
補間 ここが96なら ここが28で このへんは71.3 途中の値を 算出 する GPUががんばる
テクスチャマッピングの例
テクスチャマッピングのシェーダ uvを渡す テクセルを拾う v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); }
uvはテクスチャ座標 uvはテクスチャ座標 uvを渡す テクセルを拾う struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); }
頂点シェーダの出力 v2f v2f v2f
頂点シェーダの出力 補間 v2f v2f v2f フラグメントシェーダ の入力 v2f
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); }
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } 補間されたUV座標で テクスチャから色を得る fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); }
ところで サーフェスシェーダ(surface shader) ってなんなの?
頂点シェーダ v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } フラグメントシェーダ fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); }
頂点シェーダ v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } フラグメントシェーダ fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } たいてい 同じことを書く 省略しても 生成してくれる
頂点シェーダ v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } フラグメントシェーダ fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } たいてい 同じことを書く たいてい フクザツになる 省略しても 生成してくれる 楽な記述で 生成してくれる
サーフェスシェーダ(surface shader)は 頂点シェーダ v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } フラグメントシェーダ fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } たいてい 同じことを書く たいてい フクザツになる Unityからのプレゼント 省略しても 生成してくれる 楽な記述で 生成してくれる
CPU GPU ステップ1 3Dモデルを準備 ステップ2 Transformの値を4x4行列に変換 ステップ3 頂点ごとに描画位置を算出 頂点シェーダ ステップ4 表裏を調べ、裏なら描画しない 設定変更可 ステップ5 描画点を確定 ステップ6 描画点をデプスバッファと比較 設定変更可 ステップ7 フラグメント 描画点に打つべき色を確定 シェーダ ステップ8 点を打つ 設定変更可
休憩
Part II GPUの神秘
tex2Dでテクセルをゲット fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } tex2DはGPUに直結 ここにシェーダの神秘がある
一般論 テクスチャは縮小に課題あり
品質の低下 ちらつき
ORIGINAL NEAREST
高度な縮小は時間がかかる ORIGINAL KAISER NEAREST BICUBIC BOX HAMMING
高度な縮小はシェーダでは使えない tex2D(_MainTex, i.uv) UVが大きく動くと テクセルを飛ばしてしまう
無限平面を作ろう
作戦 カメラの視界に必ず入るように平面を作る 頂点シェーダで計算
1.0の四角形を準備する 1.0
カメラの座標 カメラの前方ベクトル 1.0 vertexシェーダで 頂点を拡大・移動
シェーダまめ知識 カメラの前方ベクトル float3 forward = -UNITY_MATRIX_V._m20_m21_m22; ビュー行列
uvにワールド座標を入れちゃう
テクスチャ座標 (0, 1) (1, 1) (0, 0) (1, 0) 画像のサイズに関わらず テクスチャ座標は [0, 1]
テクスチャ座標が[0, 1]の範囲外の場合 (0, 1) (0, 2) (1, 1) ? 2.0 (2, 2) ? ? (0, 0) (1, 0) (0, 0) (2, 0)
(0, 2) tex2D(1.5, 1.5) は何を返すのか? ? 2.0 (2, 2) ? ? (0, 0) (2, 0)
(0, 2) tex2D(1.5, 1.5) は何を返すのか? ? 2.0 (2, 2) ? ? tex2D(0.5, 0.5) の値を返す (0, 0) (2, 0)
テクスチャ座標が[0, 1]の範囲外の場合 (0, 1) (0, 2) (1, 1) (2, 2) 2.0 (0, 0) (1, 0) (0, 0) (2, 0)
[0.0, 1.0]範囲外のuvの扱い Repeat テクスチャのインポート設定で tex2Dの動作を変えられる Clamp
uvにワールド座標(x, z)を入れる (235, 1021) (-728, 1041) (346, 124) (-549, 103)
キレイに縮小されている不思議 ミップマップ(mipmap)の効果
テクスチャインポート時に 高度な縮小を作成 mipmap生成 時間がかかっても良い
縮小済みのテクスチャが使用される
ミップマップあり ミップマップなし
ミップマップあり ミップマップなし
mipmap生成を 指定するだけ シェーダはtex2Dを 呼んでいるだけ それでも mipmapレベルが選ばれる・・・
mipmap生成を 指定するだけ シェーダはtex2Dを 呼んでいるだけ それでも mipmapレベルが選ばれる・・・ 疑問 tex2Dはどうやって mipmapレベルを 選択しているのか?
それはさておき
繰り返しが気になる
単位範囲ごとに uv座標をずらしてみる
uvをずらす
コード例
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
※hash4fastは別に定義
不連続が気になるかどうかは テクスチャ次第 (意外と大丈夫だった)
やや不連続が気になる テクスチャ
チラつきが発生する いよいよシェーダの神秘へ
チラつきの原因をわかりやすいテクスチャで比較
チラつきの原因をわかりやすいテクスチャで比較 間違ったmipmap 正しいmipmap
ここで衝撃の事実 フラグメントシェーダは必ず4ピクセル同時に実行している
ここで衝撃の事実 フラグメントシェーダは必ず4ピクセル同時に実行している 実行単位の4ピクセル
本当に同時に実行される
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
隣の3つも
同じ場所を実行
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
tex2Dは
隣の3つの情報も使う!
tex2D(_MainTex, uv) uv uv uv uv GPUは異なる4つのuv値を入手している
tex2D(_MainTex, uv) uv uv uv uv GPUは異なる4つのuv値を入手している 隣接ピクセルのuv値との差分を取れる uv値の飛び具合がわかる!
再掲 mipmap生成を 指定するだけ・・・ シェーダはtex2Dを 呼んでいるだけ・・・ それでも mipmapレベルが選ばれる・・・ 疑問 tex2Dはどうやって mipmapレベルを 選択しているのか?
tex2D(_MainTex, uv) uv uv uv uv GPUは異なる4つのuv値を入手している 隣で実行中の値を使って適切なmipmapを選ぶ
チラつきが発生する uvを操作したので 適切なmipmapが 選ばれていない問題
対処:適切なuvを指定
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
float2 dx = ddx(i.uv) * off.zw;
float2 dy = ddy(i.uv) * off.zw;
fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ddx, ddy で適切な傾きを得て tex2Dgradで指定
完成! チラつきが消滅
ddx, ddy の神秘
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
float2 dx = ddx(i.uv) * off.zw;
float2 dy = ddy(i.uv) * off.zw;
fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ddx, ddy の神秘
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
float2 dx = ddx(i.uv) * off.zw;
float2 dy = ddy(i.uv) * off.zw;
fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ddx, ddy の神秘
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
float2 dx = ddx(i.uv) * off.zw;横の隣との差
float2 dy = ddy(i.uv) * off.zw;
fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ddx, ddy の神秘
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = off.zw >= float2(0.5, 0.5) ?
float2(1, 1) : float2(-1, -1);
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
float2 dx = ddx(i.uv) * off.zw;
float2 dy = ddy(i.uv) * off.zw;縦の隣との差
fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fwidth(v)=abs(ddx(v)) + abs(ddy(v)) fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return fwidth(col); }
fwidth(v)=abs(ddx(v)) + abs(ddy(v)) fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return lerp(col, fixed4(0,0,0,1), fwidth(col.a)); }
GPUを扱うときの心構え
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
fixed4 frag(v2f i) : SV_Target
{
float4 off = hash4fast(floor(i.uv));
off.zw = ((step(0.5, off.zw)) - 0.5) * 2;
float2 fuv = frac(i.uv);
float2 uv = fuv * off.zw + off.xy;
fixed4 col = tex2D(_MainTex, uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
同時に実行されることに想いを馳せる
シェーダはbranch(ようするにif文)が苦手
fixed4 frag(v2f i) : SV_Target
{
float4 col;
if (i.uv.x < 0.5)
col = tex2D(_MainTex, i.uv);
else
col = tex2D(_MainTex, -i.uv);
return col;
}
同時実行することを思えば残酷な記述
休憩
float3 forward = -UNITY_MATRIX_V._m20_m21_m22; カメラの前方ベクトル ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
1 a b d −b 1 0 a b −1= = M I= M= ( ) −c a (c d) ad − bc (0 1) (c d) −1 単位行列 I 行列 M 行列 M の逆行列 M −1
おしまい
詳しく解説したブログ記事 [Unity]無限平面を描画する過程でGPUの理解を深める https://qiita.com/yuji_yasuhara/private/158bab01cfff3c39214b [Unity] CGに使用される行列についての考察 https://qiita.com/yuji_yasuhara/private/8d63455d1d277af4c270
ボツスライド集
プログラマブルシェーダで出来ないこと • アルファブレンディング • 点・線の描画 • 頂点の生成(ジオメトリシェーダ、テセレーション) • 描画順の指定 • FrustomCulling抑制 • テクスチャサンプリング方式の変更 • トポロジ(三角形の構成)変更
深度バッファで比較するということは 奥の物体は 手前の物体に 塗りつぶされる 手前のものを 先に描画したい
コックピットは最初に描画して効率UP ※GPUごとに特性が異なります 描画順はRender Queue で指定
ところで ・頂点列 頂点座標 頂点座標 頂点座標 頂点座標 法線 法線 法線 法線 UV UV UV UV 0 1 ・三角形列 0 2 4 6 5 7 1 3 1 1 5 5 0 0 4 4 2 3 6 7 7 2 3 6 この立方体の頂点数はいくつ? 2 3
appdata に頂点情報 struct appdata { float4 vertex : POSITION; float4 normal: NORMAL; float4 uv: TEXCOORD; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } 法線が異なれば頂点も別に必要
・頂点列 頂点座標 頂点座標 頂点座標 頂点座標 法線 法線 法線 法線 UV UV UV UV 0 1 ・三角形列 0 2 4 6 5 7 1 3 1 1 5 5 0 0 4 4 2 3 6 7 7 2 3 6 ハードエッジは法線が分かれるので24頂点 2 3
ただの補間ではない 単なる二次元補間 奥行が考慮された補間
頂点シェーダのよくあるミス
モデル行列の各部の意味 Rotation & Scale 固定 1 0 0 0 b 0 1 0 0 0 0 1 0 a b c 1 Position a c
v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } UnityObjectToClipPosの中身: inline float4 UnityObjectToClipPos(in float3 pos) { return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0))); } これを自分で書いても良いが、この1.0を0.0にしたらアウト
掛けるベクトルのw要素による結果の違い 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 a b c 1 px + a px py py + b = pz pz + c 1 1 a b c 1 px px py py = pz pz 0 0 Positionが加算される 頂点ベクトルはw=1で計算 Positionが加算されない 法線ベクトルはw=0で計算
フラグメントシェーダのよくあるミス
法線を渡すときは注意 v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag(v2f i) : SV_Target { float3 n = normalize(i.normal); … 線形補間なので 短くなってしまう normalizeが必要
point sampling bilinear trilinear
Kaiser Box ミップマップ作成アリゴリズム比較
おしまい