Unity3Dのアニメーション化された宇宙船シールド効果

こんにちはHabr! Unity3Dで宇宙船のシールドをレンダリングするためのシェーダーの作成方法を教えてください。



このように




この記事は初心者を対象としていますが、経験豊富なシェーダーライターが記事を読んで批判してくれたら嬉しいです。



猫の下で興味を持ってください。 (注意!重い写真とgifの中)。



この記事は説明付きの一連の指示として書かれており、完全な初心者でもそれらを実行して既製のシェーダーを取得できますが、何が起こっているのかを理解するには、基本的な用語でナビゲートすることをお勧めします:





エフェクトは、3つの主要コンポーネントで構成されています。





これらのコンポーネントを順番にシェーダーに追加し、記事の終わりまでに両方のKDPVに効果をもたらします。



ベースシェーダー



標準のUnity3Dシェーダーから始めましょう:



標準の非照明シェーダーのソースコード
Shader "Unlit/NewUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } }
      
      







目的に合わせて準備します。



  1. 名前をShields / Transparentに変更します。これを行うには、 Shader "Unlit/NewUnlitShader"



    ラインShader "Unlit/NewUnlitShader"



    Shader "Shields/Transparent"



    に置き換えます。
  2. ユニット内の半透明の要素は、特別な方法で別のキューに描画されます。このため、 Tags { "RenderType"="Opaque" }



    Tags { "Queue"="Transparent" "RenderType"="Transparent" }



    置き換えることで、シェーダーが半透明であることをユニットに通知する必要がありTags { "Queue"="Transparent" "RenderType"="Transparent" }





    半透明の要素を描画するには、特別なブレンドモードを設定する必要があります。 Tags { "Queue"="Transparent" "RenderType"="Transparent" }



    後に行Blend SrcAlpha OneMinusSrcAlpha



    追加します。



    また、Z-Bufferのエントリを無効にする必要があります-不透明なオブジェクトをソートするために使用され、半透明のオブジェクトをレンダリングするためにのみ干渉します。 これを行うには、次の行を追加します



     ZWrite Off
          
          









     Tags { "Queue"="Transparent" "RenderType"="Transparent" }
          
          





  3. シールド効果は、ユニットに組み込まれたフォグ効果と一緒に使用されないため、シェーダーからすべての参照を削除します-ラインを削除します



     UNITY_FOG_COORDS(1)
          
          





     UNITY_TRANSFER_FOG(o,o.vertex)
          
          





     UNITY_APPLY_FOG(i.fogCoord, col)
          
          





基本的な消灯半透明シェーダーを取得しました。 次に、それからシェーダーを作成する必要があります。このシェーダーは、テクスチャを半透明のマスクとして使用し、ユーザーがピクセルの色として指定した色を使用します。



  1. これで、シェーダーには入力パラメーターが1つだけになりました。テクスチャー、入力パラメーターとして色を追加し、テクスチャーパラメーターの名前をTransparency Maskに変更します。 ユニットでは、シェーダーの入力パラメーターはPropertiesブロック内で設定されますが、次のようになります。



     Properties { _MainTex ("Texture", 2D) = "white" {} }
          
          





    入力パラメーターの色を追加し、テクスチャの名前を変更します。



     Properties { _ShieldColor("Shield Color", Color) = (1, 0, 0, 1) _MainTex ("Transparency Mask", 2D) = "white" {} }
          
          





    Propertiesブロックで設定された入力パラメーターを頂点シェーダーおよびフラグメントシェーダーで使用可能にするには、シェーダーパッセージ内で変数として宣言する必要があります-行を挿入します



      float4 _ShieldColor;
          
          





    行の前



     v2f vert (appdata v)
          
          





    公式ドキュメントでパラメーターをシェーダーに渡す方法の詳細を読むことができます
  2. 単一ピクセルの色は、フラグメントシェーダーの戻り値によって決まります。

    これは次のようになります。



     fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); return col; }
          
          





    v2fとは
    ここで、 v2f



    は、画面上の特定のピクセルに対して補間された頂点シェーダーの戻り値です



     struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };
          
          





    uv



    ピクセルテクスチャ座標

    vertext



    画面座標のピクセル座標



    この単純な関数は、頂点シェーダーから取得したテクスチャ座標でテクスチャから色を取得し、ピクセルの色として返します。 テクスチャカラーを透明マスクとして使用する必要があり、その色はシェーダーパラメータから取得する必要があります。



    次のことを行います。



     fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, transparencyMask.r); }
          
          





    つまり、以前と同じようにテクスチャをサンプリングしますが、その色を直接返す代わりに、 _ShieldColor



    として、テクスチャの赤い色から_ShieldColor



    したアルファチャネルを持つ色を返します。

  3. テクスチャを変更せずにシールドの半透明度を調整できるように、シールド強度係数という別のパラメータを追加します。



    読者は自分でそれを行うか、ネタバレを見ることを提案します。



    非表示のテキスト
     Properties { _ShieldIntensity("Shield Intensity", Range(0,1)) = 1.0 _ShieldColor("Shield Color", Color) = (1, 0, 0, 1) _MainTex ("Transparency Mask", 2D) = "white" {} }
          
          





     float _ShieldIntensity; fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, _ShieldIntensity * transparencyMask.r); }
          
          









