【読書メモ】ゲームアプリの数学
https://gyazo.com/960350bc3449058d078196a4fe1d68a6
Porin.iconここはを読んだ際のメモや、感想などをまとめてるページなり!
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
◆目次
1. この本について
2. 読書メモ START!
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
1. この本について
◆読み始めたきっかけ
(2018/12/18)〜
朝の数学タイムで数学ガールを読み終え、次になにしようかなあとなり
どうせだったらゲームに関係あることがやりたいと思って図書館で探したらちょうど面白そうな本があったので借りてみた
これを見ながらメモを取ってく
◆本の基本情報
▼公式サイト
https://gyazo.com/0b7b3c694aecd1c3e6cfb84cba4a659a
▼概要(公式サイトより引用)
ゲームアプリの開発に必要な数学をこの一冊で! Unityによるサンプルプログラムを実際に動かすことで、数式がどのようにゲームのグラフィックスに適用されるか、直感的に理解できます。
また数学の基本のみならず、
・OpenGL ES 3.2の3Dグラフィックスパイプライン
・GPUアーキテクチャー
・iOS/Androidスマートフォン向け最適化
なども解説。プロのスマホゲーム開発者にとっても有益な一冊です。
▼目次
第1章三角関数
第2章座標系
第3章ベクトル
第4章行列
第5章座標変換
第6章クォータニオン
第7章曲線
第8章ゲームアプリの環境
第9章シェーダー
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
2. 読書メモ START!
Porin.iconここから読んだ部分のメモがスタートするよん!
◆実際に読んだ場所
◆ 第1章 三角関数
◆ 第2章:座標系
◆ 第3章:ベクトル
[[]]
[[]]
(2018/12/18)
◆ 第1章 三角関数
Porin.icon三角関数の記憶怪しかったのでちゃんと説明あってよかった。。。
▼ 直角三角形
斜辺(hypotenuse)、隣辺(adjacent)と対辺(opposite)
三角関数の基本は上記の3つの辺とhypotenuseとadjacentが成す内角の角度θとの関係を使って値を導くこと
https://gyazo.com/f154a5317e39df6a526e2d9e535a489a
▼ ピタゴラスの定理
$ h^2 = a^2 + o^2
▼ sin, cos, tan
直角三角形の辺を2つ選んだ場合の2辺の長さの比率(三角比)はθの角度に依存して変化する
よってθを変化するパラメータとして受け取り、出力される結果の値が変わる関数の形式で捉えることで各辺同士の関係を表現できる
直角三角形には辺が3つあるので、それぞれの比率の組み合わせを考えると3通りの式が必要
それがsin, cos, tan
sin
sinθは内角がθのときの斜辺と対辺の比率
$ sinθ = \frac{opposite}{hypotenuse}
https://gyazo.com/37a6638c0ae869a2d7a270f8bf1d49c2
cos
cosθは内角がθのときの斜辺と隣辺の比
$ cosθ = \frac{adjacent}{hypotenuse}
https://gyazo.com/18e1206119157d12e5c55714027c01b6
tan
隣辺と対辺の比
$ tanθ = \frac{opposite}{adjacent}
https://gyazo.com/0544b4fc236d52dbee75c14b652aa8c3
アークサイン、アークコサイン、アークタンジェント
θをパラメータとして受け取り、各辺同士の比率を返すsin, cos, tan
それに対し、逆関数として、各辺同士の比率をパラメータとして受け取って内角のθの角度を求める関数がアークサイン、アークコサイン、アークタンジェント
$ θ = arcsin(sinθ)
$ θ = arccos(cosθ)
$ θ = arctan(tanθ)
▼ 三角関数の周期性
単位円
直角三角形だとθの条件として$ 0 <= θ <= 90 というシバリができてしまう(内角の和が180のため)
より広い範囲の表現のために、直角三角形を含む円を使って三角関数を表現する(単位円)
半径rが1の円を利用する
r=1のため、斜辺AB=1, 対辺BC=sinθ, 隣辺AC=cosθとなる(三角関数のそれぞれの比率の定義より)
https://gyazo.com/ceb0f8732adad283d69c7708d0209ef7
上記の図にピタゴラスの定理を当てはめると以下の式が導かれる(ピタゴラスの三角恒等式, Pythagorean trigonometric identities)
$ (sinθ)^2 + (cosθ)^2 = 1
余弦定理
直角三角形ではない一般的な三角形に成り立つ性質を見ていく
3辺の長さがそれぞれa, b, cで、bとcの内角がθである三角形を考える
sはqからprへ垂直に下ろした直線とprとの交点
https://gyazo.com/04b941d2e9721daf8989e753bc4a65b8
直角三角形pqsについて、ピタゴラスの定理から
$ a^2 = (b - c \times cosθ)^2 + (c \times sinθ)^2
右辺を展開していく
$ a^2 = (b - c \times cosθ)^2 + (c \times sinθ)^2
$ = b^2 -2bccosθ + c^2(cosθ)^2 + c^2(sinθ)^2
$ =b^2 + c^2((sinθ)^2 + (cosθ)^2) -2bccosθ
ピタゴラスの三角恒等式より$ (sinθ)^2 + (cosθ)^2 = 1のため、
$ = b^2 + c^2 -2bccosθ
よって以下の式が導かれ、これを余弦定理という
$ a^2 = b^2 + c^2 -2bccosθ
(2018/12/21)
◆ 周期性
単位円を使えば、原点を軸に円の半径を回転することで、90度より大きな角度での三角関数の数値も表現できる
例えば90 < θ < 270 のとき、cosθ<0となり、tanθ=sinθ/cosθより、tanθ<0となる
360度以上になったときは、0度に戻ったのと同じ挙動になる
つまり、サイン、コサイン、タンジェントの値は同じ変化の形をずっと繰り返す
これを三角関数の周期性という
Porin.iconこれを
https://gyazo.com/ceb0f8732adad283d69c7708d0209ef7
Porin.iconこうして
https://gyazo.com/d1a692194404c4c3a8308a21634ca58d
Porin.iconこうじゃ!
https://gyazo.com/7960cee507a04c0bdd00d411ba5f2516
▼ ラジアン
以下は実際の三角関数を載せた図
πを使用した表記はラジアンと呼ばれ、角度を表すのに使われる
https://gyazo.com/d5471656c5a2f893a0c4e2bdbff5c07a
単位円上の点D(1, 0)から単位円の円弧上で長さ1の文だけ移動した点をPとする
このときのθの角度が1ラジアンとされる(単位を省略して1と書かれることが多い)
https://gyazo.com/71c1e001b04ae1acd59b1c3797301ac9
▼ なぜ180度のときラジアンはπなのか?
円周の長さを求める際、円周の長さをC、半径の長さをrとしたとき、公式は以下
$ C = 2πr
単位円上のθが360度のとき、点Pは円周の長さCだけ移動したことになる
単位円の場合r=1なのでC=2π
つまり、θが360度のとき、θ=2π radianとなり、radianは省略される慣習なので2πとなる
よってθが180度のときはπ、90度のときはπ/2となる
度で表現するよりもラジアンで表現する方がシンプルなため数学では使われやすい
▼ 加法定理
単位円上で内角がαの位置にある点Pをβだけ回転移動した点P'の座標は(cos(α+β), sin(α+β)) となる
(cos(α+β), sin(α+β))はどうやって求める??:加法定理
https://gyazo.com/fae212ad590bd046da860061823f8596
▼ 加法定理とは
以下の定理(加法定理)によってP'の座標は(cosαcosβ- sinαsinβ, sinαcosβ + cosαsinβ)
$ sin(α \pm β) = sinαcosβ \pm cosαsinβ
$ cos(α \pm β) = cosαcosβ \mp sinαsinβ
回転のアルゴリズム
上記は原点中心の回転のアルゴリズムに使える
回転元の点Pの座標を(cosα, sinα)を(x, y)と置いてみると、ある点からβだけ回転した点の位置は以下となる
$ (xcosβ - ysinβ, ycosβ + xcosβ)
加法定理の証明について
本書には載ってなかったけど気になって個人的に調べた
参考にしたもの
▼ サイン波、コサイン波
周期性を確認するために幾何学の世界から解析学の世界へ
グラフで三角関数を表現する
サイン波
θのラジアンを横軸、サインの値を縦軸としたグラフを書くと、0→1→0→1という値の変動を繰り返す(基本周期)
このとき $ sin(x + 2π) = sinθとなる
一般的に、定数Pに対して $ f(x +P) = f(x)が成立する関数周期関数と呼ぶ
このグラフは以下のような波の繰り返しになりサイン波と呼ぶ
自然界の音波や電波もサイン波で表現できる
なんと周波数とは周期の逆数を示すらしい(2πのサイン波の場合1/2π)
https://gyazo.com/0e5cb47cd570895f759cc1aabdd38094
コサイン波
コサインのあたいは1→0→1→0という周期になっており、サイン波と2/πだけずれている
https://gyazo.com/1f90a843433695d6cd28e580b3776f71
サイン波、コサイン波はパラメータを動かすことで波のカーブを簡単に調整することができる
(イメージ)
https://gyazo.com/c2ea761111423e6285c1819110d80bbf
sinθの絶対値(magnitude)である|sinθ|を使用すると値が負になっていた部分が折り返すため、バウンドするグラフがつくれる
https://gyazo.com/cb845ec31bc46d9fa83dc5930a5e3e9c
サインとコサインのかんけい
このことから以下の関係がわかる
$ cos(θ) = cos(-θ) = sin( \frac{π}{ 2} -θ)
$ sin(θ) = -sin(-θ) = cos( \frac{π}{2} - θ)
▼ Unityサンプル:クリック位置を向くカプセル、バウンドする球
実際のようす
https://gyazo.com/77bbfe25224e9223503de2d639995b4e
カプセルの先端がクリックした場所に向きを変える
求め方
Aがうえ向きのカプセルの中心、Bがクリックした点
角βの分だけBのほうへ向かってカプセルを傾ければ良い
つまり、Aを動かさずにADをABの向きへ時計回りに傾ける
単位円の世界で考えると、ADを-βだけ回転させる
-β= α-90より、αを求めれば-βもわかる
https://gyazo.com/62331e66c8fc95a3405faf29ad63d768
Bの座標とAの座標がわかれば、三角形ABCの隣辺xと対辺yの長さはわかる
そこからアークタンジェントを使えばαの角度がわかる
$ θ = arctan(tanθ)
スクリプト
動かすカプセルは3DオブジェクトのCapsuleゲームオブジェクト
コライダとかがアタッチされてる
https://gyazo.com/464db641bb74c30ea8eda16bdc9cc09c
code:Chapter1.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Chapter01 : MonoBehaviour {
private GameObject capsule;
private float targetAngle = 0f;
public float capsuleRotationSpeed = 4f;
void Start () {
capsule = GameObject.Find("Capsule");
}
void Update () {
// 左クリックを舞うし検出&マウスポインタがEventsystem関連のUI用のゲームオブジェクト常になかった場合
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject()) {
// クリックした座標をコンソールに出力
Debug.Log (string.Format("mousePosition ({0:f}, {1:f})", Input.mousePosition.x, Input.mousePosition.y));
// 回転させる角度
targetAngle = GetRotationAngleByTargetPosition(Input.mousePosition);
}
// transform.eulerAnglesは(x, y, z)軸の周りを反時計回りに回転させる角度を指定する
// 今回は画面に垂直に通っているz軸を中心に回転させる
// Updateの特性とMath.LerpAngleを使用し、線形補間を用いて時々刻々と目的の場所に動かす
// Math.LerpAngleの第3引数に1回のメソッド実行あたりに進む値を指定。
capsule.transform.eulerAngles
= new Vector3(0, 0, Mathf.LerpAngle(capsule.transform.eulerAngles.z, targetAngle, Time.deltaTime * capsuleRotationSpeed));
if (sphere != null) {
sphere.transform.position = new Vector3(sphere.transform.position.x + (capsule.transform.position.x - sphere.transform.position.x) * Time.deltaTime * sphereMagnitudeX,
Mathf.Abs(Mathf.Sin ((Time.time - buttonDownTime) * (Mathf.PI * 2) * sphereFrequency) * sphereMagnitudeY),
0
);
}
}
// マウスクリックした座標を受け取って角度を算出する
float GetRotationAngleByTargetPosition(Vector3 mousePosition) {
// マウス座標はスクリーン座標なので、カプセルの座標をワールド座標からスクリーン座標に変換
Vector3 selfScreenPoint = Camera.main.WorldToScreenPoint(capsule.transform.position);
// 座標の差分を求める
Vector3 diff = mousePosition - selfScreenPoint;
// Mathf.Atan2でアークタンジェントを求め、Mathf.Rad2Degで度に変換している
float angle = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
Debug.Log (string.Format("angle: {0:f}", angle));
// 今求めたのがαの角度なので、そこから-90をして-βの値を求める
float finalAngle = angle - 90f;
Debug.Log (string.Format("finalAngle: {0:f}", finalAngle));
return finalAngle;
}
※少しずつものを動かすのに使えそう(今回使ったのは角度用のLerpAngle
▼ 球体をバウンドさせる処理
クリックした位置に球体が現れ、画面中央に向かいボールがバウンドする
code:Chapter1.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Chapter01 : MonoBehaviour {
private GameObject sphere;
private float buttonDownTime;
public float sphereMagnitudeX = 2.0f;
public float sphereMagnitudeY = 3.0f;
public float sphereFrequency = 1.0f;
void Update () {
// 左クリックを舞うし検出&マウスポインタがEventsystem関連のUI用のゲームオブジェクト常になかった場合
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject()) {
// クリックした座標をコンソールに出力
Debug.Log (string.Format("mousePosition ({0:f}, {1:f})", Input.mousePosition.x, Input.mousePosition.y));
if (sphere != null) {
Destroy(sphere);
sphere = null;
}
// クリックした場所に球体を出現
sphere = SpawnSphereAt(Input.mousePosition);
buttonDownTime = Time.time;
}
if (sphere != null) {
// 球体のx座標を徐々にカプセルのx座標に近づけていく
// 周波数がsphereFrequencyのサイン波にTime.deltaTime - buttonDownTimeから得られるマウスクリック時からの経過時間をパラメータとして与えることで少しずつ波をバウンドの軌道をゆるやかに
// Mathf.Absによって正の値のみを取り、バウンドするように
sphere.transform.position = new Vector3(sphere.transform.position.x + (capsule.transform.position.x - sphere.transform.position.x) * Time.deltaTime * sphereMagnitudeX,
Mathf.Abs(Mathf.Sin ((Time.time - buttonDownTime) * sphereFrequency) * sphereMagnitudeY),
0
);
}
}
GameObject SpawnSphereAt(Vector3 mousePosition) {
GameObject sp = GameObject.CreatePrimitive(PrimitiveType.Sphere);
Vector3 selfScreenPoint = Camera.main.WorldToScreenPoint(capsule.transform.position);
Vector3 position = Camera.main.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, selfScreenPoint.z));
sp.transform.position = new Vector3(position.x, position.y, 0);
return sp;
}
}
(2019/1/2)
◆ 第2章:座標系
▼ デカルト座標系
スクリーン座標
スクリーン座標系は画面左下を原点として画面の解像度に応じたピクセル位置を単位とする2Dのデカルト座標系
スクリーン座標系はカメラ位置に依存して変わる
また、Unityではスクリーン座標系を正規化した座標としてビューポート座標が存在する
正規化=ある値の範囲がとる最小値から最大値の範囲を0から1の範囲に変換すること
つまり、ビューポート座標では画面解像度がどんな大きさでも常に左下が(0, 0)、右上が(1, 1)になる
これを使ってある物体が画面内に収まっているかどうかを画面解像度に関係なく簡単に判定できる
▼ 極座標系
2Dの極座標系
世界の中心に極(pole)と呼ばれる基準点が存在する
極から横向きには極軸(poler axis)と呼ばれる半直線(ray)が伸びている
平面上での点Pの位置は、極軸とPを結ぶ直線rの長さと、極軸とrがなす角度θによって決まる
この二つの数値によりPは(r, θ)と表現される
単位円のrが1で固定ではなく変動するバージョン
なので極座標系の(r, θ)はデカルト座標系での(rcosθ, rsinθ)と等しい
逆に、2Dデカルト座標(x, y)を極座標で表現すると、ピタゴラスの定理と三角関数を用いて$ (\sqrt{x^2 + y^2}, atan2(y, x)となる(atan2はアークタンジェントの代替の、角度を求める関数)
https://gyazo.com/6f2f53837f0d7d7a863af460f67a4cea
極座標系はゲームでは平面上で円の円周上を移動させる場合に利用できる
また、画面中心からの距離に応じて画面に何らかの画像処理を加えたい場合に、デカルト座標系のスクリーン座標から極座標へ変換すると処理が簡単になる
3Dの極座標系=球面座標系
球面座標系上のある点の座標は半径の長さrと、2種類の軸の正の方向からの角度θとφの2つの数値(r, θ, φ)で表される
デカルト座標系で表すと(rsinθcosφ, rcosθ, rsinθsinφ)となる
http://livedoor.blogimg.jp/toushiwomanabudennki/imgs/1/0/1012592b.png
▼ Unityサンプル:三人称視点カメラ
挙動と仕様
デカルト座標系の場合と同じ原点上の球面座標系上を、撮影対象のほうを向きつつ移動するカメラ
https://gyazo.com/7396893a42224ed90b880024587a409e
カメラにアタッチされているスクリプトのpublicプロパティ
https://gyazo.com/857afd22d87030a3d799fdf909d9f6fa
https://gyazo.com/96e019f1a82e7553d4d680cf8758184e
(2019/1/7)
←→で回転できる方向の移動量を方位角θとして、Unity再生中はSpecial Coordinates内のAzimuthに実際の方位角の値がラジアン単位で入る
今回はカメラが見る対象が載っているとみなすx軸とz軸からなる平面からどれだけカメラが上方(y軸方向)へ動いているのかをわかりやすくするため、rが半径、θが方位角なのは同じにして、仰角をφとしており、Elevationにラジアン単位で実際の値が入る
Radiusには球面座標の半径rが入る
Spherical Coordinatesに属する他のプロパティはカメラ位置を制限している
例えば、Max Elevationには90が入っており、90度(真上)に来たらそれ以上動かないようにしている
https://gyazo.com/96e019f1a82e7553d4d680cf8758184e
スクリプト
全体
code:Chapter02.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Chapter02 : MonoBehaviour {
public float rotateSpeed = 1f;
public float scrollSpeed = 200f;
public Transform pivot;
public class SphericalCoordinates
{
public float _radius, _azimuth, _elevation;
public float radius
{
get { return _radius; }
private set
{
_radius = Mathf.Clamp( value, _minRadius, _maxRadius );
}
}
public float azimuth
{
get { return _azimuth; }
private set
{
_azimuth = Mathf.Repeat( value, _maxAzimuth - _minAzimuth );
}
}
public float elevation
{
get{ return _elevation; }
private set
{
_elevation = Mathf.Clamp( value, _minElevation, _maxElevation );
}
}
public float _minRadius = 3f;
public float _maxRadius = 20f;
public float minAzimuth = 0f;
private float _minAzimuth;
public float maxAzimuth = 360f;
private float _maxAzimuth;
public float minElevation = 0f;
private float _minElevation;
public float maxElevation = 90f;
private float _maxElevation;
public SphericalCoordinates(){}
public SphericalCoordinates(Vector3 cartesianCoordinate)
{
_minAzimuth = Mathf.Deg2Rad * minAzimuth;
_maxAzimuth = Mathf.Deg2Rad * maxAzimuth;
_minElevation = Mathf.Deg2Rad * minElevation;
_maxElevation = Mathf.Deg2Rad * maxElevation;
radius = cartesianCoordinate.magnitude;
azimuth = Mathf.Atan2(cartesianCoordinate.z, cartesianCoordinate.x);
elevation = Mathf.Asin(cartesianCoordinate.y / radius);
}
public Vector3 toCartesian
{
get
{
float t = radius * Mathf.Cos(elevation);
return new Vector3(t * Mathf.Cos(azimuth), radius * Mathf.Sin(elevation), t * Mathf.Sin(azimuth));
}
}
public SphericalCoordinates Rotate(float newAzimuth, float newElevation){
azimuth += newAzimuth;
elevation += newElevation;
return this;
}
public SphericalCoordinates TranslateRadius(float x) {
radius += x;
return this;
}
}
public SphericalCoordinates sphericalCoordinates;
// Use this for initialization
void Start () {
sphericalCoordinates = new SphericalCoordinates(transform.position);
transform.position = sphericalCoordinates.toCartesian + pivot.position;
}
// Update is called once per frame
void Update () {
float kh, kv, mh, mv, h, v;
kh = Input.GetAxis( "Horizontal" );
kv = Input.GetAxis( "Vertical" );
bool anyMouseButton = Input.GetMouseButton(0) | Input.GetMouseButton(1) | Input.GetMouseButton(2);
mh = anyMouseButton ? Input.GetAxis( "Mouse X" ) : 0f;
mv = anyMouseButton ? Input.GetAxis( "Mouse Y" ) : 0f;
h = kh * kh > mh * mh ? kh : mh;
v = kv * kv > mv * mv ? kv : mv;
if (h * h > Mathf.Epsilon || v * v > Mathf.Epsilon) {
transform.position
= sphericalCoordinates.Rotate(h * rotateSpeed * Time.deltaTime, v * rotateSpeed * Time.deltaTime).toCartesian + pivot.position;
}
float sw = -Input.GetAxis("Mouse ScrollWheel");
if (sw * sw > Mathf.Epsilon) {
transform.position = sphericalCoordinates.TranslateRadius(sw * Time.deltaTime * scrollSpeed).toCartesian + pivot.position;
}
transform.LookAt(pivot.position);
}
}
Chapter02クラスに入れ子としてSphericalCoordinatesクラスを定義している
このクラスはデカルト座標と球面座標との変換を担うユーティリティクラス
code:Chapter02.cs
public class Chapter02 : MonoBehaviour {
public float rotateSpeed = 1f;
public float scrollSpeed = 200f;
// 球面座標の原点。カメラが見る対象
public Transform pivot;
public class SphericalCoordinates
{
public float _radius, _azimuth, _elevation;
public float radius
{
get { return _radius; }
private set
{
// 半径を一定の範囲内に納める
_radius = Mathf.Clamp( value, _minRadius, _maxRadius );
}
}
public float azimuth
{
get { return _azimuth; }
private set
{
// 方位角は一定の数値間(0-360)を繰り返す(最大値に達したら最小値に戻る)
_azimuth = Mathf.Repeat( value, _maxAzimuth - _minAzimuth );
}
}
public float elevation
{
get{ return _elevation; }
private set
{
_elevation = Mathf.Clamp( value, _minElevation, _maxElevation );
}
}
ShericalCoordinatesクラスのコンストラクタ
デカルト座標の数値を引数で受け取り、球面座標の初期値を設定する
コンストラクタはPythonの__init__みたいなもの
code:Chapter02.cs
public SphericalCoordinates(Vector3 cartesianCoordinate)
{
//Unityの三角関数で使用するラジアンに変換
_minAzimuth = Mathf.Deg2Rad * minAzimuth;
_maxAzimuth = Mathf.Deg2Rad * maxAzimuth;
_minElevation = Mathf.Deg2Rad * minElevation;
_maxElevation = Mathf.Deg2Rad * maxElevation;
// Vector3.magnitudeでデカルト座標原点からの距離がわかる
radius = cartesianCoordinate.magnitude;
// 方位角と仰角
azimuth = Mathf.Atan2(cartesianCoordinate.z, cartesianCoordinate.x);
elevation = Mathf.Asin(cartesianCoordinate.y / radius);
}
方位角(azimuth, θ)と仰角(elevation, φ)を求める
方位角はx座標とz座標を使用したアークタンジェント、仰角は半径rとy座標を利用したアークサインで求められる
https://gyazo.com/96e019f1a82e7553d4d680cf8758184e
code:Chapter02.cs
azimuth = Mathf.Atan2(cartesianCoordinate.z, cartesianCoordinate.x);
elevation = Mathf.Asin(cartesianCoordinate.y / radius);
球面座標からデカルト座標への変換
Unityが主につかうのはデカルト座標なため変換が必要
toCatesianプロパティとして実装(getterのみが存在)
球面の半径radiusを斜辺とする、内角が球面座標の仰角elevationにあたる直角三角形から、コサインを使い、カメラ位置からx軸z軸の平面へ垂直に引いた線とx軸z軸の平面が接する位置から原点までの距離tをもとめる
tは方位角azimuthを内角とする直角三角形の斜辺の長さなので、隣変にあたるx座標はコサイン、対辺にあたるz座標はサインを掛ければもとまる
y座標はtを求めた時と同じように、radiusが斜辺の長さrで内角が仰角elevationの直角三角形でサインを用いる
code:Chapter02.cs
public Vector3 toCartesian
{
get
{
float t = radius * Mathf.Cos(elevation);
return new Vector3(t * Mathf.Cos(azimuth), radius * Mathf.Sin(elevation), t * Mathf.Sin(azimuth));
}
}
Update内
code:Chapter02.cs
void Update () {
float kh, kv, mh, mv, h, v;
kh = Input.GetAxis( "Horizontal" );
kv = Input.GetAxis( "Vertical" );
bool anyMouseButton = Input.GetMouseButton(0) | Input.GetMouseButton(1) | Input.GetMouseButton(2);
mh = anyMouseButton ? Input.GetAxis( "Mouse X" ) : 0f;
mv = anyMouseButton ? Input.GetAxis( "Mouse Y" ) : 0f;
// 最初はクリック位置に向き、その後はマウスドラッグ位置に対応して動くようにする
h = kh * kh > mh * mh ? kh : mh;
v = kv * kv > mv * mv ? kv : mv;
// 何らかの入力があればsphericalCoordinatesクラスのRotateメソッドを呼ぶ
if (h * h > Mathf.Epsilon || v * v > Mathf.Epsilon) {
transform.position = sphericalCoordinates.Rotate(h * rotateSpeed * Time.deltaTime, v * rotateSpeed * Time.deltaTime).toCartesian + pivot.position;
}
}
最後にtransform.LookAtを利用して、物体の方向を向くようにする
code:Chapter02.cs
transform.LookAt(pivot.position);
(2019/1/10)
◆ 第3章:ベクトル
▼ ベクトルの定義
スカラー
大きさと向きがある量のことをベクトルとした場合、大きさしかない普通の数値のことをスカラーという
コンピューターのCPUは大きさと向きを持った量である幾何ベクトルそのものを処理するようにはできておらず、あくまでスカラー量またはスカラーの配列として数ベクトルを処理する
▼ ベクトルの演算
加法・減法・交換法則・結合法則
交換法則
ベクトルの加法にて
a+b = b+a
https://gyazo.com/b8ea0933ec051d3fef355b377da483b2
減法
a+(-b) = a - b
つまりa-b = -(b-a)になるので、反可換となる
また、bに-bを加えた場合は始点に戻る
ゼロベクトル
https://gyazo.com/7b42c502b13065088dd505eb213d502a
結合法則
(a + b) + c = a + (b + c)
https://gyazo.com/73fd4041a95619ed5cb2c8e70a9db84b
スカラー乗法・除法
ベクトルの加法・減法はベクトル同士を計算していたが、ベクトルに対してスカラーを掛け合わせたり、割ったりすることができる
結果は普通の計算と同じ。負になったときは逆方向になる
ベクトルa, bとスカラーcに関しては以下のような分配法則が成立する
$ c(a + b) = ca + cb
https://gyazo.com/2301b8d0ca16112d7bbf7d3f880da5ca
単位ベクトル
これまでは幾何ベクトルについて演算法則を取り扱ったが、数ベクトルにも同様の演算法則が成立する
また、大きさが1のベクトルを単位ベクトルと呼ぶ
ベクトルaと同じ向きの単位ベクトルuはaの長さ|a|でaを除算するか、または|a|の逆数を乗算すれば求められる
$ u = \frac{a}{|a|}
単位ベクトルを求める操作のことをベクトルを正規化するという
ベクトルaの単位ベクトルを$ \hat{a}と記述することもある
UnityのVector3クラスのnormalizedプロパティはベクトルの正規化を反映してる
normalizedを使うと元のベクトルの単位ベクトルを得られる
(2019/1/11)
基底と座標系
3Dのデカルト座標系で、原点に当たる位置を始点としてx軸、y軸、z軸の方向へそれぞれ単位ベクトルi, j, kが伸びているとする
すると、この座標上の任意の点を指す位置ベクトルaは以下の式で表せる
$ a = ax + ay + az
ここでのax, ay, azはそれぞれi, j, kをスカラー量x, y, zで乗じたもの
$ a = xi + yj + zk
i, j, kはそれぞれ単位ベクトルなので、aの終点の座標は(x, y, z)
互いに平行なベクトルや、3Dを表現したいのに同じ平面に3つベクトルがある場合などは座標系を表現できない
線形従属と言う
線形従属でない状態を線形独立という
線形独立で、座標系に表現するのに使えるベクトルを基底ベクトルといい、その組を基底と呼ぶ(図ではi, j, k)
座標系を表す基底を構成する基底ベクトルの数は、ベクトルが満たす加法とスカラー乗法を同様に行える要素からなる集合(ベクトル空間という)において、次元と呼ばれる
https://gyazo.com/63e8b2eedd47390e37fa6107506c378d
法線ベクトル
法線ベクトル(normal vector)とは
2Dの場合にはあるベクトルに垂直なベクトル
3Dの場合はある平面に垂直なベクトル
法線とは垂直な直線を意味する
法線ベクトルは3Dコンピュータグラフィックスでは非常に応用範囲が広く、様々なところで使用される
ベクトルvを、その始点が属する面に垂直なベクトルと、その面上のベクトルとに分解した場合に、垂直なベクトルのことをvの法線成分、面上のベクトルのことをvの接線成分という
大きさ
ベクトルの大きさ(magunitude)を座標軸上で表現可能な数値として表現する
座標軸を使うと位置ベクトルの座標で表される各成分の数値をピタゴラスの定理に当てはめてベクトルの大きさを求められることがわかる
ただ、平方根の演算はコンピュータ上の演算としては実行コストが高くなるので、2つのベクトルの大きさを比較したいだけの場合などではベクトルの大きさの二乗の数値同士を比較すれば良い
UnityではVector3クラスにsqrMagnitudeというベクトルの大きさの二乗を表すプロパティがある
https://gyazo.com/1b10529a20b36894de5312db7e8fd8cf
(2019/1/17)
内積
内積は2つのベクトルを1つのスカラー量に変換する演算
ベクトルaとベクトルbの内積は数式上ではドット(・)で表されa・bと表記される
内積が追加されたベクトル空間を内積空間という
まず$ a・a = |a|^2のようにベクトルそれ自身の内積はそのベクトルの大きさの二乗というスカラー量に等しいと仮定してみる
https://gyazo.com/2a2ecf3727d31b37904d50098691626e
ここで、このような三角形の内角θをなしている2辺にあたるベクトルを、それぞれベクトルa, bとし、もう一つのベクトルをcとすると$ c = a - bである
内積という演算において分配・交換法則が成り立つと仮定してcとcの内積の式を展開してみる
$ |c|^2
$ = (a-b)・(a-b)
$ = a・(a-b)-b・(a-b)
$ = a・a - a・b -b・a+b・b
$ = |a|^2 + |b|^2 -2a・b
余弦定理をこの三角形に当てはめてみると
$ |c|^2 = |a|^2 + |b|^2 -2|a||b|\cosθ
これら二つの式から以下の式が求められる
$ |a|^2 + |b|^2 -2a・b = |a|^2 + |b|^2 -2|a||b|\cosθ
これを解いていくと以下の恒等式が求められる
$ a・b = |a||b|\cosθ
θが0の際cosθ= 1であり、ベクトルの内積がベクトル自身の大きさの二乗に等しいという最初の仮定の式$ a・a = |a|^2も上記の式で表現できる
また、aがbに垂直な場合、cosθ=0なので、a・b = 0になる
座標と内積の関係
2Dの場合の原点Oを基準とした一ベクトルとしてaやbを考えて、aの成分あるいは点Aの座標を(ax, ay), bの成分あるいはBの座標を(bx, by)としてこれらの座標と内積の関係を求めてみる
内積の式を導き出した時に出て来た式にもう一度戻る
$ |c|^2 = |a|^2 + |b|^2 -2a・b
cはピタゴラスの定理から
$ |c|^2 = (ax - bx)^2 + (ay-by)^2
同様に
$ |a|^2 = ax^2 + ay^2
$ |b|^2 = bx^2 + by^2
よって以下の式が得られる(内積の成分表示)
$ (ax - bx)^2 + (ay-by)^2 = ax^2 + ay^2 + bx^2 + by^2 -2a・b
$ a・b = axbx + ayby
3Dの場合はとなる
$ a・b = axbx + ayby + azbz
https://gyazo.com/7343afa341a15a421e53399482c3714d
サインコサインなどの超越関数はコンピュータでの処理コストが高いため、内積を計算で求める場合はこちらの成分表示を使用するのが良い
内積には交換法則・分配法則が成立し、スカラー定数による乗算もできる
ベクトルa, b, cとスカラー定数nを使うと以下が成立する
$ a・b = b・a
$ (a+b)・c = a・c + b・c
$ (na)・b=a・(nb)=n(a・b)
ベクトルの正射影
内積にはベクトルの正射影を求めるのに使える
正射影とは光が当たった時に落ちる影の形みたいなもの
https://gyazo.com/96347ea9eca8ca163e71f8ca8b0ec25b
上の図のように、ベクトルaの終点からベクトルbへ垂直に下ろした線とbの交点からbの始点までの長さは|a|cosθ
bが単位ベクトルの場合(|b|=1)、a・b = |a||b|cosθより、
$ a・b=|a|cosθ
よってaのbへ正射影した長さ(スカラー量)はaと単位ベクトルbの内積を取れば求められることがわかる
また、bと同じ方向のベクトルで、aをbへ正射影した長さを持つベクトルb'はbが単位ベクトルのとき
$ b'=(a・b)b
UnityにはVector3.forwardのようにz軸方向に(0, 0, 1)だけ前進するベクトルを表すプロパティもある
また、正射影を求めるメソッドとしてProjectメソッドがある
内積の応用
ベクトルaとベクトルbの内積(a・b=|a||b|cosθ)は、aとbのなす角θを変化させることによって変化するcosθの値によって変動する
このことから、内積の値の符号と、aとbの長さの積とaとbの内積を比較することで2つのベクトルの位置関係を調べられる
aとbの内積はaとbのなす角θによって以下のように変化してるのがわかる
aとbが全く同じ方向を向いているとき:a・b=|a||b| (cosθ=1)
θ<90度のとき:a・b<|a||b| (0 < cosθ< 1)
θ=90度のとき:a・b = 0 (cosθ=0)
90度<θ<180度 のとき:a・b < 0かつ|a・b| < |a||b| (cosθ<0)
θ=180度のとき:a・b = -|a||b| (cosθ= -1)
※ベクトルaとベクトルbnのなす角度(ラジアン)とcosθのとる実際の値
https://gyazo.com/860d7ac397ab12efc0c137503f8e4f4f
※コサイン波
https://gyazo.com/c03a907d5ecc18b96a79f37566e4d7bd
こういう風に点Aと点Bが為す角度θを求めるときにも使えるぽい
https://gyazo.com/8fb5850dd6e86f4ea4df9990fc561a15
外積
ベクトルには外積という演算もある
外積は2つのベクトルから新しい1つのベクトルを生成する演算
内積は2つのベクトルから1つのスカラー
3次元以上のベクトルについてでないと定義できない
外積によって生成されるベクトルは、2つのベクトルa、bに垂直でa、bからなる平行四辺形の面積に大きさが等しいベクトルになる
2つのベクトル a , bが与えられたときに、それらの両方と直角に交わるベクトルを求めたい場合、外積を計算すると便利
https://gyazo.com/3dac79da34afa2c9f944e4adb2203aae
角度θをなすベクトルaとbとの外積と、その大きさはクロス演算子(×)を用いて以下のように定義される
$ a×b = (|a||b|sinθ)u
$ |a×b|=|a||b|sinθ
uはaに垂直な単位ベクトル
底辺の長さが|b|、高さが|a|sinθの平行四辺形の面積の面積は|a||b|sinθ
aとbが平行でθ=0, つまりsinθ=0のときはa×bはゼロベクトルになる
外積は例えば平面の法線ベクトルを求めるのに使える
3つ頂点からなる三角形の二辺を為すベクトルの外積をとって正規化するとその三角形が存在する平面の法線ベクトルが得られる
https://gyazo.com/00c3af6695f84c84791c9584ccd83453
(2019/1/18)
▼ Unityサンプル:簡易あたり判定
挙動と仕様
ベクトルの外積と内積を使って簡易な当たり判定を実装してみる
こんな感じでカメラとキューブから線が出ている
カメラとキューブの動きはChapter2と同じ
https://gyazo.com/da7e3b9122496107632a82ca4fb3998f
Main Cameraから出ている線の先端がキューブの中のある位置に接触すると緑の色が赤に変わる
https://gyazo.com/0825241a7a2a87120dd2bd86cbcc9218
これは内積と外積を使用してある点が平面の外側にあるかどうかで判定している
点Pが三角形ABCの内側にある場合、それぞれの辺と、各頂点から点Pへの直線との外積(法線ベクトル)を求めると、全ての法線ベクトルは同じ方向を向いている
https://gyazo.com/a86085f4e33074fa2932907d9c5b2618
点Pが三角形ABCの外側にあると、外積として求められるベクトルの一が逆転する
右ねじの法則で考えるとわかりやすい
https://gyazo.com/5af84fe2dcb3acac29837bddda6e16bf
2つのベクトルが同じ向きか逆向きかは内積を用いることで判定できるので、得られたベクトルの1本と、他の2本それぞれとの内積の符号を確認すれば、3つの法線ベクトルが同じ向きなのかそうでないのかを確認できる
同じ向きの場合は内積は正、反対向きのときは負になる(内積の応用参照)
実装コード
code:Chapter03.cs
// キューブの頂点座標を3つ入れて当たり判定の対象の三角形として扱う
private List<Vector3> triangleVertices = new List<Vector3>();
void Start () {
// Meshクラスはverticesプロパティとしてポリゴンメッシュの頂点座標の配列を保持している
// その中から最初の3つを取得する
Mesh mesh = pivot.gameObject.GetComponent<MeshFilter>().mesh;
for (int i = 0; i < mesh.vertices.Length; i++)
{
if (triangleVertices.Count < 3) {
triangleVertices.Add(mesh.verticesi); }
}
}
辺と線のアルファベットは上記にある図を元に割り当ててる
code:Chapter03.cs
void DrawCameraLine() {
// キューブからtransform.forward方向(ワールド座標のz軸方向)に青い線を引く
Debug.DrawLine(pivot.position, pivot.transform.forward * 2, Color.blue);
// カメラから出ている線の惨憺の座標
Vector3 cameraPoint = transform.position + transform.forward * 5;
// 三角形の辺ABのベクトル
Vector3 edge1 = triangleVertices 1 - triangleVertices 0; // カメラから出ている線の先端Pと三角形の点Bを結ぶベクトルBP
Vector3 edge2 = cameraPoint - triangleVertices 1; Vector3 edge3 = triangleVertices 2 - triangleVertices 1; Vector3 edge4 = cameraPoint - triangleVertices 2; Vector3 edge5 = triangleVertices 0 - triangleVertices 2; Vector3 edge6 = cameraPoint - triangleVertices 0; // 外積を求める
Vector3 cp1 = Vector3.Cross (edge1, edge2); // AB × BP
Vector3 cp2 = Vector3.Cross (edge3, edge4);
Vector3 cp3 = Vector3.Cross (edge5, edge6);
// 法線ベクトルcp1と法線ベクトルcp2, cp3の内積を取り符号を確認する
if (Vector3.Dot (cp1, cp2) > 0 && Vector3.Dot (cp1, cp3) > 0) { // 正の場合は平面上に点Pがある
Debug.DrawLine (transform.position, cameraPoint, Color.red);
} else {
Debug.DrawLine (transform.position, cameraPoint, Color.green);
}
}