cyphen156
게임 수학 11장 : 외적(3차원 공간의 분석과 응용) 본문
백터의 외적
내적과 다르게 외적의 경우 3차원 이상의 공간에서만 사용가능한데, 그 이유는 외적이 왼손법칙에서 설명했듯이 두 벡터에 대한 외적의 결과가 두 벡터 모두에 직교하는 새로운 벡터를 만들고, 이것이 회전 축을 결정하는 역할을 하기 때문이다.
내적의 결과는 항상 스칼라였지만, 외적의 결과가 방향과 크기를 갖는 벡터이기 때문이라고 다시 설명할 수 있다.
- A · B 내적 연산 기호 = ∣ a ∣∣ b ∣ cosθ == axbx + ayby + azbz
- A X B 외적 연산 기호 = ∣ a ∣∣ b ∣ nsinθ ==
위 수식에서 보면 알 수 있듯 내적은 같은 성분끼리 곱한 뒤 더하여 최종 결과를 만들어내고, 외적은 자기 자신을 제외한 서로 다른 성분끼리 곱한뒤 서로 차감하여 최종 결과를 만들어낸다. 그리고 그 결과는 n Sinθ와 같다.
또한 주의해야 할 점은 내적 연산과 다르게 외적 연산의 경우 교환 법칙마저 성립하지 않는다. 이것은 연산 결과가 앞 인자와 뒤 인자의 순서 차이에 따른 벡터 방향 결정에 영향을 받기 때문이다.
또한 서로 평행하거나 방향이 반대(-) 두 벡터를 외적하면 결과가 항상 영벡터가 된다.
n Sinθ
두 벡터의 외적의 결과는 Sin함수에 비례한다고 하였다. 왜그럴까?
이것은 외적 연산이 벡터의 성분을 수평과 수직 성분으로 분해하여 연산했을 때 수평 성분은 항상 영벡터를 결과 산출되므로 항상 수직성분에 의해 외적연산이 결정되기 때문에, 두 벡터의 수직적 비례 관계에 집중하기 때문이다. 그리고 이 수직적 비례 관계로 인해 두 벡터 외적의 연산의 결과는 두 벡터 곱과 사인함수에 해당하게 된다.(| A || B | * Sinθ )
법선 벡터
앞서 외적의 결과로 새로 생성되는 벡터는 항상 오른손 법칙을 따르며, 인자로 제공된 두 벡터 모두에 직교하는 새로운 벡터를 생성한다고 말했는데 이를 법선 벡터 또는 방향성에 집중하기 때문에 노멀 벡터라고 부른다.
좌우 방향 판별
벡터 외적은 교환 법칙이 성립되지 않기 때문에 이 성질을 이용한다면 3차원 공간 내에서 시야각 안에 있는 사물이 나보다 왼쪽에 있는지, 오른쪽에 있는지를 쉽게 판단할 수 있다.
시선 벡터를 B->라고 항상 가정한다면 물체까지의 거리벡터 V->와의 외적의 결과는| V || B | Sinθ 의 결과가 물체가 시선벡터보다 오른쪽에 있다면 Y->의 결과가 양수, V->가 왼쪽에 있다면 Y->의 결과가 음수로 도출된다.
그런데 결과가 벡터이기 때문에 참 거짓 판별을 위해 노멀 벡터와의 내적을 추가로 활용하여 1두 벡터의 외적 * 노멀 벡터와의 내적의 결과(외적의 결과와 노멀벡터의 내적 연산 또한 결국 두 벡터의 내적 연산이기 때문에)는 스칼라이기 때문에 활용 할 수 있다. 이것을 스칼라 삼중곱이라고 부른다.
외적을 통한 회전행렬의 생성
10장에서는 카메라를 회전시킬때 오일러 각을 통해 뷰행렬을 거쳐 카메라를 회전하였다.
그런데 외적을 이용하면 시선 벡터로부터 물체의 세가지 로컬 축을 구하고, 회전행렬을 얻어낼 수 있다.
우선 로컬 Z축을 구할 때는 두 물체의 거리 만큼을 뺀 후 크기를 1로 정규화 시킨 시선벡터를 생성하면 된다.
이제 여기에 월드 y벡터를 외적한 뒤 정규화 한다면 로컬 x축을 구할 수 잇다.
여기서 로컬 Z축과 로컬 X축을 외적한다면 로컬 Y축을 구할 수 있다.
드물게 카메라가 거꾸로 되어 있거나 수직 위에서 바라보는 경우 특이사항을 감안하여 연산해야 한다.
특히 수직 위에서 바라보고 있는 경우 z벡터가 아래를 바라보므로 외적의 결과가 0이 도출된다.
예제 11_1 항상 물체를 바라보는 카메라의 구현
SoftRenderer3D.cpp
Update3D()함수 수정내용
/// 예제 11_1 항상 물체를 바라보는 카메라의 구현
GameObject& goPlayer = g.GetGameObject(PlayerGo);
CameraObject& camera = g.GetMainCamera();
TransformComponent& playerTransform = goPlayer.GetTransform();
Vector3 inputVector = Vector3(input.GetAxis(InputAxis::XAxis), input.GetAxis(InputAxis::YAxis), input.GetAxis(InputAxis::ZAxis)).GetNormalize();
playerTransform.AddPosition(inputVector * moveSpeed * InDeltaSeconds);
playerTransform.AddPitchRotation(-input.GetAxis(InputAxis::WAxis) * rotateSpeed * InDeltaSeconds);
camera.SetLookAtRotation(playerTransform.GetPosition());
보면 월드 좌표계 기즈모가 돌아가고 있는 것을 확인할 수 있다.
이것은 물체가 회전하는것이 아니라 카메라의 좌표가 변경되면서 회전하고 있다는 것을 의미한다.
백페이스 컬링(BackFace Culling) : 안보이는 면은 잘라내기
컴퓨터의 연산량을 줄이고 프레임을 올리는 가장 획기적인 방법은 그냥 안그리는 것이다. 이것을 컬링이라고 부른다.
그리고 이것을 가장 많이 줄일 수 있는 방법이 플레이어를 항상 따라다니며 뷰 공간을 형성하는 카메라를 기준으로 안그릴 놈들을 잘라내는 것이다. 그림을 그릴 범위를 제한하고, 시야각에서 벗어나면 잘라낸다. 이것이 최적화의 기본기가 된다.
이런 컬링은 다양한 기법들이 있는데 그 중 하나가 백페이스 컬링이다. 메시를 그리는 것 또한 상당한 자원이 소모되기 때문에 카메라와 마주하지 않는 뒷 면의 메시는 보여줄 필요가 없다. 이것을 어떻게 판단하는가?
삼각형을 구성하는 인덱스 버퍼에 표시된 정점 버퍼의 순서에 따라 면의 방향이 결정된다.
예제 11_2 백페이스 컬링
SoftRenderer3D.cpp
공유변수 영역 수정내용
// 게임 로직과 렌더링 로직이 공유하는 변수
bool useBackfaceCurlling = false;
Update3D()함수 수정내용
/// 예제 11_2 백페이스 컬링
if (input.IsReleased(InputButton::Space))
{
useBackfaceCurlling = !useBackfaceCurlling;
}
DrawTriangle3D()함수 수정내용
/// 예제 11_2 백페이스 컬링
if (useBackfaceCurlling)
{
// 백페이스 컬링 뒷면 생략
Vector3 edge1 = (InVertices[1].Position - InVertices[0].Position).ToVector3();
Vector3 edge2 = (InVertices[2].Position - InVertices[0].Position).ToVector3();
Vector3 faceNormal = edge1.Cross(edge2);
Vector3 viewDirection = -Vector3::UnitZ;
if (faceNormal.Dot(viewDirection) >= 0.f)
{
return;
}
}
로드리게스 회전 : 제약 없는 축-각 회전 공식
앞서 오일러 각에는 특정 조건이 발생하면 한 축의 회전 운동이 무시되는 문제가 있었다. 이것을 네번째 축에 고정하고 회전을 차례대로 진행하는 것이 아니라 모든 회전을 한번에 진행한다면 문제가 해결된다. 그래서 이용하게 된 것이 쿼터니언(사원수)이다.
이 쿼터니언 회전의 기본이 되는 것이 로드리게스의 축-각 회전 공식이다.
앞서서 외적의 결과로 두 벡터를 통해 한가지의 새로운 벡터를 생성할 수 있다는 것을 배웠고, 서로다룬 두 벡터를 내적하여 하나의 스칼라로 변환할 수 있다는 것을 배웠다. 이것을 회전 벡터에 대응하여 외적과 내적을 활용한다면, 3차원 공간상의 물체가 회전하는 것을 한 개의 축을 기준으로 특정 방향으로 회전하는 하나의 벡터 즉, 스칼라를 구할 수 있다는 의미가 된다.
두 벡터 원점 O, O'부터 두 점P, P'까지의 거리(U->, U'->)는 모두 같다. 그렇다면 P로 부터 회전한 P'의 좌표는 어떻게 구해야 할까?
우선 회전원의 반지름을 구해야 한다. 원점 O부터 O'(V->)까지의 거리와 U->의 벡터의 차(K-> = U-> - V->)를 구하면 회전원점 O'부터 점 P까지의 벡터를 구할 수 있다.
그리고 나면 반지름이 K->인 단위 원만 남는다. 여기서 삼각함수의 법칙을 응용할 수 있다. 그리고 그 삼각함수의 법칙은 다시 외적으로 쉽게 구할 수 있다. 위에서 말한 두 벡터의 외적을 성분을 각 축으로 쪼개면 평행한 성분은 영벡터니 의미가 없고 수직인 성분만 사용하게 되기 때문이다.
그리고 여기서 구해진 P'까지의 벡터 U'->는 다시 V-> + K->를 하면 구할 수 있다.
완전히 같다고 할 수 없는 이유는 벡터가 방향성을 가지고 있기 때문에 방향이 다르다면 크기가 같을 지라도 서로 다른벡터이기 때문에 종합적으로 K->는 Cos(U-> - V->) + Sin(Cross(norm(V->), U->))가 된다.
그리고 최종적으로 원점 O부터 회전변환한 점 P'까지의 벡터(U'->)는 V-> + K-> CosU-> + (1-Cos)Dot(U->, norm(V->)) + sin(norm(V->, U->))가 도출된다. 이를 로드리게스 회전 공식이라 한다.
예제 11_3 로드리게스 회전
※ 카메라 위치가 변경되면 제대로 관찰이 안되니 주석처리하고 실습하세요
SoftRenderer3D.cpp
공유변수 영역 수정내용
/// 예제 11_3 로드리게스 회전
Vector3 n;
Vector3 right, forward;
float thetaDegree = 0.0f;
Update3D()함수 수정내용
/// 예제 11_3 로드리게스 회전
static Rotator axisRotator;
// 입력에 따른 회전축의 설정
axisRotator.Yaw += -input.GetAxis(InputAxis::XAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Pitch += -input.GetAxis(InputAxis::YAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Roll += -input.GetAxis(InputAxis::ZAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Clamp();
axisRotator.GetLocalAxes(right, n, forward);
thetaDegree = Math::FMod(thetaDegree + input.GetAxis(InputAxis::WAxis) * rotateSpeed * InDeltaSeconds, 360.f);
Render3D()함수 수정내용
///예제 11_3 로드리게스 회전
Matrix4x4 finalMatrix = vMatrix;
DrawMesh3D(mesh, finalMatrix, gameObject.GetTransform().GetScale(), gameObject.GetColor());
DrawMesh3D()함수 추가 내용
헤더파일 추가
void SoftRenderer::DrawMesh3D(const Mesh& InMesh, const Matrix4x4& InMatrix, const Vector3& InScale, const LinearColor& InColor)
Cpp파일 함수 추가
#include "Precompiled.h"
#include "SoftRenderer.h"
#include <random>
using namespace CK::DDD;
/// 예제 11_3 로드리게스 회전
Vector3 n;
Vector3 right, forward;
float thetaDegree = 0.0f;
// 기즈모를 그리는 함수
void SoftRenderer::DrawGizmo3D()
{
auto& r = GetRenderer();
const GameEngine& g = Get3DGameEngine();
// 뷰 기즈모 그리기
std::vector<Vertex3D> viewGizmo = {
Vertex3D(Vector4(Vector3::Zero)),
Vertex3D(Vector4(Vector3::UnitX * _GizmoUnitLength)),
Vertex3D(Vector4(Vector3::UnitY * _GizmoUnitLength)),
Vertex3D(Vector4(Vector3::UnitZ * _GizmoUnitLength)),
};
Matrix4x4 viewMatRotationOnly = g.GetMainCamera().GetViewMatrixRotationOnly();
VertexShader3D(viewGizmo, viewMatRotationOnly);
// 축 그리기
Vector2 v0 = viewGizmo[0].Position.ToVector2() + _GizmoPositionOffset;
Vector2 v1 = viewGizmo[1].Position.ToVector2() + _GizmoPositionOffset;
Vector2 v2 = viewGizmo[2].Position.ToVector2() + _GizmoPositionOffset;
Vector2 v3 = viewGizmo[3].Position.ToVector2() + _GizmoPositionOffset;
r.DrawLine(v0, v1, LinearColor::Red);
r.DrawLine(v0, v2, LinearColor::Green);
r.DrawLine(v0, v3, LinearColor::Blue);
// 회전 축 그리기
static float axisLength = 150.f;
static float planeLength = 30.f;
Vector2 axisTo = (viewMatRotationOnly * n).ToVector2() * axisLength;
Vector2 axisFrom = -axisTo;
Vector2 rightTo = (viewMatRotationOnly * right).ToVector2() * planeLength;
Vector2 rightFrom = -rightTo;
Vector2 forwardTo = (viewMatRotationOnly * forward).ToVector2() * planeLength;
Vector2 forwardFrom = -forwardTo;
r.DrawLine(axisFrom, axisTo, LinearColor::Red);
r.DrawLine(rightFrom, rightTo, LinearColor::DimGray);
r.DrawLine(forwardFrom, forwardTo, LinearColor::DimGray);
}
// 게임 오브젝트 목록
static const std::string PlayerGo = "Player";
// 최초 씬 로딩을 담당하는 함수
void SoftRenderer::LoadScene3D()
{
GameEngine& g = Get3DGameEngine();
// 플레이어
constexpr float playerScale = 100.f;
// 플레이어 설정
GameObject& goPlayer = g.CreateNewGameObject(PlayerGo);
goPlayer.SetMesh(GameEngine::CubeMesh);
//goPlayer.GetTransform().SetPosition(Vector3(-360.f, -250.f, 0.f));
goPlayer.GetTransform().SetPosition(Vector3::Zero);
goPlayer.GetTransform().SetScale(Vector3::One * playerScale);
goPlayer.GetTransform().SetRotation(Rotator(0.f, 0.f, 0.f));
goPlayer.SetColor(LinearColor::Blue);
// 카메라 설정
CameraObject& mainCamera = g.GetMainCamera();
mainCamera.GetTransform().SetPosition(Vector3(0.f, 0.f, 500.f));
mainCamera.GetTransform().SetRotation(Rotator(180.f, 0.f, 0.f));
}
// 게임 로직과 렌더링 로직이 공유하는 변수
/// 예제 11_2 백페이스 컬링
bool useBackfaceCulling = false;
// 게임 로직을 담당하는 함수
void SoftRenderer::Update3D(float InDeltaSeconds)
{
// 게임 로직에서 사용하는 모듈 내 주요 레퍼런스
GameEngine& g = Get3DGameEngine();
const InputManager& input = g.GetInputManager();
// 게임 로직의 로컬 변수
//static float moveSpeed = 500.f;
static float rotateSpeed = 180.f;
/// 예제 11_1 항상 물체를 바라보는 카메라의 구현
/*GameObject& goPlayer = g.GetGameObject(PlayerGo);
CameraObject& camera = g.GetMainCamera();
TransformComponent& playerTransform = goPlayer.GetTransform();
Vector3 inputVector = Vector3(input.GetAxis(InputAxis::XAxis), input.GetAxis(InputAxis::YAxis), input.GetAxis(InputAxis::ZAxis)).GetNormalize();
playerTransform.AddPosition(inputVector * moveSpeed * InDeltaSeconds);
playerTransform.AddPitchRotation(-input.GetAxis(InputAxis::WAxis) * rotateSpeed * InDeltaSeconds);
camera.SetLookAtRotation(playerTransform.GetPosition());*/
/// 예제 11_2 백페이스 컬링
if (input.IsReleased(InputButton::Space))
{
useBackfaceCulling = !useBackfaceCulling;
}
/// 예제 11_3 로드리게스 회전
static Rotator axisRotator;
// 입력에 따른 회전축의 설정
axisRotator.Yaw += -input.GetAxis(InputAxis::XAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Pitch += -input.GetAxis(InputAxis::YAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Roll += input.GetAxis(InputAxis::ZAxis) * rotateSpeed * InDeltaSeconds;
axisRotator.Clamp();
axisRotator.GetLocalAxes(right, n, forward);
thetaDegree = Math::FMod(thetaDegree + input.GetAxis(InputAxis::WAxis) * rotateSpeed * InDeltaSeconds, 360.f);
}
// 애니메이션 로직을 담당하는 함수
void SoftRenderer::LateUpdate3D(float InDeltaSeconds)
{
// 애니메이션 로직에서 사용하는 모듈 내 주요 레퍼런스
GameEngine& g = Get3DGameEngine();
// 애니메이션 로직의 로컬 변수
}
// 렌더링 로직을 담당하는 함수
void SoftRenderer::Render3D()
{
// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
const GameEngine& g = Get3DGameEngine();
auto& r = GetRenderer();
const CameraObject& mainCamera = g.GetMainCamera();
// 배경에 기즈모 그리기
DrawGizmo3D();
// 렌더링 로직의 로컬 변수
const Matrix4x4 vMatrix = mainCamera.GetViewMatrix();
for (auto it = g.SceneBegin(); it != g.SceneEnd(); ++it)
{
const GameObject& gameObject = *(*it);
if (!gameObject.HasMesh() || !gameObject.IsVisible())
{
continue;
}
// 렌더링에 필요한 게임 오브젝트의 주요 레퍼런스를 얻기
const Mesh& mesh = g.GetMesh(gameObject.GetMeshKey());
const TransformComponent& transform = gameObject.GetTransform();
//Matrix4x4 finalMatrix = vMatrix * transform.GetModelingMatrix();
// 메시 그리기
//DrawMesh3D(mesh, finalMatrix, gameObject.GetColor());
///예제 11_3 로드리게스 회전
Matrix4x4 finalMatrix = vMatrix;
DrawMesh3D(mesh, finalMatrix, gameObject.GetTransform().GetScale(), gameObject.GetColor());
// 월드 공간과 뷰 공간에서의 플레이어 위치를 화면에 표시
if (gameObject == PlayerGo)
{
//Vector3 viewPosition = vMatrix * transform.GetPosition();
r.PushStatisticText("Axis : " + n.ToString());
r.PushStatisticText("Degree : " + std::to_string(thetaDegree));
r.PushStatisticText("World: " + transform.GetPosition().ToString());
//r.PushStatisticText("View : " + viewPosition.ToString());
}
}
}
// 메시를 그리는 함수
//void SoftRenderer::DrawMesh3D(const Mesh& InMesh, const Matrix4x4& InMatrix, const LinearColor& InColor)
//{
// size_t vertexCount = InMesh.GetVertices().size();
// size_t indexCount = InMesh.GetIndices().size();
// size_t triangleCount = indexCount / 3;
//
// // 렌더러가 사용할 정점 버퍼와 인덱스 버퍼로 변환
// std::vector<Vertex3D> vertices(vertexCount);
// std::vector<size_t> indice(InMesh.GetIndices());
// for (size_t vi = 0; vi < vertexCount; ++vi)
// {
// vertices[vi].Position = Vector4(InMesh.GetVertices()[vi]);
//
// if (InMesh.HasColor())
// {
// vertices[vi].Color = InMesh.GetColors()[vi];
// }
//
// if (InMesh.HasUV())
// {
// vertices[vi].UV = InMesh.GetUVs()[vi];
// }
// }
//
// // 정점 변환 진행
// VertexShader3D(vertices, InMatrix);
//
// // 삼각형 별로 그리기
// for (int ti = 0; ti < triangleCount; ++ti)
// {
// int bi0 = ti * 3, bi1 = ti * 3 + 1, bi2 = ti * 3 + 2;
// std::vector<Vertex3D> tvs = { vertices[indice[bi0]] , vertices[indice[bi1]] , vertices[indice[bi2]] };
//
// size_t triangles = tvs.size() / 3;
// for (size_t ti = 0; ti < triangles; ++ti)
// {
// size_t si = ti * 3;
// std::vector<Vertex3D> sub(tvs.begin() + si, tvs.begin() + si + 3);
// DrawTriangle3D(sub, InColor, FillMode::Color);
// }
// }
//}
void SoftRenderer::DrawMesh3D(const Mesh& InMesh, const Matrix4x4& InMatrix, const Vector3& InScale, const LinearColor& InColor)
{
size_t vertexCount = InMesh.GetVertices().size();
size_t indexCount = InMesh.GetIndices().size();
size_t triangleCount = indexCount / 3;
// 렌더러가 사용할 정점 버퍼와 인덱스 버퍼로 변환
std::vector<Vertex3D> vertices(vertexCount);
std::vector<size_t> indice(InMesh.GetIndices());
for (size_t vi = 0; vi < vertexCount; ++vi)
{
vertices[vi].Position = Vector4(InMesh.GetVertices()[vi]);
if (InMesh.HasColor())
{
vertices[vi].Color = InMesh.GetColors()[vi];
}
if (InMesh.HasUV())
{
vertices[vi].UV = InMesh.GetUVs()[vi];
}
}
// 정점 변환 진행
for (Vertex3D& v : vertices)
{
float sin = 0.f, cos = 0.f;
Math::GetSinCos(sin, cos, thetaDegree);
Vector3 u = v.Position.ToVector3();
float udotn = u.Dot(n);
Vector3 ncrossu = n.Cross(u);
Vector3 result = Vector3(u * cos + n * ((1.f - cos) * udotn) + ncrossu * sin) * InScale;
v.Position = InMatrix * Vector4(result);
}
// 삼각형 별로 그리기
for (int ti = 0; ti < triangleCount; ++ti)
{
int bi0 = ti * 3, bi1 = ti * 3 + 1, bi2 = ti * 3 + 2;
std::vector<Vertex3D> tvs = { vertices[indice[bi0]] , vertices[indice[bi1]] , vertices[indice[bi2]] };
size_t triangles = tvs.size() / 3;
for (size_t ti = 0; ti < triangles; ++ti)
{
size_t si = ti * 3;
std::vector<Vertex3D> sub(tvs.begin() + si, tvs.begin() + si + 3);
DrawTriangle3D(sub, InColor, FillMode::Color);
}
}
}
// 삼각형을 그리는 함수
void SoftRenderer::DrawTriangle3D(std::vector<Vertex3D>& InVertices, const LinearColor& InColor, FillMode InFillMode)
{
auto& r = GetRenderer();
const GameEngine& g = Get3DGameEngine();
if (useBackfaceCulling)
{
// 백페이스 컬링 ( 뒷면이면 그리기 생략 )
Vector3 edge1 = (InVertices[1].Position - InVertices[0].Position).ToVector3();
Vector3 edge2 = (InVertices[2].Position - InVertices[0].Position).ToVector3();
Vector3 faceNormal = edge1.Cross(edge2);
Vector3 viewDirection = -Vector3::UnitZ;
if (faceNormal.Dot(viewDirection) >= 0.f)
{
return;
}
}
LinearColor finalColor = _WireframeColor;
if (InColor != LinearColor::Error)
{
finalColor = InColor;
}
r.DrawLine(InVertices[0].Position, InVertices[1].Position, finalColor);
r.DrawLine(InVertices[0].Position, InVertices[2].Position, finalColor);
r.DrawLine(InVertices[1].Position, InVertices[2].Position, finalColor);
}
삼중곱
마지막으로 삼중곱이다.
삼중곱이란 벡터들을 외적과 내적을 두번 연속하여 사용하는 경우를 삼중곱이라 부르며, 삼중곱 또한 벡터의 연산이기 때문에 순서가 중요하다.
Dot(U->, Dot (V->, W->)) => 스칼라와 스칼라 내적 => 벡터의 삼중곱이 아니다.- Dot(U->, Cross(V->, W->)) => 벡터와 벡터의 내적 ==> 삼중곱의 결과가 스칼라
Cross (U->, Dot(V->, W->)) => 벡터와 스칼라의 외적 ==> 불가능 하다- Cross (U->, Cross(V->, W->)) => 벡터와 벡터의 외적 ==> 삼중곱의 결과가 벡터
이와 같이 삼중곱은 연산 순서에 따라 가능하기도, 불가능하기도 하며, 도출되는 결과가 모두 달라진다.
스칼라 삼중곱인 2번 케이스의 경우 결과로 평행사변형의 부피가 나오며, 바닥에 어떤 면에서부터 시작하던지 평행사변형의 부피는 달라지지 않으므로, 들어오는 벡터의 순서가 중요하지 않다.
또한 두 벡터가 평행하다면 외적의 결과가 항상 0이기 때문에 삼중곱의 결과가 0이 되고, 두 벡터의 외적의 결과로 만들어진 평면 위에 다른 한 벡터가 존재하는 경우 법선벡터에 직교하게 되므로 내적의 결과가 0이 된다.
이는 다시말해 세 벡터가 선형 독립의 관계를 갖지 못한다는 것을 알 수 있고, 스칼라 삼중곱의 결과가 0이 아니라면 세 벡터는 선형 독립의 관계에 있다는 것을 반증한다.
하지만 벡터 삼중곱인 4번 케이스의 경우 결과가 벡터로 도출된다. 그러므로 순서가 중요하며 다음과 같이 전개할 수 있다. 이를 삼중곱 전개(Triple Produce Expansion) 혹은 라그랑주 공식(Lagrange's formula)이라 부른다.
Cross(U->, Cross(V->, W->)) = Dot(Dot(U->W->), V->) - Dot(Dot(U->V->), W->)
간단히 정리하자면 벡터의 삼중곱은 복잡하니 두개의 내적 삼중곱으로 변환하여 연산할 수 있다는 특징이 있다는 것만 알면 된다.
나중에 기회가 된다면 이에 대해 자세히 알아보자
모든 예제 코드의 소스파일은
GitHub - onlybooks/gamemath: <이득우의 게임 수학> 공식 깃허브 페이지
GitHub - onlybooks/gamemath: <이득우의 게임 수학> 공식 깃허브 페이지
<이득우의 게임 수학> 공식 깃허브 페이지. Contribute to onlybooks/gamemath development by creating an account on GitHub.
github.com
또한 제 개인 깃허브 레포지토리 에 있습니다.
Workspace/C++/GameMath at main · cyphen156/Workspace · GitHub
Workspace/C++/GameMath at main · cyphen156/Workspace
Studying . Contribute to cyphen156/Workspace development by creating an account on GitHub.
github.com
※ 이득우 교수님의 인프런 게임수학강의를 참고하여 작성되었습니다.
집에간다!!
근데 이제 11장 끝냇는데 주말안에 17장까지...? 흠..'';;;;
- 벡터의 내적 연산의 결과는 Cos함수에 비례 하기 때문에 시선 방향벡터에 해당하는 노멀 벡터를 내적한다면 플레이어가 바라보고있는 방향의 왼쪽인지, 오른쪽인지 판단 할 수 있고, 반대의 경우 나보다 뒤에 있는데 왼쪽인지 오른쪽인지를 판단 할 수 있다. [본문으로]
'수학 > 게임수학' 카테고리의 다른 글
게임 수학 12장-1 : 원근 투영(화면에 현실감을 부여하는 변환) - 원근투영과 동차좌표 (0) | 2025.04.04 |
---|---|
게임 수학 10장 : 3차원 공간(입체 공간의 생성)-3차원 트랜스폼과 오일러각 (1) | 2025.03.24 |
게임 수학 9장 : 게임 엔진(콘텐츠를 만드는 기술) (3) | 2025.03.21 |
게임 수학 8장 : 삼각형(물체를 구성하는 가장 작은 단위)-메시와 텍스쳐 (0) | 2025.03.19 |
게임 수학 7장-2 : 내적(벡터 공간의 분석과 응용)-조명과 투영 (0) | 2025.03.17 |