Upload
katsutoshi-makino
View
16.377
Download
2
Embed Size (px)
Citation preview
ShadowGun Sample Level から学べるモバイル最適化
株式会社 Aimingリードソフトウェアエンジニア
牧野克俊2012/06/01
自己紹介• オンラインゲームを長年作ってきま
した• 主にサーバや通信部分担当です!• クライアントや 3D は自信ないです
でもがんばって解説します!
• 基本的に多くの情報はすでに英語で Unity の Web サイトに出ています–http://blogs.unity3d.com/2012/03/23/shad
owgun-optimizing-for-mobile-sample-level/–http://blogs.unity3d.com/2011/08/18/fast-
mobile-shaders-talk-at-siggraph/
ShadowGun
概要
• データを見てみよう• シェーダから学ぶ• テクニックを解説してみる
データを見てみよう
Scene 内データ数
• GameObject : 795• メッシュ (MeshFilter) : 429–不透明 : 369–半透明 : 60
Scene 内データ数
• マテリアル : 40• シェーダ : 17• ライト : 92–Directional : 1–Point : 91
テクスチャ枚数
• 64 × 1 枚• 128 × 1 枚• 256 × 7 枚• 512 × 10 枚• 1024 × 10 枚• 2048 × 3 枚
テクスチャ用途• 64 、 128– 光の表現、 God Ray 、 BRDF LookUpTexture
• 256– 遠景(月、山)、煙、床、水面
• 512– 彫像、床、旗、空、 NormalMap
• 1024– 敵、シャトル、壁、窓
• 2048– 壁、オブジェクト用アトラス
テクスチャフォーマット• 64 、 128–RGBA 32 、 RGB 16
• 256–RGBA 32 、 RGB 16 、 Compressed
• 512 、 1024 、 2048–Compressed
ポリゴン数• ポリゴン数–毎フレーム : 20k 〜 30k 描画–敵1体 : 約 2k–Draw-call : 30 〜 50–Batched : 120 〜 150
レンダリングオブジェクト数
• 不透明 : 349
• 半透明 : 49
レンダリングオブジェクト数
• 不透明 : 349–5 種類のマテリアルで約 300–この 5 種類は同じシェーダ
• 半透明 : 49–3 種類のマテリアルで約 30
処理時間• physx: 0.3• animation: 0.3• culling 0.0• skinning: 2.6• batching: 0.7• render: 1.8• fixed-update-count: 1 .. 4• update: 0.2• fixedUpdate: 0.4• coroutines: 0.0
処理時間• physx: 0.3• animation: 0.3• culling 0.0• skinning: 2.6• batching: 0.7• render: 1.8• fixed-update-count: 1 .. 4• update: 0.2• fixedUpdate: 0.4• coroutines: 0.0
結構重い!
シェーダから学ぶ
Case 1v2f vert (appdata_full v) {
v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);o.uv = v.texcoord;float3 worldNormal = mul((float3x3)_Object2World, v.normal);float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);float3 viewDir = float3(0,0,1);float3 viewLightPos = _SpecOffset * float3(1,1,-1);float3 dirToLight = viewPos.xyz - viewLightPos;float3 h = (viewDir + normalize(-dirToLight)) * 0.5;float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;o.SHLighting = ShadeSH9(float4(worldNormal,1)) * _SHLightingScale;return o;
}
fixed4 frag (v2f i) : COLOR {fixed4 c = tex2D (_MainTex, i.uv);c.rgb *= i.SHLighting;c.rgb += i.spec.rgb * c.a;return c;
}
頂点シェーダ
フラグメントシェーダ
Case 2v2f vert (appdata_full v) {
v2f o;float3 viewPos = mul(UNITY_MATRIX_MV,v.vertex);float dist = length(viewPos);float nfadeout = saturate(dist / _FadeOutDistNear);float ffadeout = 1 - saturate(max(dist - _FadeOutDistFar,0) * 0.2);ffadeout *= ffadeout;nfadeout *= nfadeout;nfadeout *= nfadeout;nfadeout *= ffadeout;float4 vpos = v.vertex;vpos.xyz -= v.normal * saturate(1 - nfadeout) * v.color.a *
_ContractionAmount;o.uv = v.texcoord.xy;o.pos = mul(UNITY_MATRIX_MVP, vpos);o.color = nfadeout * v.color * _Multiplier;return o;
}
fixed4 frag (v2f i) : COLOR {return tex2D (_MainTex, i.uv.xy) * i.color;
}
頂点シェーダ
フラグメントシェーダ
• フラグメントシェーダが超シンプル!
• フラグメントシェーダが超シンプル!–なぜ?
• フラグメントシェーダが超シンプル!–なぜ?•計算量を減らすため•ピクセル数 >>> 頂点数
• Virtual Gloss Per-Vertex Additive (Supports Lightmap)–壁とか床–一番使用量が多い
Pass {CGPROGRAMpragma vertex vert#pragma fragment frag#pragma fragmentoption
ARB_precision_hint_faste
fixed4 frag (v2f i) : COLOR{
・・・
Pass {CGPROGRAMpragma vertex vert#pragma fragment frag#pragma fragmentoption
ARB_precision_hint_faste
fixed4 frag (v2f i) : COLOR{
・・・
精度を可能な限り落して実行時間を
短くする
fixed4 frag (v2f i) : COLOR {fixed4 c = tex2D (_MainTex, i.uv);fixed3 spec = i.spec.rgb * c.a;c.rgb += spec;fixed3 lm = DecodeLightmap(tex2D(
unity_Lightmap, i.lmap));c.rgb *= lm;return c;
}
fixed4 frag (v2f i) : COLOR {fixed4 c = tex2D (_MainTex, i.uv);fixed3 spec = i.spec.rgb * c.a;c.rgb += spec;fixed3 lm = DecodeLightmap(tex2D(
unity_Lightmap, i.lmap));c.rgb *= lm;return c;
}
明示的に精度を落とす
精度• fixed– 通常 11 bit– -2.0 〜 +2.0– 精度 1 / 256
• half– 通常 16 bit– -60000 〜 +60000– 精度おおよそ 3 桁
• float– 32 bit
精度• fixed– 通常 11 bit– -2.0 〜 +2.0– 精度 1 / 256
• half– 通常 16 bit– -60000 〜 +60000– 精度おおよそ 3 桁
• float– 32 bit
速い
遅い
精度• fixed–色、単位ベクトル
• half–その他
• float–half で精度が足りないとき
精度• こんな宣言もできます– lowp 、 mediump 、 highp
精度• こんな宣言もできます– lowp 、 mediump 、 highp
あくまでヒントでハードウェアによってどの精度が使われるか違ってくる
テクニックを学ぼう
描画順
1. 不透明2. Skybox–空、地面–月、遠景の山
3. 半透明
ライティング
• ライト–Directional: 1–Point : 91
ライティング
• ライト–Directional: 1–Point : 91
リアルタイムで計算なんか無理!
ライティング
• 静的オブジェクト→ Lightmap
• 動的オブジェクト→ LightProbe
静的オブジェクトのライティング
Lightmap+
Per Vertex Specular Maps
Per Vertex Specular Maps
1. テクスチャの α に鏡面反射強度を格納しておく
2. 頂点シェーダでスペキュラ計算3. フラグメントシェーダで上記計算結
果と強度を掛けて足す
コードv2f vert (appdata_full v) {
・・・o.spec = _SpecColor *
pow(saturate(dot(viewNormal,normalize(h))), _Shininess * 128)* 2 * atten;
・・・}
コード
half4 frag (v2f i) : COLOR {half4 c = tex2D (_MainTex, i.uv);half3 spec = i.spec.rgb * c.a;
}
動的オブジェクトのライティング
LightProbe+
BRDF
BRDF とは
Bidirectional ReflectanceDistribution Function= 双方向反射率分布関数
BRDF とは
物質の質感をあらわすパラメータで、物質に光が入射してきたときに、どのように反射するのか記述したもの
BRDF
• 事前に LookupTexture を作成– RGB にディフューズ– α にスペキュラ
• ライトとビュー方向から LookupTexture から値を取得
• アルベドと上記値を乗算
コードvoid surf (Input IN, inout MySurfaceOutput o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);o.Albedo = tex.rgb;o.Gloss = tex.a;o.Alpha = tex.a;o.Normal = tex2D(_BumpMap, IN.uv_BumpMap).rgb * 2.0 - 1.0;
}
コードixed4 LightingPseudoBRDF (MySurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten){
fixed3 halfDir = normalize (lightDir + viewDir);// N.Lfixed NdotL = dot (s.Normal, lightDir);// N.Hfixed NdotH = dot (s.Normal, halfDir);
// remap N.L from [-1..1] to [0..1]fixed biasNdotL = NdotL * 0.5 + 0.5;・・・
コード・・・
fixed4 l = tex2D (_BRDFTex, fixed2(biasNdotL, NdotH));
fixed4 c;c.rgb = s.Albedo * (l.rgb + s.Gloss * l.a) * 2;c.a = 0;
return c;} s.Gloss ( = tex.a )
GodRay
Godray
• ポストプロセスで実装されることが多い
• モバイルでポストプロセスはコストが高い
Godray
• ポストプロセスで実装されることが多い
• モバイルでポストプロセスはコストが高い–板ポリとテクスチャ–距離に応じて縮小、透明度を上げる
Godray
• ポストプロセスで実装されることが多い
• モバイルでポストプロセスはコストが高い–板ポリとテクスチャ–距離に応じて縮小、透明度を上げる• 画面いっぱいに広がると負荷が高い• それっぽく見せるため
コードv2f vert (appdata_full v) {
v2f o;float3 viewPos = mul(UNITY_MATRIX_MV,v.vertex);float dist = length(viewPos);float nfadeout = saturate(dist / _FadeOutDistNear);float ffadeout = 1 - saturate(max(dist - _FadeOutDistFar,0)
* 0.2);
ffadeout *= ffadeout;
・・・
距離に応じての減衰率を出す
コード・・・
float4 vpos = v.vertex;vpos.xyz -= v.normal * saturate(1 - nfadeout) * v.color.a
* _ContractionAmount;
o.uv = v.texcoord.xy;o.pos = mul(UNITY_MATRIX_MVP, vpos);o.color = nfadeout * v.color * _Multiplier;return o;
}
頂点を法線方向に移動
旗
旗• 頂点カラーの α に風の影響を受ける
かを格納してある• 上記と各種パラメータを使って頂点
シェーダで計算して直接頂点の位置を変更
煙
煙• 通常はパーティクルで作成
煙• 普通はパーティクルで作成• 少しでも軽くするために–テクスチャの 2 重スクロール–頂点カラーとブレンド–端っこの頂点カラーを透明にする
コードv2f vert (appdata_full v) {
v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MainTex) +
frac(float2(_ScrollX, _ScrollY) * _Time);o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_DetailTex)
+ frac(float2(_Scroll2X, _Scroll2Y) *
_Time);
・・・
テクスチャ座標移動
コード・・・
o.uv.x += sin(_Time * _SineFreqX) * _SineAmplX;o.uv.y += sin(_Time * _SineFreqY) * _SineAmplY;
o.uv.z += sin(_Time * _SineFreqX2) * _SineAmplX2;o.uv.w += sin(_Time * _SineFreqY2) * _SineAmplY2;
o.color = _MMultiplier * _Color * v.color;return o;
}
Sphere Ambient Occlusion
Sphere Ambient Occlusion
• ある点がどれだけ遮蔽されているかに応じて陰をつける
• ShadowGun ではキャラクターの影で使用している–両足、腰に球を置いてその影を投影
Sphere Ambient Occlusion
• なんか小難しい式をあれこれすると最終的に
参考資料• http
://www.iquilezles.org/www/articles/sphereao/sphereao.htm/
void surf (Input IN, inout SurfaceOutput o) {fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = tex.rgb;o.Alpha = tex.a;
half3 t = _Center.xyz - IN.worldPos.xyz;half d = 1 / (t.x * t.x + t.y * t.y + t.z * t.z);o.Gloss = (_Radius * _Radius * d);
WorldNormalVector(IN, o.Normal);}
コードfixed4 LightingSimpleLambertAO (SurfaceOutput s, fixed3 lightDir, fixed atten) {
fixed NdotL = dot (s.Normal, lightDir);fixed bl = NdotL * s.Gloss;
fixed4 c;c.rgb = s.Albedo * _LightColor0.rgb *
(NdotL * atten * 2) * (1 - bl);c.a = s.Alpha;return c;
}
光のブロック率を出す
まとめ
まとめ• StaticBatching のためにマテリアルを共通化• テクスチャは結構な量を使っても大丈夫– ただし圧縮フォーマット前提
• 静的なライティングで十分• 処理を頂点シェーダに可能な限り持ってい
く– = ピクセル単位での処理を減らす
• シェーダの計算は精度を落とす