次のようなものが得られるはずです。







以下、このシームレスなノイズテクスチャを使用します




結果のシェーダーの完全なリスト
 Shader "Shields/Transparent" { Properties { _ShieldIntensity("Shield Intensity", Range(0,1)) = 1.0 _ShieldColor("Shield Color", Color) = (1, 0, 0, 1) _MainTex ("Transparency Mask", 2D) = "white" {} } SubShader { Tags { "Queue"="Transparent" "RenderType"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal: NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _ShieldColor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } float _ShieldIntensity; fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, _ShieldIntensity * transparencyMask.r); } ENDCG } } }
      
      







今のところあまり良く見えませんが、これが全体の効果が構築される基礎です。



フレネル効果



一般に、フレネル効果は、入射角が増加すると反射ビームの強度が増加する効果です。 しかし、この効果の計算に使用される式を使用して、シールドの輝きの強度の視野角への依存を設定します。



nvidiaのcgチュートリアルの近似式を使用して始めましょう



ここで、Iはカメラからピークへの方向、Nは入射点での表面の法線です。



  1. 最初に、シェーダーを新しいファイルにコピーし、Shields / Fresnelに名前を変更して、変更履歴を保持します。
  2. 式からわかるように、 Bias,

    Scale, Power




    シェーダーBias,

    Scale, Power




    3つの新しいパラメーターが必要ですBias,

    Scale, Power




    Bias,

    Scale, Power




    Bias,

    Scale, Power




    。 読者がシェーダーにパラメーターを追加する方法を既に学んでおり、これを行う方法についての詳細な指示を与えないことを願っています。 困難な場合は、セクションの最後にある完全なコードをいつでも見ることができます
  3. 頂点シェーダーでIとNを計算します。 シェーダーの頂点シェーダーはv2f vert (appdata v)



    関数v2f vert (appdata v)



    戻り値は前述のv2f



    構造体であり、 appdata



    はメッシュから取得した頂点パラメーターです。



    appdataとは
     struct appdata { float4 vertex : POSITION; float3 normal: NORMAL; float2 uv : TEXCOORD0; };
          
          





    vertex



    -ローカル座標のvertex



    座標

    normal



    この頂点に指定されたサーフェスの法線

    uv



    頂点テクスチャ座標



    I-カメラからワールド座標でのトップへの方向-は、トップのワールド座標とカメラのワールド座標の差として計算できます。 Unityシェーダーでは、ローカル座標からワールド座標への遷移マトリックスはunity_ObjectToWorld



    変数で使用でき、ワールドカメラ座標は_WorldSpaceCameraPos



    変数で使用_WorldSpaceCameraPos



    ます。 これを知って、頂点シェーダーコードの次の行でIを計算できます。



     float4 worldVertex = mul(unity_ObjectToWorld, v.vertex); float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz);
          
          





    N-世界座標の表面法線-計算がさらに簡単:



      float3 normWorld = normalize(mul(unity_ObjectToWorld, v.normal));
          
          





  4. これで、フレネル効果の式に従ってシールドの不透明度を計算できます。



     float fresnel = _Bias + _Scale * pow(1.0 + dot(I, normWorld), _Power);
          
          





    変数の特定の値のフレネル値が0未満になる可能性があることに気付くかもしれません。これにより、レンダリング時に色のアーチファクトが生じます。 saturate



    関数を使用して、変数の値を区間[0; 1]に制限します。



     float fresnel = saturate(_Bias + _Scale * pow(1.0 + dot(I, normWorld), _Power));
          
          





  5. この値をピクセルシェーダーに転送するためだけに残ります。 これを行うには、強度フィールドをv2f構造に追加します。



     struct v2f { float2 uv : TEXCOORD0; float intensity : COLOR0; float4 vertex : SV_POSITION; };
          
          





    COLOR0



    はセマンティクスであり、それが何であるかの説明はこの記事の範囲外です。興味のある人はhlslのセマンティクスについて読むことができます)。



    これで、頂点シェーダーでこのフィールドを埋め、フラグメントシェーダーで使用できます。



     v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); float4 worldVertex = mul(unity_ObjectToWorld, v.vertex); float3 normWorld = normalize(mul(unity_ObjectToWorld, v.normal)); float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz); float fresnel = saturate(_Bias + _Scale * pow(1.0 + dot(I, normWorld), _Power)); o.intensity = fresnel; return o; } float _ShieldIntensity; fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, (_ShieldIntensity + i.intensity) * transparencyMask.r); }
          
          





    _ShieldIntensity



    i.intensity



    が頂点シェーダーに追加できるようになったことがわかります。



