LineRendererをベジェ曲線で描画する
Unityにて、LineRendererを用いてベジェ曲線を描画する方法を説明する。
なお、この記事内容はOculus/VRには直接関係は無く汎用的な内容である。
ベジェ曲線の実装方法を調査する
まずベジェ曲線については以下のサイトの2〜5枚目の画像を見れば理解できる。
記事は英語だけど画像を見るだけでだいたいわかる。
https://gyazo.com/dc944c2f6ef0e1a7c7950d2e571585dc
多分問題ないと思うので核心の部分だけ転載する。
これを見れば「ああ、Lerp使うだけで実装できるのね」と理解できる(はず)。考えた人あったまいーと毎回思う。
(Lerpは入力値AとBの間の値をいい感じに計算してくれる関数)。
Unityから曲線を直接描画する機能は無いので、上記の黄色いポイントをいくつか設定し、それを繋げる直線として実装することになる。
上記サイトでは黄色いポイントの座標を取得するコードまで用意してくれているのでありがたく頂戴する。
但し一部コードが間違っていたので修正版を以下に記す。
code:SampleCurve.cs
Vector3 SampleCurve(Vector3 start, Vector3 end, Vector3 control, float t) {
// Interpolate along line S0: control - start;
Vector3 Q0 = Vector3.Lerp(start, control, t);
// Interpolate along line S1: S1 = end - control;
Vector3 Q1 = Vector3.Lerp(control, end, t);
// Interpolate along line S2: Q1 - Q0
Vector3 Q2 = Vector3.Lerp(Q0, Q1, t);
return Q2; // Q2 is a point on the curve at time t
}
start, endは説明不要。
controlは任意で指定する中間ポイント(上図の真ん中の黒い点)。
tはstart〜endまでの距離を1とした時の経過距離。
中間ポイントはとりあえず雑に以下の計算で作成する
code:getMiddlePoint.cs
var control = (start + end) / 2 + Vector3.up;
値は最終的にインスペクタで指定できるようにするので問題なし。
tの値は直線としての分割数から計算できる。
よし、なんとかなるな。
Unityでの実装作業(ひとまず直線)
以上を踏まえて、Unityで描画の実装に入る。
新規プロジェクトを作成し、Sphereを2個配置してカメラから以下のように見える距離に調整する。
https://gyazo.com/7776917f9eb4945a9c44684b8ab4e110
具体的には以下のように設定した
メインカメラ:(0,1,-2)
Sphere1:(2,1,2)
Sphere1:(-2,1,2)
画像のSphereが市松模様なのは単色だと寂しいから変えただけなので気にしなくて良い。
次に、ヒエラルキー上で"Create Empty"してオブジェクトを作成する(場所は気にしなくて良い)。
名前は"LineRendererObject"にでもしておく。
LineRendererObjectのインスペクタからAdd Componentで"Effects > Line Renderer"を追加する。
LineRenderのインスペクタはWidthは0.5くらいにしておく。
https://gyazo.com/ce23746271e98cfad4cb8813c645c49e
ここではマテリアルを作成して線の色をなんとなくオレンジに変更してあるが、デフォルトのまま(紫色)で良ければこの設定は行う必要はない。
LineRendererObjectが(0,0,0)の位置にある場合、実行して得られる画像は以下のようになっているはずである。
https://gyazo.com/6b4f4cefc9669e34d8de6d9749fae9c0
(゚Д゚)
上記のようになっている事が確認できたら、まず単純にSphere1と2を繋げるだけのスクリプトを作成する。
Asset上でCreate > C# Scriptを実行し、スクリプトファイルを作成する。
スクリプト名は"BezierLineRendererScript"とする。
エディタでスクリプトを開き、内容を以下に書き換える。
code:BezierLineRendererScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BezierLineRendererScript : MonoBehaviour {
public Transform trans1;
public Transform trans2;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
LineRenderer render = GetComponent<LineRenderer>();
render.positionCount = 2;
render.SetPosition(0, trans1.position);
render.SetPosition(1, trans2.position);
}
}
先ほどのLineRendererObjectにBezierLineRendererScriptを追加(Add Component)し、tran1, 2にSphere1, 2を設定する。
https://gyazo.com/7ca9a45c7bfb67c63fab26631ca17d66
Trans1,2を正しく設定していれば、実行で以下の画像が得られる。
https://gyazo.com/3892bd92bcb3a202d5b84d4ff62e350f
ベイマックス
これでLineRendererの描画位置をコード上から操作出来たことになる。
ここで試しにBezierLineRendererScriptのUpdateを以下のように変えて、強引に描画用Positionを増やしてる。
code:update.cs
void Update () {
LineRenderer render = GetComponent<LineRenderer>();
render.positionCount = 3;
render.SetPosition(0, trans1.position);
render.SetPosition(1, new Vector3(0, 2, 2));
render.SetPosition(2, trans2.position);
}
実行すると以下のような画像が得られる。
https://gyazo.com/6639c672e212834e73e52c9c533f783d
以上の結果により、LineRendererのPositionを増やして値を自動で計算するようにすれば曲線を作成出来る目処がたった。
Unityでのベジェ曲線実装
再びBezierLineRendererScriptのUpdateを以下のように変えてみる。また、先ほどのベジェ曲線計算コードも追加する。
code:*.cs
void Update () {
int middlePoints = 10;
LineRenderer render = GetComponent<LineRenderer>();
var control = (trans1.position + trans2.position) / 2 + Vector3.up;
var totalPoints = middlePoints + 2;
render.positionCount = totalPoints;
render.SetPosition(0, trans1.position);
for (int i = 1; i <= middlePoints; i++)
{
var t = (float)i / (float)(totalPoints - 1);
var mpos = SampleCurve(trans1.position, trans2.position, control, t);
render.SetPosition(i, mpos);
}
render.SetPosition(totalPoints-1, trans2.position);
}
Vector3 SampleCurve(Vector3 start, Vector3 end, Vector3 control, float t)
{
// Interpolate along line S0: control - start;
Vector3 Q0 = Vector3.Lerp(start, control, t);
// Interpolate along line S1: S1 = end - control;
Vector3 Q1 = Vector3.Lerp(control, end, t);
// Interpolate along line S2: Q1 - Q0
Vector3 Q2 = Vector3.Lerp(Q0, Q1, t);
return Q2; // Q2 is a point on the curve at time t
}
Update関数中のmiddlePointsが中継ポイントの数、controlが中間ポイントの位置となる。
全体のポイント数からSampleCurveの引数tを算出し、描画位置mposを取得、SetPositionでLineRendererに設定している。
上記のコードを実行すると以下のように曲線で描画された画像が得られる。
https://gyazo.com/dc1eaaf25e650f895921f39cd2fca4e5
やった!
後はmiddlePoints,controlをプロパティにすれば完成となる。
最終的なコードを改めて掲載する。
code:BezierLineRendererScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class BezierLineRendererScript : MonoBehaviour {
public Transform trans1;
public Transform trans2;
private int middlePoints = 10;
private Vector3 controlPoint = new Vector3(0, 2, 0);
public bool considerDistance = false;
public float distanceEffect = 0.2f;
// Use this for initialization
void Start () {
}
void Update () {
LineRenderer render = GetComponent<LineRenderer>();
var de = 1.0f;
if (considerDistance) {
de = (trans1.position - trans2.position).magnitude * distanceEffect;
}
var control = (trans1.position + trans2.position) / 2 + controlPoint * de;
var totalPoints = middlePoints + 2;
render.positionCount = totalPoints;
render.SetPosition(0, trans1.position);
for (int i = 1; i <= middlePoints; i++)
{
var t = (float)i / (float)(totalPoints - 1);
var mpos = SampleCurve(trans1.position, trans2.position, control, t);
render.SetPosition(i, mpos);
}
render.SetPosition(totalPoints-1, trans2.position);
}
Vector3 SampleCurve(Vector3 start, Vector3 end, Vector3 control, float t)
{
// Interpolate along line S0: control - start;
Vector3 Q0 = Vector3.Lerp(start, control, t);
// Interpolate along line S1: S1 = end - control;
Vector3 Q1 = Vector3.Lerp(control, end, t);
// Interpolate along line S2: Q1 - Q0
Vector3 Q2 = Vector3.Lerp(Q0, Q1, t);
return Q2; // Q2 is a point on the curve at time t
}
}
完全版ではプロパティにconsiderDistance、distanceEffectを追加しているが、これは距離に応じて中間ポイントの位置を調整するためのものである。
実行して、LineRendererObjectのBezierLineRendererScriptパラメータをいじるとベジェ曲線が働いている事がわかる。
https://gyazo.com/4b50e05cfa6305ac6aef707cd6d0acbb
この応用として、このLineRendererをRaycastに使えば釣竿的なRaycast描画が実現できる。