デスクトップアプリケーションの開発では、クオリティ維持に緩やかな移動が欠かせません。
二点間での緩やかな移動の実装を容易に可能にしてくれるのが、線形補間の考え方です。
本記事の前半では線形補間の概念を、後半ではC++のコンソール上で線形補間のサンプルコードを紹介したいと思います。
線形補間とは
線形補間とは、2点間のデータを使って、その間の値を直線的に計算する方法のことです。
演算を行うと、二点間の一点(p)が求められます。
英語ではLinear Interpolationというため、lerpと略されます。
考え方
二点間ということで、終点(end)と始点(start)の二点があり、終点から始点を引くことで方向ベクトルが求まり、方向ベクトルを可変の補間係数(t)でかけてやることで、線形補間が可能になります。
方向ベクトルの始点が原点になってしまうので、最後に始点(start)を足してやることで、始点からの線形補間となります。
二点間の方向ベクトルに割合であるtの値を乗算するため、しっかり二点間を移動したければ、当然tの値は0~1の間の値に収まります。
線形補間の計算式
式にするとこうです。
P' = (End - Start) * t + Start
コード例
ここからはC++を使用して、実際に実装してみたいと思います。
単純な線形補間
まずは、単純に0.01fずつtを増やして、float型の値を線形補間してみたいと思います。
#include <iostream>
int main() {
float start = 2.6f; //始点
float end = 3.5f; //終点
//線形補間
float t = 0.0f; //補間係数
//お試しで20ループ
for (int i = 0;i < 20;i++) {
t += 0.1f; //補間係数を増加
t = fmin(t, 1.0f); //tを0.0f~1.0fの範囲に制限;
float p = (end - start) * t + start; //線形補間の計算
//結果を表示
printf_s("t: %f, p: %f\n", t, p);
}
system("pause");
return 0;
}
t: 0.100000, p: 2.690000
t: 0.200000, p: 2.780000
t: 0.300000, p: 2.870000
t: 0.400000, p: 2.960000
t: 0.500000, p: 3.050000
t: 0.600000, p: 3.140000
t: 0.700000, p: 3.230000
t: 0.800000, p: 3.320000
t: 0.900000, p: 3.410000
t: 1.000000, p: 3.500000
t: 1.000000, p: 3.500000
t: 1.000000, p: 3.500000
--------------------
中略
--------------------
t: 1.000000, p: 3.500000
t: 1.000000, p: 3.500000
続行するには何かキーを押してください . . .
単純な始点から終点への線形補間が実装できました。
今回は0.1fずつ加算していますが、この値を変えてやることで補間のスピードを変えてやることが出来ます。
往復させる
二点間の間を緩やかに往復させてやりたいことがあると思います。
今回は見栄えもよく都合が良いため、sinカーブの性質を利用してtの計算を行いたいと思います。
#include <iostream>
int main() {
float start = 1.0f; //始点
float end = 2.0f; //終点
//線形補間
float radian = 0.0f; //ラジアン値
//お試しで100ループ
for (int i = 0;i < 100;i++) {
radian += 0.1f; //ラジアン値を増加
float t = sinf(radian) * 0.5f + 0.5f; //0.0f~1.0fの範囲で指定
float p = (end - start) * t + start; //線形補間の計算
//結果を表示
printf_s("t: %f, p: %f\n", t, p);
}
system("pause");
return 0;
}
t: 0.549917, p: 1.549917
t: 0.599335, p: 1.599335
t: 0.647760, p: 1.647760
t: 0.694709, p: 1.694709
t: 0.739713, p: 1.739713
t: 0.782321, p: 1.782321
t: 0.822109, p: 1.822109
t: 0.858678, p: 1.858678
t: 0.891663, p: 1.891664
t: 0.920736, p: 1.920736
t: 0.945604, p: 1.945604
t: 0.966020, p: 1.966020
t: 0.981779, p: 1.981779
t: 0.992725, p: 1.992725
t: 0.998747, p: 1.998747
t: 0.999787, p: 1.999787
t: 0.995832, p: 1.995832
t: 0.986924, p: 1.986924
t: 0.973150, p: 1.973150
t: 0.954649, p: 1.954649
t: 0.931605, p: 1.931605
t: 0.904248, p: 1.904248
t: 0.872853, p: 1.872853
t: 0.837732, p: 1.837732
t: 0.799236, p: 1.799236
t: 0.757751, p: 1.757751
--------------------
中略
--------------------
t: 0.412836, p: 1.412836
t: 0.364119, p: 1.364119
t: 0.316760, p: 1.316760
t: 0.271231, p: 1.271231
t: 0.227989, p: 1.227989
続行するには何かキーを押してください . . .
sinf()の特徴として、ラジアン角を与えると-1.0f~1.0fの値を返します。
float t = sinf(radian) * 0.5f + 0.5f; //0.0f~1.0fの範囲で指定
として0.0f~1.0fの間になるように補正することで、tが補間係数としてうまい具合に収まります。
ベクトルで実装
上記二つではスカラー値で実装を行いましたが、実際の開発ではベクトル値も扱うことになると思います。
今回は、hlslppというライブラリを使用してfloat3のベクトルでコードを組んでみます。
理屈が分かれば、C++標準のstd::vectorや自作の構造体でも同じことが出来ると思います。
また、hlslppの導入については下記の記事で触れています。
#include <iostream>
#include "hlsl++.h" // 全機能をインクルード
int main() {
hlslpp::float3 start = hlslpp::float3(1.0f, 1.0f, 1.0f);//始点
hlslpp::float3 end = hlslpp::float3(2.0f, 5.0f, 3.0f); //終点
//線形補間
float radian = 0.0f; //ラジアン値
//お試しで100ループ
for (int i = 0;i < 100;i++) {
radian += 0.1f; //ラジアン値を増加
float t = sinf(radian) * 0.5f + 0.5f; //0.0f~1.0fの範囲で指定
hlslpp::float3 p = (end - start) * t + start; //線形補間の計算
//結果を表示
printf_s("t: %f, x: %f, y: %f, z: %f\n",t, static_cast<float>(p.x), static_cast<float>(p.y), static_cast<float>(p.z));
}
system("pause");
return 0;
}
t: 0.549917, x: 1.549917, y: 3.199667, z: 2.099833
t: 0.599335, x: 1.599335, y: 3.397339, z: 2.198669
t: 0.647760, x: 1.647760, y: 3.591040, z: 2.295520
t: 0.694709, x: 1.694709, y: 3.778837, z: 2.389418
t: 0.739713, x: 1.739713, y: 3.958851, z: 2.479425
t: 0.782321, x: 1.782321, y: 4.129285, z: 2.564642
t: 0.822109, x: 1.822109, y: 4.288435, z: 2.644218
t: 0.858678, x: 1.858678, y: 4.434712, z: 2.717356
t: 0.891663, x: 1.891664, y: 4.566654, z: 2.783327
t: 0.920736, x: 1.920736, y: 4.682942, z: 2.841471
t: 0.945604, x: 1.945604, y: 4.782415, z: 2.891207
t: 0.966020, x: 1.966020, y: 4.864079, z: 2.932039
t: 0.981779, x: 1.981779, y: 4.927116, z: 2.963558
t: 0.992725, x: 1.992725, y: 4.970900, z: 2.985450
t: 0.998747, x: 1.998747, y: 4.994990, z: 2.997495
t: 0.999787, x: 1.999787, y: 4.999147, z: 2.999574
t: 0.995832, x: 1.995832, y: 4.983330, z: 2.991665
t: 0.986924, x: 1.986924, y: 4.947695, z: 2.973848
t: 0.973150, x: 1.973150, y: 4.892600, z: 2.946300
t: 0.954649, x: 1.954649, y: 4.818595, z: 2.909297
t: 0.931605, x: 1.931605, y: 4.726418, z: 2.863209
t: 0.904248, x: 1.904248, y: 4.616993, z: 2.808496
t: 0.872853, x: 1.872853, y: 4.491410, z: 2.745705
t: 0.837732, x: 1.837732, y: 4.350926, z: 2.675463
t: 0.799236, x: 1.799236, y: 4.196945, z: 2.598472
t: 0.757751, x: 1.757751, y: 4.031003, z: 2.515502
t: 0.713690, x: 1.713690, y: 3.854761, z: 2.427380
t: 0.667494, x: 1.667494, y: 3.669977, z: 2.334989
t: 0.619625, x: 1.619625, y: 3.478500, z: 2.239250
t: 0.570560, x: 1.570560, y: 3.282241, z: 2.141121
--------------------
中略
--------------------
t: 0.412836, x: 1.412836, y: 2.651346, z: 1.825673
t: 0.364119, x: 1.364119, y: 2.456477, z: 1.728239
t: 0.316760, x: 1.316760, y: 2.267040, z: 1.633520
t: 0.271231, x: 1.271231, y: 2.084925, z: 1.542463
t: 0.227989, x: 1.227989, y: 1.911955, z: 1.455977
続行するには何かキーを押してください . . .
hlslppでは、演算子オーバーロードによって、ベクトルに演算を行うことで、全要素に演算を通してくれるため、先ほどのfloatで実装した演算の部分をベクトル値にするだけで実装できました。
式を覚えなくてもよい
先程から散々式を使用して線形補間を行いましたが、hlslppには線形補間を行うlerpという関数があります。
使い方は
//第一引数:始点
//第二引数:終点
//第三引数:補間係数
//戻り値:補間後の一点
hlslpp::float3 p = hlslpp::lerp(start, end, t);
とします。
#include <iostream>
#include "hlsl++.h" // 全機能をインクルード
int main() {
hlslpp::float3 start = hlslpp::float3(1.0f, 1.0f, 1.0f);//スタート
hlslpp::float3 end = hlslpp::float3(2.0f, 5.0f, 3.0f); //エンド
//線形補間
float radian = 0.0f; //ラジアン値
//お試しで100ループ
for (int i = 0;i < 100;i++) {
radian += 0.1f; //ラジアン値を増加
float t = sinf(radian) * 0.5f + 0.5f; //0.0f~1.0fの範囲で指定
hlslpp::float3 p = hlslpp::lerp(start, end, t); //hlslpp::lerpで線形補間
//結果を表示
printf_s("t: %f, x: %f, y: %f, z: %f\n",t, static_cast<float>(p.x), static_cast<float>(p.y), static_cast<float>(p.z));
}
system("pause");
return 0;
}
t: 0.549917, x: 1.549917, y: 3.199667, z: 2.099833
t: 0.599335, x: 1.599335, y: 3.397339, z: 2.198669
t: 0.647760, x: 1.647760, y: 3.591040, z: 2.295520
t: 0.694709, x: 1.694709, y: 3.778837, z: 2.389418
t: 0.739713, x: 1.739713, y: 3.958851, z: 2.479425
t: 0.782321, x: 1.782321, y: 4.129285, z: 2.564642
t: 0.822109, x: 1.822109, y: 4.288435, z: 2.644218
t: 0.858678, x: 1.858678, y: 4.434712, z: 2.717356
t: 0.891663, x: 1.891664, y: 4.566654, z: 2.783327
t: 0.920736, x: 1.920736, y: 4.682942, z: 2.841471
t: 0.945604, x: 1.945604, y: 4.782415, z: 2.891207
t: 0.966020, x: 1.966020, y: 4.864079, z: 2.932039
t: 0.981779, x: 1.981779, y: 4.927116, z: 2.963558
t: 0.992725, x: 1.992725, y: 4.970900, z: 2.985450
t: 0.998747, x: 1.998747, y: 4.994990, z: 2.997495
t: 0.999787, x: 1.999787, y: 4.999147, z: 2.999574
t: 0.995832, x: 1.995832, y: 4.983330, z: 2.991665
t: 0.986924, x: 1.986924, y: 4.947695, z: 2.973848
t: 0.973150, x: 1.973150, y: 4.892600, z: 2.946300
t: 0.954649, x: 1.954649, y: 4.818595, z: 2.909297
t: 0.931605, x: 1.931605, y: 4.726418, z: 2.863209
t: 0.904248, x: 1.904248, y: 4.616993, z: 2.808496
t: 0.872853, x: 1.872853, y: 4.491410, z: 2.745705
t: 0.837732, x: 1.837732, y: 4.350926, z: 2.675463
t: 0.799236, x: 1.799236, y: 4.196945, z: 2.598472
t: 0.757751, x: 1.757751, y: 4.031003, z: 2.515502
t: 0.713690, x: 1.713690, y: 3.854761, z: 2.427380
t: 0.667494, x: 1.667494, y: 3.669977, z: 2.334989
t: 0.619625, x: 1.619625, y: 3.478500, z: 2.239250
t: 0.570560, x: 1.570560, y: 3.282241, z: 2.141121
--------------------
中略
--------------------
t: 0.412836, x: 1.412836, y: 2.651346, z: 1.825673
t: 0.364119, x: 1.364119, y: 2.456477, z: 1.728239
t: 0.316760, x: 1.316760, y: 2.267040, z: 1.633520
t: 0.271231, x: 1.271231, y: 2.084925, z: 1.542463
t: 0.227989, x: 1.227989, y: 1.911955, z: 1.455977
lerp3.cppと同じ結果になりました。
総括
- 作成したアプリケーションをしょぼいと思わせないための手法として、二点の値の移動の際に線形補間を行う手法が有効。
- 線形補間の式を使用することで、スカラー値でも、ベクトル値でも線形補間が可能。
- sinやcosを利用することで、都合よく補間係数を作ることが出来る。
- プログラミングなので、関数を使用することで式なんて覚えなくても緩やかな移動が実現できる。