はじめに
uDesktopDuplication に用意されているカーブドスクリーンを uWindowCapture でも使いたいとの要望を頂いたので、単体のアセットとして作成してみました。
デモ
ダウンロード
Release から最新の .unitypackage をダウンロードしてプロジェクトへ展開してください。
使い方
メッシュ
使えるメッシュは付属の CurvedScreen
のみになります。Z軸方向を向き X 方向に曲げ用に分割され、片側の XY 平面が Z=0 になっている形です。スケールは X = Y = 1 m、Z = 0.01 m です。
シェーダ
CurvedScreen/Unlit
および CurvedScreen/Standard
の 2 つのシェーダが用意されています。それぞれ、Unlit/Texture
および Surface Standard Shader
に Radius
および Thickness
プロパティが追加されたものになっています。
Radius
は曲げの半径、Thickness
は厚み(1 が 1 cm)です。横幅は transform.localScale.x
がそのまま使われます。
解説
頂点変形
曲げる部分は以下のように入力で渡ってきたローカル座標の頂点を関数を通して変形します。
#include "./CurvedScreen.cginc" float _Radius; float _Thickness; v2f vert(appdata v) { v2f o; CurvedScreenVertex(v.vertex.xyz, _Radius, CurvedScreenGetWidth(), _Thickness); o.vertex = UnityObjectToClipPos(v.vertex); ... return o; }
CurvedScreen.cginc
にここでの変形が書かれています。
inline float CurvedScreenGetWidth() { return length(float3(unity_ObjectToWorld[0].x, unity_ObjectToWorld[1].x, unity_ObjectToWorld[2].x)); } inline void CurvedScreenVertex(inout float3 v, float radius, float width, float thickness) { float angle = width * v.x / radius; v.z *= thickness; radius += v.z; v.z -= radius * (1.0 - cos(angle)); v.x = radius * sin(angle) / width; }
X 方向(画面横幅)のスケールは unity_ObjectToWorld
から以下のフォーラムのスレッドを参考に抽出しています。
移動成分と違ってスケール成分は回転成分と混ざってるので、モデル行列から取り出すのにちょっとだけ計算が必要です。こうして得られた横幅を使いながら、内側の真ん中の点を中心に曲がるように三角関数を適当に使いながらローカル座標位置を動かしています。
法線変形
CurvedScreen/Standard
シェーダの方ではサーフェスシェーダを使って記述しています。こちらはライティングが影響するため、法線も曲げてあげなければいけません。
void vert(inout appdata_full v) { float width = CurvedScreenGetWidth(); CurvedScreenNormal(v.vertex.x, v.normal, _Radius, width); v.normal.x *= width; // for UnityObjectToWorldNormal() CurvedScreenVertex(v.vertex.xyz, _Radius, width, _Thickness); }
CurvedScreenNormal()
の中では法線を曲げに応じて回転させる処理が入っています。
inline float3 CurvedScreenRotateY(float3 n, float angle) { float c = cos(angle); float s = sin(angle); return float3(c * n.x - s * n.z, n.y, s * n.x + c * n.z); } inline void CurvedScreenNormal(float x, inout float3 n, float radius, float width) { float angle = -width * x / radius; n = CurvedScreenRotateY(n, angle); }
ただし、法線は UnityObjectToWorldNormal()
を通る際にスケーリングを考慮した計算が行われます。頂点は円形に曲げているのでスケーリングの考慮は要らず、単に回転させるだけで良いはずなのでこのままでは都合がよくありません。そこでこの考慮を打ち消すために、width
を X 方向に掛ける計算を挟んでいます。詳しくは Catlike Coding さんにて解説されているので興味がある方は読んでみてください。
影の対応
頂点変形をした結果を ShadowCaster
パスへ反映させるためには、addshadow
というキーワードを pragma 文に追加します。
addshadow
の説明は次になります。
Generate a shadow caster pass. Commonly used with custom vertex modification, so that shadow casting also gets any procedural vertex animation. Often shaders don’t need any special shadows handling, as they can just use shadow caster pass from their fallback.
#pragma surface surf Standard addshadow fullforwardshadows vertex:vert
ちなみに、以前の記事では解説しませんでしたが、サーフェスシェーダが頂点・フラグメントシェーダへ展開されると、この vert
関数は頂点シェーダの早い段階で呼ばれるように差し込まれます。
v2f_surf vert_surf (appdata_full v) { UNITY_SETUP_INSTANCE_ID(v); v2f_surf o; UNITY_INITIALIZE_OUTPUT(v2f_surf,o); UNITY_TRANSFER_INSTANCE_ID(v,o); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); vert (v); ...
自作シェーダへの組み込み
自身のシェーダへ組み込みたい場合は、CurvedScreen.cginc
を include して、上記で説明した CurvedScreenVertex()
および CurvedScreenNormal()
を頂点シェーダに差し込んでください。
おわりに
コリジョンが面倒だったりと問題はあるのですが、動的に曲げないといけないようなユースケースで使えると思います(ちなみに uDesktopDuplication では同様の方法で曲げていて、専用のレイキャストを用意しています)。現在は簡単のために Y 軸周りのカーブしか実装していませんが、X 軸側のカーブをつけても面白いかも知れません。