3d數(shù)學(xué)

在游戲開發(fā)中,需要使用到向量,三角函數(shù)之類的知識。先學(xué)會里面的概念,后續(xù)應(yīng)用才會容易制作。

1.笛卡爾坐標(biāo)系

2d坐標(biāo)系:x,y

3d坐標(biāo)系:x,y,z

在3d坐標(biāo)系里面有左手坐標(biāo)系和右手坐標(biāo)系。這個可能對人來說有直觀認(rèn)知上的區(qū)別,其實是不相悖。

右手坐標(biāo)系

左手坐標(biāo)系

附帶:

極坐標(biāo)系(polar coordinates)是指在平面內(nèi)由極點(diǎn)、極軸和極徑組成的坐標(biāo)系。在平面上取定一點(diǎn)O,稱為極點(diǎn)。從O出發(fā)引一條射線Ox,稱為極軸。再取定一個單位長度,通常規(guī)定角度取逆時針方向為正。這樣,平面上任一點(diǎn)P的位置就可以用線段OP的長度ρ以及從Ox到OP的角度θ來確定,有序數(shù)對(ρ,θ)就稱為P點(diǎn)的極坐標(biāo),記為P(ρ,θ);ρ稱為P點(diǎn)的極徑,θ稱為P點(diǎn)的極角。

極坐標(biāo)系用于定位和導(dǎo)航。極坐標(biāo)通常被用于導(dǎo)航,作為旅行的目的地或方向可以作為從所考慮的物體的距離和角度。

2.三角學(xué)

1.直角三角形三角函數(shù)概念

a:對邊
b:鄰邊
c:斜邊

勾股定理(畢達(dá)哥拉斯定理)

c=\sqrt{a^2+b^2}

5=\sqrt{3^2+4^2}

∠A 為\theta

sin(\theta) = \frac{a}{c}

正弦:對邊比斜邊。

cos(\theta) = \frac{b}{c}

余弦:鄰邊比斜邊。

正切:對邊比鄰邊。

tan(\theta) = \frac{a}{b}

余切:鄰邊比對邊。

ctan(\theta) = \frac{b}{a}

2.角度弧度

半徑為1的園,全弧長為2πr。

radian=angle*(\pi/180)

angle = radian*(180/\pi)

角度是兩條線段的夾角,弧度是兩條線段和園相交的點(diǎn),在圓弧上走過的距離。

3.誘導(dǎo)公式

1.二倍角

\sin(2\theta)=2\sin(\theta)\cos(\theta)

在閱讀Detour源碼里面有這樣的應(yīng)用。里面將會讀取一個sin cos的一半做乘法。

3.向量

向量計算應(yīng)用于游戲中來計算位置,里面和三角函數(shù)也有關(guān)系。

向量和標(biāo)量不一樣,

標(biāo)量(scale)只表示數(shù)值大小;

向量(矢量、vector)包含方向和數(shù)值大小。

舉例:

速度、位移是向量

速率、長度是標(biāo)量

零向量是指的長度為0,無方向的向量。

1.加法

將兩個向量拼接成平行四邊形,對角向量就是加法的結(jié)果。兩個相同的向量相加,等于將向量長度增加一倍。

\vec{u} + \vec{v} = \vec{a}

2.減法

u向量-v向量,就是指的從u向量目的點(diǎn)指向v向量目標(biāo)點(diǎn)

\vec{v} - \vec{u} = \vec{w} = \vec{a}

3.向量與標(biāo)量乘

向量與標(biāo)量乘法,將向量按照某個長度縮放,一般用于單位向量向前行進(jìn)、縮回多少距離。

4.獲取長度

獲取向量從開始到結(jié)束的距離。從向量得到標(biāo)量。利用勾股定理,向量記錄的就是直角三角形斜邊在x,y軸上的投影長度,斜邊長度就是x,y的平方和的開方。

數(shù)學(xué)公式里面向量長度使用雙豎線引用。

\left||\vec{v}\right||=\sqrt{a^2+b^2}

5.normalized

歸一化需要將向量長度計算出來,然后將向量在各個維度的分量都除以長度。這樣就能得到一個單位向量。歸一化用一條豎線。

\vec{v}_{norm} = \frac{\vec{v}} {\left||\vec{v}\right||}

單位化的向量分量的幾何意義