できた! フレネル方程式のパラメーターを再生すると、そのような画像を得ることができます







私のオプション
バイアス= -0.5、スケール= 1、電力= 1


フルフレネルシールドのリスト
 Shader "Shields/Fresnel" { Properties { _ShieldIntensity("Shield Intensity", Range(0,1)) = 1.0 _ShieldColor("Shield Color", Color) = (1, 0, 0, 1) _MainTex ("Transparency Mask", 2D) = "white" {} _Bias("Bias", float) = 1.0 _Scale("Scale", float) = 1.0 _Power("Power", float) = 1.0 } SubShader { Tags { "Queue"="Transparent" "RenderType"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal: NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float intensity : COLOR0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _ShieldColor; float _ShieldIntensity; float _Bias; float _Scale; float _Power; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); float4 worldVertex = mul(unity_ObjectToWorld, v.vertex); float3 normWorld = normalize(mul(unity_ObjectToWorld, v.normal)); float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz); float fresnel = saturate(_Bias + _Scale * pow(1.0 + dot(I, normWorld), _Power)); o.intensity = fresnel + _ShieldIntensity; return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, i.intensity * transparencyMask.r); } ENDCG } } }
      
      







これで最も興味深いものに進むことができます-シールドにヒットを表示します。



ヒット図面



