このセクションでは,最適化のレンダリングを技術的に紹介します。パフォーマンス向上のためのライティング結果を焼く方法とShadowgunの開発者が彼らのゲームを素晴らしいものに見えるようにするライティングをベイクして高コントラストのテクスチャをレバレッジする方法を示します。モバイルに最適化されたゲームがどのように見えるか,一般的な情報を探している場合は,グラフィック方法ページをチェックしてください。
時には,あなたのゲーム内のレンダリングを最適化することは,いくつかの割に合わない仕事を必要とします。Unityが提供するストラクチャーのすべては何かを速く動作させるのが容易になりますが,限られたハードウェア上で忠実に動作する必要がある場合,あなたが,自分自身でもっとより速くする鍵となるストラクチャーの変化を導入することができ,ストラクチャーを回避して行く方法でより高速になものになります。 選択するあなたのツールは,エディターのスクリプト,シンプルなシェーダーと昔ながらのアート制作です。
Unityインディーユーザーのための注意: ここで参照するエディタースクリプトは,スムーズな生産を行うためにRenderTexturesを使用し,それらはすぐに動作します。しかし,その背後にある原則は同様にスクリーンショットと連携し,あなたがそれらの技術を使用していくつかのテクスチャのために独自にベイクするのを止めません。
まず最初に,このシェーダが書かれている方法の紹介をチェックしてください。
#pragma debug
)#pragma DEBUG
を追加した場合は,インスペクタ経由でコンパイルされたシェーダを開くと,中間CGコードを見ることができます。これは,シェーダの特定の部分が実際にはどのように計算されるか,検査するのに便利で,また,あなたがサーフェスシェーダから欲しい特定の側面をつかむためやCGシェーダに適用するのに使えます。Shadowgunは,それを実行するハードウェアを考慮した素晴らしいグラフィカルな成果です。アートの質はパズルの鍵であると言われますが,そのような品質を達成するために,プログラマは彼らのアーティストの可能性を最大限引き出させるカップルのトリックがあります。
In the Graphics Methods page we used the golden statue in Shadowgun as an example of a great optimization; instead of using a normal map to give their statue some solid definition, they just baked lighting detail into the texture. Here, we will show you how and why you should use a similar technique in your own game.
// This is the pixel shader code for drawing normal-mapped
// specular highlights on static lightmapped geometry
// 5 texture reads, lots of instructions
SurfaceOutput o;
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
fixed4 c = tex * _Color;
o.Albedo = c.rgb;
o.Gloss = tex.a;
o.Specular = _Shininess;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
float3 worldRefl = WorldReflectionVector (IN, o.Normal);
fixed4 reflcol = texCUBE (_Cube, worldRefl);
reflcol *= tex.a;
o.Emission = reflcol.rgb * _ReflectColor.rgb;
o.Alpha = reflcol.a * _ReflectColor.a;
fixed atten = LIGHT_ATTENUATION(IN);
fixed4 c = 0;
half3 specColor;
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
const float3x3 unity_DirBasis = float3x3(
float3( 0.81649658, 0.0, 0.57735028),
float3(-0.40824830, 0.70710679, 0.57735027),
float3(-0.40824829, -0.70710678, 0.57735026) );
half3 lm = DecodeLightmap (lmtex);
half3 scalePerBasisVector = DecodeLightmap (lmIndTex);
half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal));
lm *= dot (normalInRnmBasis, scalePerBasisVector);
return half4(lm, 1);
// This is the pixel shader code for lighting which is
// baked into the texture
// 2 texture reads, very few instructions
fixed4 c = tex2D (_MainTex, i.uv.xy);
c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a;
return c;
The real-time light is definitely higher quality, but the performance gain from the baked version is massive. So how was this done? An editor tool called Render to Texel was created for this purpose. Note that you will need to be running Unity PRO in order to use this tool. It bakes the light into the texture through the following process:
This is how the best graphics optimizations work. They sidestep tons of calculations by preforming them in the editor or before the game runs. In general, this is what you want to do:
ちょうど,オーディオトラックの低音と高音のように,画像はまた,高周波と低周波成分を有し,レンダリングするとき,さまざまな方法で処理するのが最善で,完全なステレオサウンドを作成するためにサブウーファーとツイーターを使用する方法に似ています。画像の異なる周波数を可視化する1つの方法は,Photoshopで“ハイパス”」"フィルタを使用することです。 Filters->Other->High Pass. あなたが以前にオーディオの作業を行っている場合は,あなたはハイパスの名前(意味)がわかるでしょう。本質的に,全ての周波数のうち,Xよりも低いと遮断され,通過させるパラメータをフィルタに渡します。画像の場合,ガウスぼかし,低域を通過するのと同じです。
周波数は物事を分離することと,何か処理方法を決定する良い方法であるため,リアルタイムグラフィックスに適用されます。例えば,基本的なライトマップ環境では,最終画像は低い周波数であるライトマップと高周波であるテクスチャの合成によって得られます。Shadowgunでは,低周波数の光は,迅速に光プローブを用いて文字に適用され,高周波光は,任意の光の方向を持つ単純なバンプマップシェーダを使用することによって疑似されています。
一般に,光の異なる周波数をレンダリングするために異なる方法を使用することで,例えば,ベイクか動的か,オブジェクト毎かレベル毎か,ピクセル毎かの頂点毎か,など,あなたは限られたハードウェア上でフルボディのイメージを作成することができます。要約すると,一般的に高周波と低周波の両方で強力なバリエーションの色または値を持つようにすることをお勧めします。
注意: 通常,これらの分解は,繰延レンダラでのステップを参照しますが,ここではそうではありません。この全ては一回のパスで行われます。これらこの合成に基づいた2つの関連シェーダです。:
Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" {
Properties {
_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
//_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0
_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
_SpecRange ("Specular Range", Float) = 20
_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
_ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0)
}
SubShader {
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
LOD 100
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
samplerCUBE _ReflTex;
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
sampler2D unity_Lightmap;
#endif
//float _MainTexMipBias;
float3 _SpecOffset;
float _SpecRange;
float3 _SpecColor;
float _Shininess;
float4 _ScrollingSpeed;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
#ifndef LIGHTMAP_OFF
float2 lmap : TEXCOORD1;
#endif
fixed3 spec : TEXCOORD2;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y);
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;
#ifndef LIGHTMAP_OFF
o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#endif
return o;
}
ENDCG
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
fixed4 frag (v2f i) : COLOR
{
fixed4 c = tex2D (_MainTex, i.uv);
fixed3 spec = i.spec.rgb * c.a;
#if 1
c.rgb += spec;
#else
c.rgb = c.rgb + spec - c.rgb * spec;
#endif
#ifndef LIGHTMAP_OFF
fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap));
c.rgb *= lm;
#endif
return c;
}
ENDCG
}
}
}
Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" {
Properties {
_MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
_SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
_SpecRange ("Specular Range", Float) = 20
_SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
_Shininess ("Shininess", Range (0.01, 1)) = 0.078125
_SHLightingScale("LightProbe influence scale",float) = 1
}
SubShader {
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
LOD 100
CGINCLUDE
#pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float3 _SpecOffset;
float _SpecRange;
float3 _SpecColor;
float _Shininess;
float _SHLightingScale;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 refl : TEXCOORD1;
fixed3 spec : TEXCOORD3;
fixed3 SHLighting: TEXCOORD4;
};
v2f 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;
}
ENDCG
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
fixed4 frag (v2f i) : COLOR
{
fixed4 c = tex2D (_MainTex, i.uv);
c.rgb *= i.SHLighting;
c.rgb += i.spec.rgb * c.a;
return c;
}
ENDCG
}
}
}
いくつかのGPU,特にモバイル機器で,アルファテストのため高いパフォーマンスのオーバーヘッドの発生を見つけます。( __discard__and__clip__ピクセルシェーダでの操作のまたは使用で)。あなたは可能であればアルファブレンドと一緒にアルファテストシェーダを交換する必要があります。アルファテストを避けることができない場合,最小限のアルファテストされた画素の総数を維持する必要があります。
いくつかの画像,特に iOS/AndroidのPVRテクスチャ圧縮を使用する場合,アルファチャンネルにおける視覚アーティファクトになる傾向があります。このような場合には,あなたのイメージングソフトウェアで直接PVRTの圧縮パラメータを調整する必要がある場合があります。あなたは,その__PVR export plugin__をインストールしたり,イマジネーションテックから,PVRTC形式のクリエイター[PVRTexTool](http://www.imgtec.com/powervr/insider/powervr-pvrtextool.asp)を使用して行うことができます。__.pvr__拡張子を持つ圧縮された結果の画像ファイルを直接Unityシステムエディターでインポートし,指定された圧縮パラメータは保持されます。PVRT-圧縮テクスチャが十分に良い画質を与えないか(GUIテクスチャのためかもしれませんが)あなたが特に鮮明な画像が必要な場合,あなたは,16ビットのテクスチャの代わりに,32ビットの使用を検討する必要があります。それによって,メモリ帯域幅および必要なストレージを半分に低減します。
OpenGL ES 2.0をサポートしているすべてのAndroidデバイスはETC1 圧縮フォーマットをサポート;したがって,可能な限り,好ましいテクスチャフォーマットとしてETC1の使用を奨励します。
NVIDIA TegraやクアルコムのSnapdragonなど,特定のグラフィックス·アーキテクチャをターゲットにする場合,それらのアーキテクチャで利用可能な独自の圧縮形式の使用を検討する価値はあるかもしれません。Androidマーケットはまた,サポートされるテクスチャ圧縮フォーマットに基づいてフィルタリングでき,サポートしていないデバイス上でダウンロードを防止することができる例えば[DXT圧縮テクスチャ](class-Texture3D)と一緒に配布アーカイブ(.apk)を意味します。
ダウンロードテクセルへのレンダリング. あなたのモデルにライティングをベイクする。 Photoshopで結果にハイパスフィルタを実行します。 “モバイル/キューブマップ”シェーダを編集し,テクセルパッケージへのレンダリングに含まれ,欠落した低周波の光の詳細は,頂点光によって置換されます。