シェーダを書けるプログラマになろう
ユニティ・テクノロジーズ・ジャパン
安原 祐二
Part I
シェーダを理解しよう
理解とは?
理解とは?
境界の把握
それができないことは何か?
できる
できる
できる
できる
できる
できる
〜〇〇を理解したい〜
それができないことは何か?
できる
できる
できる
できる
できる
できる
できる
できる
できる
できる
できる
できる
できない
できない
できない
できない
できない
できない
できない
できない
できない
できない
〜〇〇を理解したい〜
こんなことにも使えるよ!
・・・という話はまたこんど
道具の特徴を抑えて
持ち手
接合部
刃
紙が切れる理由を知ろう
Part I
シェーダを理解しよう
8ステップで描画する
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
シェーダとは
GPU搭載の基本シェーダはこのふたつ
頂点シェーダ
フラグメントシェーダ
描画とは
モニタ上の画素に色付きの点を打つこと
ステップ1
ステップ1 3Dモデルを準備
ステップ2
ステップ2 Transformの値を4x4行列に変換
モデル行列という
Rotation
&
Scale
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1固定
Position
ステップ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 点を打つ
ブレンド関数を指定可 深度バッファも更新
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
CPUGPU
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
CPUGPU
頂点シェーダ
フラグメント
シェーダ
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
シェーダで
対応できない例
線を引きたい
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
CPUGPU
頂点シェーダ
フラグメント
シェーダ
ステップ1で対応が必要
3Dモデル(Mesh)の構造
・頂点列
・三角形列
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
0
1
2
0 1 2 3
2
1
3
4
5
6
6
5
7
5
0
7
7
0
2
1
4
3
3
4
6
線を引くには線分用のデータが必要
シェーダで
対応できない例
半透明描画をしたい
窓ガラス
ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
CPUGPU
頂点シェーダ
フラグメント
シェーダ
ステップ8で対応が必要
+
=
2
半透明に必要な計算例
+
=
2
計算にはすでに打ってある色が必要
フラグメント
シェーダの出力 打つべき色
半透明はBlend で設定可能
ステップ8に対する設定
Shader "Custom/minimum" {
SubShader {
Tags { "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
…
プログラマブルではない 演算は書けない
ピクセルごとに変えられない
余談
半透明&深度バッファ問題
不透明を半透明の後に描画する困難
透明部の深度バッファを更新
不透明を半透明の後に描画する困難
透明部の深度バッファを
更新しない
現代における対策:半透明は全ての不透明のあとに描画
不透明を半透明の後に描画する困難
透明部の深度バッファを更新
改めて
シェーダコードを観察
最小のシェーダ
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
}
}
}
頂点シェーダ
フラグメント
シェーダ
最小のシェーダ
Unityが用意している
便利な記述
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
}
}
}
頂点シェーダ
フラグメント
シェーダ
セマンティクス付き構造体
セマンティクス
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
変数の内容の意味
GPUに扱い方を伝える
補間
ここが28で
ここが96なら
このへんは71.3
途中の値を算出する
GPUががんばる
テクスチャマッピングの例
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を渡す
テクセルを拾う
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
uvはテクスチャ座標
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を渡す
テクセルを拾う
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);
}
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座標で
テクスチャから色を得る
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
ところで
ってなんなの?
サーフェスシェーダ(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;
}
たいてい
同じことを書く
たいてい
フクザツになる
省略しても
生成してくれる
楽な記述で
生成してくれる
頂点シェーダ
フラグメントシェーダ
Unityからのプレゼント
fixed4 frag(v2f i) : SV_Target {
return tex2D(_MainTex, i.uv);
}
3Dモデルを準備
Transformの値を4x4行列に変換
点を打つ
描画点に打つべき色を確定
描画点をデプスバッファと比較
描画点を確定
頂点ごとに描画位置を算出
表裏を調べ、裏なら描画しない
CPUGPU
頂点シェーダ
フラグメント
シェーダ
設定変更可
設定変更可
設定変更可ステップ8
ステップ7
ステップ6
ステップ5 
ステップ3 
ステップ4 
ステップ1
ステップ2
休憩
Part II
GPUの神秘
fixed4 frag(v2f i) : SV_Target {
return tex2D(_MainTex, i.uv);
}
tex2Dでテクセルをゲット
tex2DはGPUに直結
ここにシェーダの神秘がある
テクスチャは縮小に課題あり
一般論
品質の低下
ちらつき
ORIGINAL NEAREST
KAISER BOX
BICUBIC
HAMMING
ORIGINAL NEAREST
高度な縮小は時間がかかる
UVが大きく動くと
テクセルを飛ばしてしまう
tex2D(_MainTex, i.uv)
高度な縮小はシェーダでは使えない
無限平面を作ろう
カメラの視界に必ず入るように平面を作る
頂点シェーダで計算
作戦
1.0
1.0の四角形を準備する
1.0
vertexシェーダで
頂点を拡大・移動
カメラの前方ベクトル
カメラの座標
カメラの前方ベクトル
シェーダまめ知識
float3 forward = -UNITY_MATRIX_V._m20_m21_m22;
ビュー行列
uvにワールド座標を入れちゃう
(0, 1)
(0, 0)
(1, 1)
(1, 0)
テクスチャ座標
画像のサイズに関わらず
テクスチャ座標は [0, 1]
(0, 2)
(0, 0)
(2, 2)
(2, 0)
テクスチャ座標が[0, 1]の範囲外の場合
2.0
? ?
?
(0, 1)
(0, 0)
(1, 1)
(1, 0)
(0, 2)
(0, 0)
(2, 2)
(2, 0)
2.0
? ?
?
tex2D(1.5, 1.5)
は何を返すのか?
(0, 2)
(0, 0)
(2, 2)
(2, 0)
2.0
? ?
?
tex2D(1.5, 1.5)
は何を返すのか?
tex2D(0.5, 0.5)
の値を返す
(0, 1)
(0, 0)
(1, 1)
(1, 0)
(0, 2)
(0, 0)
(2, 2)
(2, 0)
テクスチャ座標が[0, 1]の範囲外の場合
2.0
Repeat
Clamp
テクスチャのインポート設定で
tex2Dの動作を変えられる
[0.0, 1.0]範囲外のuvの扱い
uvにワールド座標(x, z)を入れる
(-728, 1041)
(235, 1021)
(-549, 103)
(346, 124)
キレイに縮小されている不思議
ミップマップ(mipmap)の効果
テクスチャインポート時に
高度な縮小を作成
mipmap生成
時間がかかっても良い
縮小済みのテクスチャが使用される
ミップマップなしミップマップあり
ミップマップなしミップマップあり
mipmap生成を
指定するだけ
シェーダはtex2Dを
呼んでいるだけ
それでも
mipmapレベルが選ばれる・・・
tex2Dはどうやって
mipmapレベルを
選択しているのか?
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;
}
隣の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;
}
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)
GPUは異なる4つのuv値を入手している
uv uv
uvuv
tex2D(_MainTex, uv)
GPUは異なる4つのuv値を入手している
uv uv
uvuv
隣接ピクセルのuv値との差分を取れる
uv値の飛び具合がわかる!
tex2Dはどうやって
mipmapレベルを
選択しているのか?
mipmap生成を
指定するだけ・・・
シェーダはtex2Dを
呼んでいるだけ・・・
疑問
それでも
mipmapレベルが選ばれる・・・
再掲
tex2D(_MainTex, uv)
uv uv
uvuv
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;
}
縦の隣との差
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));
}
fwidth(v)=abs(ddx(v)) + abs(ddy(v))
https://blogs.unity3d.com/jp/2019/02/14/procedural-stochastic-texturing-in-unity/
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;
}
GPUを扱うときの心構え
シェーダは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;
? ? ? ?
? ? ? ?
? ? ? ?
? ? ? ?
M =
(
a b
c d)
I =
(
1 0
0 1)
=
(
a b
c d)
−1
=
1
ad − bc (
d −b
−c a )M−1
行列単位行列 行列 の逆行列M−1
M MI
おしまい
詳しく解説したブログ記事
https://qiita.com/yuji_yasuhara/private/158bab01cfff3c39214b
[Unity]無限平面を描画する過程でGPUの理解を深める
https://qiita.com/yuji_yasuhara/private/8d63455d1d277af4c270
[Unity] CGに使用される行列についての考察
ボツスライド集
プログラマブルシェーダで出来ないこと
• アルファブレンディング
• 点・線の描画
• 頂点の生成(ジオメトリシェーダ、テセレーション)
• 描画順の指定
• FrustomCulling抑制
• テクスチャサンプリング方式の変更
• トポロジ(三角形の構成)変更
深度バッファで比較するということは
奥の物体は
手前の物体に
塗りつぶされる
手前のものを
先に描画したい
コックピットは最初に描画して効率UP
描画順はRender Queue で指定
※GPUごとに特性が異なります
・頂点列
・三角形列
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
0
1
2
0 1 2 3
2
1
3
4
5
6
6
5
7
5
0
7
7
0
2
1
4
3
3
4
6
ところで
この立方体の頂点数はいくつ?
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;
}
appdata に頂点情報
法線が異なれば頂点も別に必要
・頂点列
・三角形列
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
頂点座標
法線
UV
0
1
2
0 1 2 3
2
1
3
4
5
6
6
5
7
5
0
7
7
0
2
1
4
3
3
4
6
ハードエッジは法線が分かれるので24頂点
ただの補間ではない
奥行が考慮された補間単なる二次元補間
頂点シェーダのよくあるミス
モデル行列の各部の意味
Rotation
&
Scale
1 0 0
0 1 0
0 0 1
0 0 0 1固定
Position
a b c
a
b
c
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
inline float4 UnityObjectToClipPos(in float3 pos)
{
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
}
UnityObjectToClipPosの中身:
これを自分で書いても良いが、この1.0を0.0にしたらアウト
1 0 0 a
0 1 0 b
0 0 1 c
0 0 0 1
px
py
pz
1
=
px + a
py + b
pz + c
1
1 0 0 a
0 1 0 b
0 0 1 c
0 0 0 1
px
py
pz
0
=
px
py
pz
0
掛けるベクトルのw要素による結果の違い
Positionが加算されない
Positionが加算される
頂点ベクトルはw=1で計算
法線ベクトルはw=0で計算
フラグメントシェーダのよくあるミス
法線を渡すときは注意
v2f vert(appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
normalizeが必要
fixed4 frag(v2f i) : SV_Target {
float3 n = normalize(i.normal);
…
線形補間なので
短くなってしまう
point sampling bilinear trilinear
Kaiser Box
ミップマップ作成アリゴリズム比較
おしまい

【Unity道場 2月】シェーダを書けるプログラマになろう