x=\cos(\theta)

y=\sin(\theta)

這個特性將會應(yīng)用于計算位置。

6. Dot Product

\cos\theta=\frac{\vec{u} \cdot \vec{v}} {\left||\vec{u}\right||\left||\vec{v}\right||}

點(diǎn)乘能計算兩向量的夾角的cos值。cos有一個特點(diǎn),在取值±90°的值域都是>0。在游戲中,這種計算能很快判斷一個怪物是否在玩家身后。這個函數(shù)不能判斷左右,但是能判斷前后。

7.cross produce

\sin\theta=\frac{\vec{u} \times \vec{v}} {\left||\vec{u}\right||\left||\vec{v}\right||}

叉乘用于算左右。sin有個特點(diǎn),取值在0~179°都是>0。用找個特點(diǎn)能判定向量是在自己的左邊還是右邊。

叉乘需要有3個維度才有意義。

\vec{u}\times\vec{v}=\left|\vec{u}\right|\left|\vec{v}\right|\sin(\theta)n

u叉乘v之后結(jié)果是sin*u、v向量的分量。n就是垂直于u、v構(gòu)成平面的垂直法線向量。

8.獲取角度

將向量轉(zhuǎn)換成弧度,向量無需歸一化。

\angle\theta=\arctan(y,z)

9.常用函數(shù)


// 將弧度轉(zhuǎn)換成角度
float radian2angle(float radian) {
    return radian * (180.0f / mathfu::kPi);
}

// 將角度轉(zhuǎn)換成弧度
float angle2radian(float angle) {
    return angle * (mathfu::kPi / 180.0f);
}

// 將向量轉(zhuǎn)換成角度
float vector2angle(mathfu::Vector<float, 2> a) {
    return radian2angle(std::atan2(a.y, a.x));
}

// 角度轉(zhuǎn)換成向量
void angle2vector(float angle, mathfu::Vector<float, 2>& a) {
    auto radian = angle2radian(angle);
    a.y = std::sin(radian);
    a.x = std::cos(radian);
}

// 按照角度,長度,轉(zhuǎn)換一個位置
void movepos(float angle,mathfu::Vector<float,2>& rawPos, float len, mathfu::Vector<float, 2>& out) {
    mathfu::Vector<float, 2> dir;
    angle2vector(angle, dir);
    dir.x *= len;
    dir.y *= len;
    out = rawPos + dir;
}

// 輸出一個向量
void outputvector(const char* tag, mathfu::Vector<float, 2>& a) {
    std::cout << tag << " "<< a.x << "," << a.y << "\n";
}

實例

1.計算圍繞role的怪物

先檢查是否和其他怪物重合

按照±小角度開始偏移嘗試是否能站

#include "mathfu/vector.h"
#include "mathfu/constants.h"
#include <iostream>

float radian2angle(float radian) {
    return radian * (180.0f / mathfu::kPi);
}

float angle2radian(float angle) {
    return angle * (mathfu::kPi / 180.0f);
}

float vector2angle(mathfu::Vector<float, 2> a) {
    return std::atan2(a.y, a.x)*(180.0f / mathfu::kPi);
}

void angle2vector(float angle, mathfu::Vector<float, 2>& a) {
    auto radian = angle2radian(angle);
    a.y = std::sin(radian);
    a.x = std::cos(radian);
}

void outputvector(const char* tag, mathfu::Vector<float, 2>& a) {
    std::cout << tag << "=(" << a.x << "," << a.y << ")\n";
}

// rawBattleCircleFix posSelf: linmath.Vector3{X:11424.3, Y:-311.48605, Z:17336.395}, 
// posEnemy: linmath.Vector3{X:11330.254, Y:-311.48605, Z:17465.838},
// newPos: linmath.Vector3{X:11245.467, Y:-311.48605, Z:17601.525},
// angle: -32, e2sLen: 160.00067
// 測試怪物按照弧形排布在玩家周圍
void test_monster_battle_cricle()
{
    float dst = 160.0f; // 怪物距離
    float bodyRadius = 80.0f;// 怪物的寬度

    mathfu::Vector<float, 2> monsterPos(11424.3f, 17336.395f);
    mathfu::Vector<float, 2> rolePos(11330.254f, 17465.838f);
    auto dir = monsterPos - rolePos;
    auto angle = vector2angle(dir) + 20.0f;

    mathfu::Vector<float, 2> finalDir;
    angle2vector(angle, finalDir);

    finalDir.x *= dst;
    finalDir.y *= dst;

    mathfu::Vector<float, 2> newPos = rolePos + finalDir;
    
    outputvector("monsterPos", monsterPos);
    outputvector("rolePos", rolePos);
    outputvector("newPos",newPos);
}