ヒットに対する可能な反応の1つだけを説明します。パフォーマンスの点では非常にシンプルで安価ですが、非常に見栄えがよく、非常にシンプルなものとは異なり、密接に横たわるヒットで美しい写真を提供します。



  1. 効果を実装するために、シェーダーは何らかの方法でヒットが発生したポイントと時間を検出する必要があります。 GameObjectシールドのスクリプトは、これらの引数の受け渡しに関与します。c#スクリプトはこの記事の主題ではないため、スクリプトのソースコードを簡単に示します。



    シールド付きオブジェクトのスクリプトリスト
     public class ShieldHitter : MonoBehaviour { private static int[] hitInfoId = new[] { Shader.PropertyToID("_WorldHitPoint0"), Shader.PropertyToID("_WorldHitPoint1"), Shader.PropertyToID("_WorldHitPoint2") }; private static int[] hitTimeId = new[] { Shader.PropertyToID("_HitTime0"), Shader.PropertyToID("_HitTime1"), Shader.PropertyToID("_HitTime2") }; private Material material; void Start() { if (material == null) { material = this.gameObject.GetComponent<MeshRenderer>().material; } } int lastHit = 0; public void OnHit(Vector3 point, Vector3 direction) { material.SetVector(hitInfoId[lastHit], point); material.SetFloat(hitTimeId[lastHit], Time.timeSinceLevelLoad); lastHit++; if (lastHit >= hitInfoId.Length) lastHit = 0; } void OnCollisionEnter(Collision collision) { OnHit(collision.contacts[0].point, Vector3.one); } }
          
          







    カメラスクリプトリスト
     using UnityEngine; [ExecuteInEditMode] public class CameraControls : MonoBehaviour { private const int minDistance = 25; private const int maxDistance = 25; private const float minTheta = 0.01f; private const float maxTheta = Mathf.PI - 0.01f; private const float minPhi = 0; private const float maxPhi = 2 * Mathf.PI ; [SerializeField] private Transform _target; [SerializeField] private Camera _camera; [SerializeField] [Range(minDistance, maxDistance)] private float _distance = 25; [SerializeField] [Range(minTheta, maxTheta)] private float _theta = 1; [SerializeField] [Range(minPhi, maxPhi)] private float _phi = 2.5f; [SerializeField] private float _angleSpeed = 2.0f; [SerializeField] private float _distanceSpeed = 2.0f; // Update is called once per frame void Update () { if (_target == null || _camera == null) { return; } if (Application.isPlaying) { if (Input.GetKey(KeyCode.Q)) { _distance += _distanceSpeed * Time.deltaTime; } if (Input.GetKey(KeyCode.E)) { _distance -= _distanceSpeed * Time.deltaTime; } Mathf.Clamp(_distance, minDistance, maxDistance); if (Input.GetKey(KeyCode.A)) { _phi += _angleSpeed * Time.deltaTime; } if (Input.GetKey(KeyCode.D)) { _phi -= _angleSpeed * Time.deltaTime; } _phi = _phi % (maxPhi); if (Input.GetKey(KeyCode.S)) { _theta += _angleSpeed * Time.deltaTime; } if (Input.GetKey(KeyCode.W)) { _theta -= _angleSpeed * Time.deltaTime; } _theta = Mathf.Clamp(_theta, minTheta, maxTheta); Vector3 newCoords = new Vector3 { x = _distance * Mathf.Sin(_theta) * Mathf.Cos(_phi), z = _distance * Mathf.Sin(_theta) * Mathf.Sin(_phi), y = _distance * Mathf.Cos(_theta) }; this.transform.position = newCoords + _target.position; this.transform.LookAt(_target); if (Input.GetMouseButtonDown(0)) { Ray ray = _camera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; var isHit = Physics.Raycast(ray, out hit); if (isHit) { ShieldHitter handler = hit.collider.gameObject.GetComponent<ShieldHitter>(); Debug.Log(hit.point); if (handler != null) { handler.OnHit(hit.point, ray.direction); } } } } } }
          
          







  2. 前回同様、シェーダーを新しい名前Shields / FresnelWithHitsで保存します
  3. アイデアは、シールドの各ポイントの隣のヒットからシールドの摂動を計算することであり、ヒットが早く発生するほど、シールドの摂動への影響が少なくなります。



    次の式を選択しました。





    =11/1







    ここで:

    distance



    最大値からヒットポイントまでの距離の一部、[0、1]

    time-最大からの寿命の割合、[0、1]

    したがって、強度は衝突点までの距離に反比例し、

    ヒットの終了までの残り時間に比例し、最大値以上の距離および残り時間が0の場合は0にもなります。



    時間と距離の範囲を制限することなく、これらの条件を満たす関数を見つけたいのですが、これだけです。

  4. シェーダーでヒットの効果を描画すると、必然的に同時に処理されるヒットの数に制限が課せられます。たとえば、同時に表示される3つのヒットを選択しました。 シェーダーに入力パラメーターWorldHitPoint0、WorldHitPoint1、WorldHitPoint2、HitTime0、HitTime1、HitTime2を追加-同時に処理される各ヒットのペア。 また、MaxDistanceパラメーター(ヒットからのシールド妨害の最大距離)とHitDuration(ヒットからのシールド妨害の持続時間)も必要です。
  5. 各ヒットについて、頂点シェーダーの時間と距離を計算します



     float t0 = saturate((_Time.y - _HitTime0) / _HitDuration); float d0 = saturate(distance(worldVertex.xyz, _WorldHitPoint0.xyz) / (_MaxDistance)); float t1 = saturate((_Time.y - _HitTime1) / _HitDuration); float d1 = saturate(distance(worldVertex.xyz, _WorldHitPoint1.xyz) / (_MaxDistance)); float t2 = saturate((_Time.y - _HitTime2) / _HitDuration); float d2 = saturate(distance(worldVertex.xyz, _WorldHitPoint2.xyz) / (_MaxDistance));
          
          





    次の式で総ヒット率を計算します。



     float hitIntensity = (1 - t0) * ((1 / (d0)) - 1) + (1 - t1) * ((1 / (d1)) - 1) + (1 - t2) * ((1 / (d2)) - 1);
          
          





    ヒットからのシールドの強度と他の効果からの強度を追加するためにのみ残ります:



     o.intensity = fresnel + _ShieldIntensity + hitIntensity;
          
          





  6. マテリアルをセットアップし、正しい距離と出来上がり値を設定します。







    もう十分ですよね? しかし、1つの問題があります。 シールドの背面のヒットは表示されません。 これは、デフォルトでは、法線がカメラから遠ざかる方向にあるポリゴンが描画されないためです。 グラフィックエンジンにそれらを描画させるには、 ZWrite Off



    後にZWrite Off



    Cull off



    ラインを追加する必要があります。 しかし、ここで問題に直面しています。

    最後のセクションで実装されたフレネル効果は、カメラから見たすべてのポリゴンを強調表示します-数式を



     float dt = dot(I, normWorld); fresnel = saturate(_Bias + _Scale * pow(1.0 - dt * dt, _Power));
          
          





    初期式はすでに近似値であるため、正方形を使用しても結果に大きな影響はなく(他のパラメーターで修正できます)、高価な分岐演算子を追加したり、高価なsqrtを使用したりすることはできません。



    開始、確認、および:







    今ではすべてがとても良いです。

  7. 最後にもう1つ、「活気」の効果を与えるために、ノイズのテクスチャ座標に現在の時間を追加して、球の周りを移動するシールドの効果を作成できます。



     o.uv = TRANSFORM_TEX(v.uv, _MainTex) + _Time.x / 6;
          
          







最終結果:







シェーダーの最終バージョンのリスト
 Shader "Shields/FresnelWithHits" { Properties { _ShieldIntensity("Shield Intensity", Range(0,1)) = 1.0 _ShieldColor("Shield Color", Color) = (1, 0, 0, 1) _MainTex ("Transparency Mask", 2D) = "white" {} _Bias("Bias", float) = 1.0 _Scale("Scale", float) = 1.0 _Power("Power", float) = 1.0 _WorldHitPoint0("Hit Point 0", Vector) = (0, 1, 0, 0) _WorldHitTime0("Hit Time 0", float) = -1000 _WorldHitPoint1("Hit Point 1", Vector) = (0, 1, 0, 0) _WorldHitTime1("Hit Time 1", float) = -1000 _WorldHitPoint2("Hit Point 2", Vector) = (0, 1, 0, 0) _WorldHitTime2("Hit Time 2", float) = -1000 _HitDuration("Hit Duration", float) = 10.0 _MaxDistance("MaxDistance", float) = 0.5 } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" } ZWrite Off Cull Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal: NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float intensity : COLOR0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _ShieldColor; float _ShieldIntensity; float _Bias; float _Scale; float _Power; float _MaxDistance; float _HitDuration; float _HitTime0; float4 _WorldHitPoint0; float _HitTime1; float4 _WorldHitPoint1; float _HitTime2; float4 _WorldHitPoint2; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex) + _Time.x / 6; float4 worldVertex = mul(unity_ObjectToWorld, v.vertex); float3 normWorld = normalize(mul(unity_ObjectToWorld, v.normal)); float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz); float fresnel = 0; float dt = dot(I, normWorld); fresnel = saturate(_Bias + _Scale * pow(1.0 - dt * dt, _Power)); float t0 = saturate((_Time.y - _HitTime0) / _HitDuration); float d0 = saturate(distance(worldVertex.xyz, _WorldHitPoint0.xyz) / (_MaxDistance)); float t1 = saturate((_Time.y - _HitTime1) / _HitDuration); float d1 = saturate(distance(worldVertex.xyz, _WorldHitPoint1.xyz) / (_MaxDistance)); float t2 = saturate((_Time.y - _HitTime2) / _HitDuration); float d2 = saturate(distance(worldVertex.xyz, _WorldHitPoint2.xyz) / (_MaxDistance)); float hitIntensity = (1 - t0) * ((1 / (d0)) - 1) + (1 - t1) * ((1 / (d1)) - 1) + (1 - t2) * ((1 / (d2)) - 1); o.intensity = fresnel + _ShieldIntensity + hitIntensity; return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 transparencyMask = tex2D(_MainTex, i.uv); return fixed4(_ShieldColor.r, _ShieldColor.g, _ShieldColor.b, saturate(i.intensity * transparencyMask.r)); } ENDCG } } }
      
      







必要なもの。



したがって、宇宙船のシールドの非常に美しい効果を得るのは簡単であり、それほど高価ではありません。



あとがきの代わりに:最適化



可能な最適化の主な領域について概説します。






All Articles