前回投稿の「Unityシェーダについて考える」の続編です。
さて前回に書いた、たった数行のコードからUnityはどれぐらいの量のコードを自動生成するでしょう?Arasさんのブログから再び翻訳します:
(シェーダ周りは専門家でないので多少の誤訳はご勘弁ください)
http://aras-p.info/blog/page/4/
2010-07-16
自動生成されるコード
Unityのsurface shaderのコードマクロはその後、実際の頂点シェーダ、ピクセルシェーダを生成して、それぞれのプラットフォーム向けにコンパイルする。Unity3.0のデフォルト設定で対応している内容としては:
- フォワードレンダラーおよびデファードライティングレンダラー(Light Pre-Pass)
- 事前計算ライトマップのオブジェクト
- 各種ライト(Directional, Point, Spot Light)対応。シャドウマップ対応、ただしフォワードレンダラーのみであり、これはLight Pre-passでは別処理であることが理由。
- フォワードレンダラーではVertex LitおよびSphere Harmonicsライトがオブジェクトごとにコンパイルされる。さらに追加のブレンドパスを追加のピクセルごとのライトが異なるパスでレンダリングされる必要がある場合に対応
- Light Pre-Passレンダラーについて、ベースのパスを生成してNormalや鏡面反射を出力して、最終パスとしてAlbedoをライティングと組み合わせ、ライトマップや自己発光ライティングを追加する
- オプションとして影のレンダリングパスを生成する(頂点編集のカスタマイズ(Vertex Modifier)が必要なケース、たとえば頂点シェーダのアニメーションとして活用されたり、あるいは複雑な透明度効果が含まれる場合など)
実際の例として、フォワードレンダリングのベースパスとしてひとつのDirectional Light、4つの頂点Point Light、3rd order SH Light、追加のライトマップで生成を設定したケースで、どれだけのコードが自動生成されるのかご覧いただこう:
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase
#include "HLSLSupport.cginc"
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct Input {
float2 uv_MainTex : TEXCOORD0;
};
sampler2D _MainTex;
sampler2D _BumpMap;
void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_MainTex));
}
struct v2f_surf {
V2F_POS_FOG;
float2 hip_pack0 : TEXCOORD0;
#ifndef LIGHTMAP_OFF
float2 hip_lmap : TEXCOORD1;
#else
float3 lightDir : TEXCOORD1;
float3 vlight : TEXCOORD2;
#endif
LIGHTING_COORDS(3,4)
};
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
#endif
float4 _MainTex_ST;
v2f_surf vert_surf (appdata_full v) {
v2f_surf o;
PositionFog( v.vertex, o.pos, o.fog );
o.hip_pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
#ifndef LIGHTMAP_OFF
o.hip_lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#endif
float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
TANGENT_SPACE_ROTATION;
#ifdef LIGHTMAP_OFF
o.lightDir = mul (rotation, ObjSpaceLightDir(v.vertex));
#endif
#ifdef LIGHTMAP_OFF
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight = shlight;
#ifdef VERTEXLIGHT_ON
float3 worldPos = mul(_Object2World, v.vertex).xyz;
o.vlight += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor0, unity_LightColor1, unity_LightColor2, unity_LightColor3,
unity_4LightAtten0, worldPos, worldN );
#endif // VERTEXLIGHT_ON
#endif // LIGHTMAP_OFF
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap;
#endif
half4 frag_surf (v2f_surf IN) : COLOR {
Input surfIN;
surfIN.uv_MainTex = IN.hip_pack0.xy;
SurfaceOutput o;
o.Albedo = 0.0;
o.Emission = 0.0;
o.Specular = 0.0;
o.Alpha = 0.0;
o.Gloss = 0.0;
surf (surfIN, o);
half atten = LIGHT_ATTENUATION(IN);
half4 c;
#ifdef LIGHTMAP_OFF
c = LightingLambert (o, IN.lightDir, atten);
c.rgb += o.Albedo * IN.vlight;
#else // LIGHTMAP_OFF
half3 lmFull = DecodeLightmap (tex2D(unity_Lightmap, IN.hip_lmap.xy));
#ifdef SHADOWS_SCREEN
c.rgb = o.Albedo * min(lmFull, atten*2);
#else
c.rgb = o.Albedo * lmFull;
#endif
c.a = o.Alpha;
#endif // LIGHTMAP_OFF
return c;
}
実に90行のコード!
このうち、10行は自身のsurface shaderのコードであり、残りの80行はUnity 2.xの際は手書きが必要であったものである。(まあ、そもそも2.xはここまでライティング対応できてなかったから行数もここまでなかっただろうが)。さらに言ってしまうと、これはフォワードレンダラーのベースパスだけなのである。さらに追加のパスのコードも生成するし、デファードのベースパスも生成するし、デファードの最終パスも生成するし、影のパスも生成するし、まだまだ処理はあるのだ。
このうち、10行は自身のsurface shaderのコードであり、残りの80行はUnity 2.xの際は手書きが必要であったものである。(まあ、そもそも2.xはここまでライティング対応できてなかったから行数もここまでなかっただろうが)。さらに言ってしまうと、これはフォワードレンダラーのベースパスだけなのである。さらに追加のパスのコードも生成するし、デファードのベースパスも生成するし、デファードの最終パスも生成するし、影のパスも生成するし、まだまだ処理はあるのだ。
要するに、シェーダはより簡潔に書かれるべきなのである(少なくとも私にとって)。これによりshaderを書くUnityユーザが3倍ぐらいに増えてほしいものである。(今は10人だから30人か!)ライティングに関する変更予定のパイプラインが次のUnityバージョンで予定されているが、互換性がより高まるだろう。
----------
おー。コードは8倍に膨れ上がるのですね。これを自動生成とはさすがUnity!
しかしAras氏のブログは色々と気持ちがこもっているので、難しい用語は少し読み飛ばしながら読んでもなかなか読み応えがある内容ですね。学習に生かしたいものです。
世界でも数少ないシェーダをマスターしているプログラマを目指そう!
0 件のコメント:
コメントを投稿