cmake定義文件

cmake_minimum_required (VERSION 3.2)
project(math_base)

IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
    add_definitions(-DWIN32)
    add_definitions(-DWIN32_LEAN_AND_MEAN)
    add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
    add_definitions(-D_USE_MATH_DEFINES)
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Windows")

include_directories( $ENV{MATHFU_PATH}/include )

file(GLOB_RECURSE all_SRC "src/*.cpp" 
    "src/*.hpp" "src/*.h" 
    "src/*.cc" )

add_executable(test_math ${all_SRC})

target_link_libraries(test_math)

計算的位置,在坐標(biāo)系上的位置

2.計算園外切線

利用三角函數(shù)來計算點(diǎn)對于圓的切線;

void test_fun_fix_circle()
{
    // 圓半徑
    float radius = 8.0f;
    // 圓心位置
    mathfu::Vector<float, 2> circlePos(0, 0);
    // 園外一點(diǎn)
    mathfu::Vector<float, 2> checkPos(10, 12);
    // 園外點(diǎn)指向圓心向量
    mathfu::Vector<float, 2> l = circlePos - checkPos;

    // 計算距離
    auto len = l.Length();

    // 計算出園外點(diǎn)指向圓心向量角度
    float cos = radius / len;
    float radian = std::acos(cos);
    
    // 直角三角形,計算另一角度
    float offsetangle = 90 - radian2angle(radian);
    
    // 將園外點(diǎn)指向圓心向量歸一化,將向量轉(zhuǎn)換成角度
    auto dir = l.Normalized();
    auto oldAngle = vector2angle(l);

    // 將 園外點(diǎn)指向圓心向量角度 + 通過直角三角形方式計算出來的夾角
    // 這個夾角就切線方向
    auto finalAngle = oldAngle + offsetangle;

    // 將角度換算成為向量
    mathfu::Vector<float, 2> finalDir;
    angle2vector(finalAngle, finalDir);

    outputvector("finalDir", finalDir);

    // 計算出切線點(diǎn)離 園外點(diǎn) 的距離,將向量長度設(shè)置成這個距離
    auto al = std::sqrt(len * len - radius * radius);
    finalDir.x = finalDir.x * al;
    finalDir.y = finalDir.y * al;

    // 使用 園外點(diǎn) + 偏移向量就能得到切線過圓邊的點(diǎn)
    auto finalPos = checkPos + finalDir;

    outputvector("finalPos", finalPos);
}

效果:

3.計算某個點(diǎn)是否為三角形內(nèi)

原理在 b站 GAMES101-現(xiàn)代計算機(jī)圖形學(xué)入門-閆令琪 38分鐘處講解了。

叉積是用于控制左右。如果獲取的值域是正數(shù)左邊,負(fù)數(shù)為右邊。

利用的是,三角形三點(diǎn)按照順時針的向量,以及p點(diǎn)的向量的叉乘永遠(yuǎn)是相同的象限的。


void test_triangle_inner()
{
    mathfu::Vector<float, 2> A(8.66992, 6.79278);
    mathfu::Vector<float, 2> B(4.96974, 2.1609);
    mathfu::Vector<float, 2> C(12.31686, 1.78822);
    mathfu::Vector<float, 2> P(8.98936, 4.07754);

    mathfu::Vector<float, 2> P2(11, 5);

    auto u = B - A;
    auto v = C - B;
    auto w = A - C;

    std::cout << "start check P\n";
    auto t = P - A;
    std::cout << t.DotProduct(t, u) << "\n";
    t = P - B;
    std::cout << t.DotProduct(t, v) << "\n";
    t = P - C;
    std::cout << t.DotProduct(t, w) << "\n";


    std::cout << "start check P2\n";
    t = P2 - A;
    std::cout << t.DotProduct(t, u) << "\n";
    t = P2 - B;
    std::cout << t.DotProduct(t, v) << "\n";
    t = P2 - C;
    std::cout << t.DotProduct(t, w) << "\n";

}

