GrabPassを使った屈折を表現するシェーダの実装方法です。
Unity2018.2.0
GrabPass?
GrabPassの説明と基本的な使い方は下記の記事を参照してください。
シェーダ
さっそくですがシェーダの実装です。
Shader "GrabPassRefraction" { Properties { _RelativeRefractionIndex("Relative Refraction Index", Range(0.0, 1.0)) = 0.67 [PowerSlider(5)]_Distance("Distance", Range(0.0, 100.0)) = 10.0 } SubShader { Tags {"Queue"="Transparent" "RenderType"="Transparent" } Cull Back ZWrite On ZTest LEqual ColorMask RGB GrabPass { "_GrabPassTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { half4 vertex : POSITION; half4 texcoord : TEXCOORD0; half3 normal : NORMAL; }; struct v2f { half4 vertex : SV_POSITION; half2 samplingViewportPos : TEXCOORD0; }; sampler2D _GrabPassTexture; float _RelativeRefractionIndex; float _Distance; v2f vert (appdata v) { v2f o = (v2f)0; o.vertex = UnityObjectToClipPos(v.vertex); float3 worldPos = mul(unity_ObjectToWorld, v.vertex); half3 worldNormal = UnityObjectToWorldNormal(v.normal); half3 viewDir = normalize(worldPos - _WorldSpaceCameraPos.xyz); // 屈折方向を求める half3 refractDir = refract(viewDir, worldNormal, _RelativeRefractionIndex); // 屈折方向の先にある位置をサンプリング位置とする half3 samplingPos = worldPos + refractDir * _Distance; // サンプリング位置をプロジェクション変換 half4 samplingScreenPos = mul(UNITY_MATRIX_VP, half4(samplingPos, 1.0)); // ビューポート座標系に変換 o.samplingViewportPos = (samplingScreenPos.xy / samplingScreenPos.w) * 0.5 + 0.5; #if UNITY_UV_STARTS_AT_TOP o.samplingViewportPos.y = 1.0 - o.samplingViewportPos.y; #endif return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_GrabPassTexture, i.samplingViewportPos); } ENDCG } } }
説明はコメントに書いた通りです。
疑似的に求めたワールド座標をビューポート座標に変換し、
GrabPassのテクスチャのUV座標としています。
使い方と結果
使い方としては、まず表現したい物質たちの相対屈折率を求めます。
これについては次節を説明します。
また、虫眼鏡のように近くのものを拡大する効果を得たい場合はDistanceを小さく、
遠景の屈折のように上下反転するような効果を得たい場合にはDistanceを大きくします。
これを踏まえて_RelativeRefractionIndex
を0.645、_Distance
を14.8とした時の結果が以下です。
Sphereのメッシュに適用しています。
いい感じに屈折していることが確認できます。
Inspectorを拡張する
上述のシェーダでは_RelativeRefractionIndex
として相対屈折率を指定します。
相対屈折率は静的に求められるものの、このままでは使いづらいのでInspectorを拡張します。
ちなみに相対屈折率については下記の記事で説明しています。
using UnityEngine; using UnityEditor; public class GrabPassRefractionInspector : ShaderGUI { private enum MaterialType { Acryl, Ice, Water, Diamond, } private MaterialType _materialType; public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { var relativeRefractionIndexProp = FindProperty("_RelativeRefractionIndex", properties); var distanceProp = FindProperty("_Distance", properties); // 屈折率入力領域 materialEditor.DefaultShaderProperty(relativeRefractionIndexProp, relativeRefractionIndexProp.displayName); using (new EditorGUI.IndentLevelScope()) { using (new EditorGUILayout.HorizontalScope()) { _materialType = (MaterialType)EditorGUILayout.EnumPopup("From Material", _materialType); if (GUILayout.Button("Apply")) { var refractionIndex = GetRefractionIndex(_materialType); if (refractionIndex != -1) { // 空気の絶対屈折率はほぼ1なので1.0fで計算する var relativeRefractiveIndex = 1.0f / refractionIndex; relativeRefractionIndexProp.floatValue = relativeRefractiveIndex; } } } } // 距離入力領域 materialEditor.DefaultShaderProperty(distanceProp, distanceProp.displayName); } private float GetRefractionIndex(MaterialType type) { switch (type) { case MaterialType.Acryl: return 1.49f; case MaterialType.Ice: return 1.309f; case MaterialType.Water: return 1.3334f; case MaterialType.Diamond: return 2.417f; default: return -1.0f; } } }
よく使いそうな素材を選択すれば空気との相対屈折率が入力されるようにしてみました。
これをシェーダから指定します。
CustomEditor "GrabPassRefractionInspector"
Inspectorは次のようになります。