はじめに:最初の試み
この効果を再現するために、もっとシンプルなものから始めましょう。 たとえば、オブジェクトの位置に応じてオブジェクトに異なる色を付けるシェーダーから。 これを行うには、ワールド内のレンダリングされたピクセルの位置にアクセスする必要があります。 これは、
worldPos
フィールドをUnity 5サーフェスシェーダーの
Input
構造に追加することで実現できます。
struct Input { float2 uv_MainTex; float3 worldPos; };
次に、表面関数でワールド位置のY座標を使用して、オブジェクトの色を変更できます。 これは、
SurfaceOutputStandard
構造体の
Albedo
プロパティを変更することで実現できます。
float _ConstructY; fixed4 _ConstructColor; void surf (Input IN, inout SurfaceOutputStandard o) { if (IN.worldPos.y < _ConstructY) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = ca; } else { o.Albedo = _ConstructColor.rgb; o.Alpha = _ConstructColor.a; } o.Metallic = _Metallic; o.Smoothness = _Glossiness; }
結果は、Astroneerの効果に対する最初の近似です。 主な問題は、色の部分のシェーディング表示がまだ進行中であることです。
照らされていない表面シェーダー
PBRと照明モデルに関する以前のチュートリアルでは、サーフェスシェーダー用の独自の照明モデルを作成する方法を検討しました。 点灯していないシェーダーは、外部照明や視野角に関係なく、常に同じ色を作成します。 次のように実装できます。
#pragma surface surf Unlit fullforwardshadows inline half4 LightingUnlit (SurfaceOutput s, half3 lightDir, half atten) { return _ConstructColor; }
彼の唯一の仕事は、単色の単色を返すことです。 ご覧のとおり、彼はUnity 4で使用された
SurfaceOutput
を参照しています
SurfaceOutput
およびグローバルライティングで動作する独自のライティングモデルを作成する場合は、
SurfaceOutputStandard
を入力として受け取る関数を実装する必要があります。 Unity 5では、このために次の機能が使用されます。
inline half4 LightingUnlit (SurfaceOutputStandard s, half3 lightDir, UnityGI gi) { return _ConstructColor; }
ここでの
gi
パラメーターはグローバルイルミネーションを指しますが、非点灯のシェーダーではタスクを実行しません。 このアプローチは機能しますが、大きな問題があります。 Unityでは、サーフェスシェーダーで照明機能を選択的に変更することはできません。 オブジェクトの下部に標準のランバート照明を適用することはできませんが、同時に上部を消灯することはできません。 被写体全体に単一の照明機能を割り当てることができます。 位置に応じて、オブジェクトのレンダリング方法を変更する必要があります。
ライティング関数のパラメーターを渡します
残念ながら、ライティング機能はオブジェクトの位置にアクセスできません。 この情報を提供する最も簡単な方法は、表面関数で定義するブール変数(
building
)を使用することです。 この変数は、新しいライティング関数で確認できます。
int building; void surf (Input IN, inout SurfaceOutputStandard o) { if (IN.worldPos.y < _ConstructY) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = ca; building = 0; } else { o.Albedo = _ConstructColor.rgb; o.Alpha = _ConstructColor.a; building = 1; } o.Metallic = _Metallic; o.Smoothness = _Glossiness; }
標準照明機能の拡張
私たちが直面している最後の問題は非常に複雑です。 前のセクションで説明したように、
building
を使用して照明の計算方法を変更できます。 現在作成中のオブジェクトの部分は消灯し、残りの部分は正しく計算された照明になります。 マテリアルでPBRを使用する場合、フォトリアリスティックカバレッジのすべてのコードを書き換えることはできません。 唯一の妥当な解決策は、Unityに既に実装されている標準の照明機能を呼び出すことです。
従来の標準のサーフェスシェーダーでは、PBRライティング関数の使用を定義する
#pragma
は次のようになります。
#pragma surface surf Standard fullforwardshadows
Unityの命名基準により、使用される機能は
LightingStandard
と呼ばれるべきであることが容易にわかります。 この関数は
UnityPBSLighting.cginc
ファイルにあり、必要に応じて接続できます。
LightingCustom
と呼ばれる独自の照明関数を作成します。 通常の状況では、Unityから
LightingStandard
と呼ばれる標準のPBR関数を呼び出すだけです。 ただし、必要に応じて、以前に定義された
LightingUnlit
使用します。
inline half4 LightingCustom(SurfaceOutputStandard s, half3 lightDir, UnityGI gi) { if (!building) return LightingStandard(s, lightDir, gi); // Unity5 PBR return _ConstructColor; // Unlit }
このコードをコンパイルするには、Unity 5は別の関数を定義する必要があります。
inline void LightingCustom_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); }
ライティングがグローバルライティングに影響する度合いを計算するために使用されますが、チュートリアルの目的ではオプションです。
結果は、まさに必要なものになります。
この最初の部分では、1つのシェーダーで2つの異なる照明モデルを使用する方法を学びました。 これにより、PBRを使用してモデルの半分をレンダリングし、他の半分を消灯したままにすることができました。 2番目の部分では、このチュートリアルを完了し、効果をアニメーション化して改善する方法を示します。
ジオメトリを切り取ります
シェーダーに追加する最も簡単な方法は、ジオメトリの上部のレンダリングを停止する効果です。 シェーダーで任意のピクセルのレンダリングをキャンセルするには、
discard
キーワードを使用できます。 これを使用すると、モデルの上部に境界線のみを描画できます。
void surf (Input IN, inout SurfaceOutputStandard o) { if (IN.worldPos.y > _ConstructY + _ConstructGap) discard; ... }
これにより、ジオメトリに「穴」が残る可能性があることに注意してください。 オブジェクトの裏面が完全に描画されるように、面のクリッピングを無効にする必要があります。
Cull Off
今、私たちはオブジェクトが中空に見えるという事実に最も不快です。 これは単なる感覚ではありません。実際、3Dモデルはすべて中空です。 ただし、オブジェクトが実際には固体であるという錯覚を作成する必要があります。 これは、同じ非点灯シェーダでオブジェクトを内側からペイントすることで簡単に実現できます。 オブジェクトはまだ空ですが、満杯に感じられます。
これを達成するために、カメラの方に向けられた三角形を背面で単純に色付けします。 ベクトル代数に慣れていない場合、これは非常に複雑に見えるかもしれません。 実際、これはスカラー積を使用して非常に簡単に実現できます。 2つのベクトルのスカラー積は、それらがどのように「方向付けられている」かを示します。 そして、これはそれらの間の角度に直接関係しています。 2つのベクトルのスカラー積が負の場合、それらの間の角度は90度より大きくなります。 カメラの視線の方向(サーフェスシェーダーの
viewDir
)と三角形の法線の間のスカラー積を取得することにより、初期条件を確認できます。 負の場合、三角形はカメラから遠ざかります。 つまり、その「裏返し」が表示され、単色でレンダリングできます。
struct Input { float2 uv_MainTex; float3 worldPos; float3 viewDir; }; void surf (Input IN, inout SurfaceOutputStandard o) { viewDir = IN.viewDir; ... } inline half4 LightingCustom(SurfaceOutputStandard s, half3 lightDir, UnityGI gi) { if (building) return _ConstructColor; if (dot(s.Normal, viewDir) < 0) return _ConstructColor; return LightingStandard(s, lightDir, gi); }
結果は以下の画像に示されています。 左側では、「間違ったジオメトリ」が赤でレンダリングされます。 オブジェクトの上部の色を使用すると、オブジェクトはもう白く見えなくなります。
波状効果
Planetary Annihilationをプレイした場合、3Dプリンターのシェーダーは小さなうねりの効果を使用することがわかります。 また、ワールド内のレンダリングされたピクセルの位置に少しノイズを追加することで実装することもできます。 これは、ノイズテクスチャによって、または連続的な周期関数を使用して実現できます。 以下のコードでは、任意のパラメーターで正弦波を使用しています。
void surf (Input IN, inout SurfaceOutputStandard o) { float s = +sin((IN.worldPos.x * IN.worldPos.z) * 60 + _Time[3] + o.Normal) / 120; if (IN.worldPos.y > _ConstructY + s + _ConstructGap) discard; ... }
これらのパラメータは、美しい波状の効果を得るために手動で調整できます。
アニメーション
エフェクトの最後の部分はアニメーションです。
_ConstructY
パラメーターを追加するだけで取得できます。 シェーダーが残りを処理します。 コードまたはアニメーションカーブを使用して、エフェクトの速度を制御できます。 最初のオプションを使用すると、その速度を完全に制御できます。
public class BuildingTimer : MonoBehaviour { public Material material; public float minY = 0; public float maxY = 2; public float duration = 5; // Update is called once per frame void Update () { float y = Mathf.Lerp(minY, maxY, Time.time / duration); material.SetFloat("_ConstructY", y); } }
最後に、この画像で使用されているモデルは、加速器の下部が開いているため、数秒間中空に見えることに注意してください。 つまり、オブジェクトは実際には中空です。
[元の記事の著者をPatreonで10ドルでサポートすることにより、Unityパッケージ(コード、シェーダー、および3Dモデル)をダウンロードできます。]