//output:
//start check P
//11.3947
//28.8183
//23.5922
//start check P2
//- 0.317775
//43.247
//20.8761
// 如果旋轉(zhuǎn)方向相同,這些向量的sin值的符號都是一致的。
//
// 這個計算不會涉及到開方,所以很好用。
//static inline T DotProductHelper(const Vector<T, 2>& v1,
//                                  const Vector<T, 2>& v2) {
//   return v1[0] * v2[0] + v1[1] * v2[1];
// }

4.計算矩形內(nèi)的一點(diǎn)

原理和三角形檢查一樣。

先將一個矩形做偏移,旋轉(zhuǎn):

取兩個點(diǎn)開始計算:

void test_rect_inner()
{
    mathfu::Vector<float, 2> r1(-4, -5);
    mathfu::Vector<float, 2> r2(-1.401923789, -3.5);
    mathfu::Vector<float, 2> r3(-4.901923789, 2.562177826);
    mathfu::Vector<float, 2> r4(-7.5, 1.062177826);


    mathfu::Vector<float, 2> i(-4.88, -1.49);
    mathfu::Vector<float, 2> j(-8.26, -3.77);

    std::cout << "計算i點(diǎn)\n";
    auto t1 = r2 - r1;
    auto t2 = i - r1;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1,t2) << "\n";
    t1 = r3 - r2;
    t2 = i - r2;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r4 - r3;
    t2 = i - r3;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r1 - r4;
    t2 = i - r4;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";

    std::cout << "計算j點(diǎn)\n";

    t1 = r2 - r1;
    t2 = j - r1;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r3 - r2;
    t2 = j - r2;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r4 - r3;
    t2 = j - r3;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r1 - r4;
    t2 = j - r4;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
}

4.矩陣

1.概念

\left|\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix}\right| \tag{A}

矩陣是按照行列方式排列的數(shù)字。是線性代數(shù)里面中重要的數(shù)學(xué)概念。

描述矩陣一般都是說

r \times c

的矩陣。r是rows行(橫著的條目算1個),c是column(豎著的條目算1個)

方陣就是行和列數(shù)目都是相同的。在3d運(yùn)算中經(jīng)常使用這種方陣

單位矩陣,對角線都是1,其余都是0。

\left|\begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{matrix}\right| \tag{M}

書寫的時候,矩陣都是寫成大寫。M,A,R。手寫的時候,矩陣的括號其實要寫成圓括號(),印刷體中都是[]表示。

向量轉(zhuǎn)換成矩陣可以成為 行矩陣、列矩陣。

2.矩陣運(yùn)算

單位矩陣

1.轉(zhuǎn)置

\left| \begin{matrix} 1 \\ 2 \\ 3 \end{matrix}\right|\tag{M}

\left| \begin{matrix} 1 & 2 & 3\\ \end{matrix} \tag{A}\right|

記作:

M^t=A

2.矩陣與標(biāo)量乘

M*k= k *\left| \begin{matrix} m11 & m12 & m13\\ m21 & m22 & m23\\ m31 & m32 & m33\\ \end{matrix} \right| = k \left| \begin{matrix} k*m11 & k*m12 & k*m13\\ k*m21 & k*m22 & k*m23\\ k*m31 & k*m32 & k*m33\\ \end{matrix} \right|

3.矩陣乘法

公式定義:

(AB)_{ij}=\sum_{k=1}^p a_{i1}b_{1j}+a_{i2}b_{2j}+...+a_{ip}b_{pj}

公式分解:

A*B= \left| \begin{matrix} a11 & a12 & a13\\ a21 & a22 & a23\\ \end{matrix} \right| * \left| \begin{matrix} b11 & b12 \\ b21 & b22 \\ b31 & b32 \\ \end{matrix} \right|=\left| \begin{matrix} a11b11+a12b21+a13b31 & a11b21+ a12b22+a13b23\\ a21b11+a22+b21+a23b31 & a21b12+a22b22+a23b32\\ \end{matrix} \right|

A*B=C

1、當(dāng)矩陣A的列數(shù)(column)等于矩陣B的行數(shù)(row)時,A與B可以相乘。

2、矩陣C的行數(shù)等于矩陣A的行數(shù),C的列數(shù)等于B的列數(shù)。

3、乘積C的第m行第n列的元素等于矩陣A的第m行的元素與矩陣B的第n列對應(yīng)元素乘積之和。

wps矩陣計算

使用矩陣來做位移,旋轉(zhuǎn),縮放操作:

坐標(biāo)系上的位置:

4.克羅內(nèi)克積(Kronecker Product)

克羅內(nèi)克積是兩個任意大小的矩陣間的運(yùn)算,符號記作 。克羅內(nèi)克積也被稱為直積或張量積.以德國數(shù)學(xué)家利奧波德·克羅內(nèi)克命名。

\left| \begin{matrix} a11 & a12 \\ a21 & a22 \\ \end{matrix} \right| \bigotimes \left| \begin{matrix} b11 & b12 \\ b12 & b22 \\ \end{matrix} \right| = \left| \begin{matrix} a11b11 & a11b12 & a12b11 & a12b12 \\ a11b21 & a11b22 & a12b21 & a12b22 \\ a21b11 & a11b12 & a22b21 & a22b22 \\ a21b21 & a21b22 & a22b21 & a22b22 \\ \end{matrix} \right|

5.四元數(shù)

1.概述

四元數(shù)是1843年發(fā)明的。愛爾蘭數(shù)學(xué)家哈密頓(William Rowan Hamilton,1805-1865)。

四元數(shù)運(yùn)算在電動力學(xué)與廣義相對論中有廣泛的應(yīng)用。四元數(shù)可以用來取代張量表示。有時候采用帶有復(fù)數(shù)元素之四元數(shù)會比較容易,導(dǎo)得結(jié)果不為除法代數(shù)之形式。然而亦可結(jié)合共軛運(yùn)算以達(dá)到相同的運(yùn)算結(jié)果。

從概念上來看,就是在數(shù)學(xué)里面定義對于-1開方最后獲取的值。

i=\sqrt{-1}

復(fù)數(shù)是對實數(shù)集合的一種擴(kuò)展。

在游戲開發(fā)應(yīng)用里面,四元數(shù)用于做旋轉(zhuǎn)計算。所以最好先將矩陣搞清楚。復(fù)數(shù)已經(jīng)是一種數(shù)學(xué)工具了,在實際世界里面不能表示什么意義。

四元數(shù)不是專門給3D圖形學(xué)設(shè)計的,但是能用在3D圖形學(xué)里面:

  • 3D相機(jī)控制
  • 壓縮存儲
  • 平滑3D插值

復(fù)數(shù)定義

z=a+b*i

a是實部,b是虛部;

復(fù)數(shù)與標(biāo)量相乘、相除

k*Z_{1}=k*(a+bi)=k*a+(k*b)*i

復(fù)數(shù)加減

Z_{1}=(a+b*i)

Z_{2}=(c+d*i)

Z_{1}+Z_{2}=(a+b*i)+(c+d*i)=((a+c)+(b+d)*i)

Z_{1}-Z_{2}=(a+bi)-(c+di)=(a-c)+(b-d)i

復(fù)數(shù)加法恒等元

復(fù)數(shù)恒等元

(0+0*i)

復(fù)數(shù)除法

Z_{1}/Z_{2}=\frac{(a+b*i)}{(c+d*i)}

推算的時候,需要分子和分母都乘上分母的共軛復(fù)數(shù)。

共軛(Conjugate)

兩個實部相等,虛部互為相反數(shù)的復(fù)數(shù)互為共軛復(fù)數(shù)(conjugate complex number)。(當(dāng)虛部不等于0時也叫共軛虛數(shù))復(fù)數(shù)z的共軛復(fù)數(shù)記作 (z上加一橫,英文中可讀作Conjugate z,z conjugate or z bar),有時也可表示為

Z^*=\overline{Z}

Z=(a+bi)

\overline{Z}=(a-bi)

計算負(fù)數(shù)的模

\left||p\right||=\sqrt{p\overline{p}}

中劃線

\underline{\text{下劃線}}

\overline{\text{上劃線}}

2.歐拉恒等式

我還沒有理解到這個意義。

\cos\varphi+i\sin\varphi=e^{i\varphi}

當(dāng)

\varphi=\pi

推導(dǎo)

e^{i\pi}+1=0

3.歐拉角

4.萬向節(jié)死鎖

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容