2025. 12. 5. 07:05ㆍ카테고리 없음
벡터의 내적은 같은 차원의 두 벡터가 주어졌을 때, 벡터를 구성하는 각 성분을 곱한 후 이들을 더해 스칼라를 만들어내는 연산입니다

7.1.1 내적의 성질
내적은 스칼라의 곱셈과 덧셈으로 구성되어 있으므로 교환법칙이 성립합니다.

하지만 결과가 벡터가 아닌 스칼라로 나오는 성질로 인해 결합법칙은 성립하지 않습니다.

내적은 덧셈에 대한 분배 법칙이 성립됩니다.

같은 벡터를 내적하면 벡터의 크기를 제곱한 결과가 나옵니다. 이와 같은 내적의 성질은 모든 차원의 벡터에 동일하게 적용됩니다.

내적은 교환법칙과 분배법칙이 성립하기 때문에, 두 벡터 합의 내적 (u +v) ○ (v + u )은 두 벡터의 크기로 표현할 수 있습니다.

7.1.2 내적과 삼각함수와의 관계
벡터의 내적은 아래와 같이 두 벡터의 사잇각에 대한 cos 함수와 비례하는 특징을 가집니다.

두 벡터 u v의 사잇각이 세타일 때 내적과 cos 함수는 비례하고 다음과 같은 관계를 가집니다.

어떻게 유도되는지 확인을 해보면. 데카르트 좌표계에 세 점 A,B,C로 구성된 삼각형을 그리고 각 점을 마주보는 벡터를 a,b,c,로 원점에 위치한 사잇각은 세타로 지정해봅시다


여기서 벡터 b는 벡터 a에서 벡터 c를 뺀 결과와 동일합니다.
b = C-A = a + (-C)

이때 벡터 b의 크기를 제곱한 수식은 다음과 같이 전개됩니다.

같은 벡터를 내적하면 벡터 크기의 제곱이 되므로 이를 활용해 내적을 전개해봅시다.

1번2번은 동일한 값이므로 다음 식이 성립함을 알 수 있습니다.
a○c = |a||c| cos세타
두 벡터의 크기가 1이면 두 벡터의 내적은 cos 함수가 됩니다.

내적의 성질은 덧셈과 곱셈만 사용해 cos함수 값을 빠르게 계산할 수 있어 유용하게 활용됩니다. 영 벡터가 아닌 경우, 내적 값이 0이 되기 위한 조건은 cos 함수 값이 0이 되는 경우 뿐인데, 이는 두 벡터의 사잇각이 90도 혹은 270(-90)도 인 경우입니다.

따라서 두 벡터의 내적이 0이면 두 벡터는 직교한다는 결론을 내릴 수 있습니다.
벡터 공간에서 직교하는 두 표준벡터 (1,0)과 (0,1)을 내적한 결과는 0이 나옵니다. 두 표준 기저 벡터를 각 세타만큼 회전한 두 기저 벡터도 서로 직교하는데, 아래 와 같이 두 기저 벡터 (cos세타,sin세타) 와 (-sin세타,cos세타)는 항상 직교하기 때문에 두 벡터의 내적은 언제나 0이 나옵니다.

7.1.3 행렬의 곱셈을 내적으로 표현하기
행렬의 곱셈 연산은 내적으로 표현이 가능합니다

행렬과 벡터의 곱셈은 행렬을 구성하는 두 개의 행벡터 (a,b) , (c,d)와 벡터를 구성하는 열벡터 (x,y)의 내적을 표현할 수 있습니다.

행렬의 곱셈 또한 벡터의 내적으로 바꿔 표현할 수 있습니다. 행렬의 곱을 내적을 표현을 한다면 아래와 같습니다.

직교행렬 : 정방행렬을 구성하는 모든 행벡터와 열벡터의 크기가 1이고 벡터들이 서로 직교하는 행렬
a,b,c,d로 구성된 직교행렬을 Q로 지정하면 열벡터와 행벡터를 구성하는 (a,b) , (b,d) , (a,c) , (c,d)의 크기는 1이고 (a,b)와 (c,d)는 서로 직교합니다.
직교행렬이 지니는 특징은 직교행렬의 전치행렬은 역행렬이 된다는 점입니다. 직교 행렬Q와 이의 전치행렬 QT의 곱은 항등행렬이 됩니다.

이는 내적을 사용해 증명할 수 있습니다. 임의의 직교행렬과 이의 전치행렬의 곱을 내적을 사용해 표현해보면

직교행렬의 정의에 의해 벡터 (a,b) 와 (c,d)는 서로 직교하므로 두 벡터의 내적은 0이 나옵니다.
직교 행렬의 정의에 의해 행벡터와 열벡터의 크기는 각각 1이므로, 자신을 내적한 결과는 1이 됩니다.
따라서 직교행렬과 그 전치행렬의 곱은제나 항등행렬을 보장합니다.

회전 변황행렬은 각 행벡터와 열벡터의 크기가 1이고 서로 직교하므로 직교행렬입니다.

강체 변환 : 여러 종류의 선형 변환 중 물체의 형태가 그대로 유지되는 선형변환
선형 변환이 강체 변환이 되기 위한 조건
1. 변화된 기저벡터의 크기는 모두 1이어야 한다.
2. 모든 기저벡터는 서로 직교해야 한다.
3. 행렬식 값이 1이어야 한다.
7.2 시야 판별
벡터의내적은 공간을 분석하는 데 유용하게 사용된다. 캐릭터 시야각 영역에 목표물이 존재하는지 내적을 활용해 파악이 가능합니다.
7.2.1 아퓌 판별
게임 제작 과정에서 공간을 파악하는 데 가장 유용하게 활용되는 사례는 목표물이 캐릭터의앞에 있는지, 뒤에 있는지 구분하는 것입니다. 이는 내적의 부호를 통해 쉽게 파악할 수 있습니다.
벡터의 크기는 언제나 양수이므로 벡터 내적의 부호는 cos 함수가 결정합니다.

두 벡터가 이루는 사잇각의 범위에 따라 결정된다는 것을 알 수 있습니다.
첫 번째 벡터를 캐릭터의 시선 방향으로 생각한다면 두 번째 벡터는 시선 벡터에서부터 멀어 질수록 사잇각이 커질것입니다.

이를 응용하면 내적의 부호만 가지고도 두 벡터가 같은 방향을 향하는지, 아니면 서로 마주보는지를 확인할 수 있습니다.

7.2.2 시야 판별
아래와 같이 캐릭터에 시야각이라는 특성을 부여해봅시다

캐릭터에 부여한 시야각이 b라면 양쪽으로 균등하게 b/2의 시야각이 설정될 것입니다. 나머지는 앞뒤 판별 문제와 동일하게 캐릭터의 시선 방향을 벡터 f로 표시하고, 캐릭터에서 목표물로 향하는 벡터 v를 만든 후 벡터 f와 벡터v의 사잇각을 a로 설정합니다.
내적으로 캐릭터의 시야에 목표물이 탐지되는지 여부를 파악해보면
사이는 사잇각 a가 시야각의 절반인 b/2 보다 작거나 같은지 비교하는 문제가 될 것입니다.
내적에 비례하는 cos함수, 그리고 아래 그림과 같이 0,180도 범위에서는 각이 커질수록 값이 작아지는 성질을 보면
해당 범위에서는 각이 작을수록 cos함수의 값은 반대로 커지는것을 알 수 있습니다.

이를 활용해 다음의 과정으로 캐릭터의 시야에 목표물이 탐지 됐는지 판별해봅니다.

public static bool IsInSight(Vector3 forward, Vector3 toTarget, float fov)
{
float cosHalfFov = Mathf.Cos(fov * 0.5f * Mathf.Deg2Rad);
Vector3 f = forward.normalized;
Vector3 v = toTarget.normalized;
float dot = Vector3.Dot(f, v);
return dot >= cosHalfFov;
}
atan2 함수를 활용해 두 벡터가 이루는 각 a를 구한 후 이를 b/2와 비교해도 동일한 결과를 얻을 수 잇습니다. 하지만 단위 벡터를 구한 후 내적을 계산하는 방식이 atan2를 호출하는 것보다 더 효율적입니다.
public static bool IsInSightAtan2(Vector2 forward, Vector2 toTarget, float fov)
{
float fAngle = Mathf.Atan2(forward.y, forward.x);
float vAngle = Mathf.Atan2(toTarget.y, toTarget.x);
float delta = vAngle - fAngle;
if (delta > Mathf.PI) delta -= Mathf.PI * 2f;
if (delta < -Mathf.PI) delta += Mathf.PI * 2f;
return Mathf.Abs(delta) <= (fov * 0.5f * Mathf.Deg2Rad);
}
using UnityEngine;
public class SoftRenderer : MonoBehaviour
{
public float fov = 60f;
public float moveSpeed = 600f;
public float sightLength = 300f;
Vector3 playerPos = Vector3.zero;
Vector3 targetPos = new Vector3(0f, 100f, 0f);
Vector3 forward = Vector3.up;
Color playerColor = Color.gray;
Color targetColor = Color.blue;
Vector3 targetStart;
Vector3 targetDest;
float duration = 3f;
float elapsedTime;
System.Random rng = new System.Random();
void Start()
{
targetStart = targetPos;
targetDest = GetRandomPos();
Camera.main.orthographic = true;
Camera.main.orthographicSize = 200f;
Camera.main.transform.position = new Vector3(0, 0, -10);
}
void Update()
{
float dt = Time.deltaTime;
elapsedTime += dt;
if (elapsedTime >= duration)
{
targetStart = targetDest;
targetPos = targetDest;
targetDest = GetRandomPos();
elapsedTime = 0f;
}
else
{
float r = elapsedTime / duration;
targetPos = Vector3.Lerp(targetStart, targetDest, r);
}
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"), 0f).normalized;
playerPos += input * moveSpeed * dt;
Vector3 toTarget = targetPos - playerPos;
bool inSight = IsInSight(forward, toTarget, fov);
if (inSight)
{
playerColor = Color.red;
targetColor = Color.red;
}
else
{
playerColor = Color.gray;
targetColor = Color.blue;
}
}
void OnDrawGizmos()
{
if (!Application.isPlaying) return;
DrawPlayer();
DrawTarget();
}
void DrawPlayer()
{
Gizmos.color = playerColor;
float halfRad = fov * 0.5f * Mathf.Deg2Rad;
float sin = Mathf.Sin(halfRad);
float cos = Mathf.Cos(halfRad);
Vector3 left = new Vector3(-sin, cos, 0f) * sightLength;
Vector3 right = new Vector3(sin, cos, 0f) * sightLength;
Gizmos.DrawLine(playerPos, playerPos + left);
Gizmos.DrawLine(playerPos, playerPos + right);
Gizmos.DrawLine(playerPos, playerPos + forward * (sightLength * 0.25f));
Gizmos.DrawSphere(playerPos, 2f);
}
void DrawTarget()
{
Gizmos.color = targetColor;
Gizmos.DrawSphere(targetPos, 2.5f);
}
Vector3 GetRandomPos()
{
float x = Mathf.Lerp(-300f, 300f, (float)rng.NextDouble());
float y = Mathf.Lerp(-200f, 200f, (float)rng.NextDouble());
return new Vector3(x, y, 0f);
}
public static bool IsInSight(Vector3 forward, Vector3 toTarget, float fov)
{
float cosHalfFov = Mathf.Cos(fov * 0.5f * Mathf.Deg2Rad);
Vector3 f = forward.normalized;
Vector3 v = toTarget.normalized;
float dot = Vector3.Dot(f, v);
return dot >= cosHalfFov;
}
}
7.3 조명 효과의 구현
현실 세계와 비슷한 조명 효과를 만들기 위해 고안된 방법으로 램버트 반사모델이 있습니다.
램버트 반사 모델은 컴퓨터 그래픽스에서 표면에서의 조명 모델 계산을 위해 쓰입니다.
아래와 같이 광원이 물체를 향해 직사광선을 발사하는 상황을 보면,
빛을 받아 표면에서 반사되는 빛의 세기는 두 벡터가 만드는 사잇각의 cos 함수에 비례한다는 것이 램버트 반사의 모델의 주요 내용입니다. 표면이 향하는 단위벡터를 n으로 지정하고 표면에서 광원으로 향하는 단위 벡터를 l로 지정해보면
두 벡터의 내적을 사용하면 램버트 반사 모델에 필요한 사잇각의 cos값을 얻을 수 있습니다.

using System.Collections.Generic;
using UnityEngine;
public class SoftRendererLighting : MonoBehaviour
{
public float circleRadius = 50f;
public float lightDistance = 200f;
public float duration = 5f;
struct Pixel
{
public short x;
public short y;
public Vector2 normal;
}
List<Pixel> circlePixels = new List<Pixel>();
Vector2 lightPos;
Color lightColor;
float elapsedTime;
void Start()
{
BuildCircle();
SetupCamera();
}
void Update()
{
elapsedTime += Time.deltaTime;
elapsedTime %= duration;
float rad = (elapsedTime / duration) * Mathf.PI * 2f;
lightPos = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)) * lightDistance;
float h = rad / (Mathf.PI * 2f);
lightColor = Color.HSVToRGB(h, 1f, 1f);
}
void OnDrawGizmos()
{
if (!Application.isPlaying) return;
DrawLight();
DrawShadedCircle();
}
void BuildCircle()
{
circlePixels.Clear();
circlePixels.Capacity = (int)(circleRadius * circleRadius * 4);
for (short x = (short)-circleRadius; x <= circleRadius; x++)
{
for (short y = (short)-circleRadius; y <= circleRadius; y++)
{
if (x * x + y * y <= circleRadius * circleRadius)
{
Vector2 p = new Vector2(x, y);
circlePixels.Add(new Pixel
{
x = x,
y = y,
normal = p.normalized
});
}
}
}
}
void DrawLight()
{
Gizmos.color = lightColor;
Gizmos.DrawSphere(lightPos, 3f);
Gizmos.DrawLine(lightPos, Vector2.zero);
}
void DrawShadedCircle()
{
foreach (var px in circlePixels)
{
Vector2 worldPos = new Vector2(px.x, px.y);
Vector2 l = (lightPos - worldPos).normalized;
float shade = Vector2.Dot(px.normal, l);
if (shade <= 0f)
continue;
Gizmos.color = lightColor * shade;
Gizmos.DrawSphere(worldPos, 0.12f);
}
}
void SetupCamera()
{
var cam = Camera.main;
cam.orthographic = true;
cam.orthographicSize = 200f;
cam.transform.position = new Vector3(0, 0, -10);
}
}
7.4 투영 벡터
벡터 내적은 어떤 벡터를 다른 벡터에 직교 투영하는 용도로 사용됩니다.
예를 들어 아래 그림과 같이 카메라가 만드는 공간을 분석할 때 카메라와 물체 사이의 거리 외에도 카메라에서 물체까지의 깊이 값이 필요한 경우도 있습니다.
이 경우 내적을 활용한 투영 벡터를 구하는 공식이 활용됩니다.

임의의 두 벡터 u와 v가 주어졌을 때 벡터 u를 v에 투영하는 상황을 생각해보면, 투영한 벡터를 v`로 표기했는데 투영한 벡터는 대상 벡터 v와 방향이 같기 때문입니다.

따라서 투영한 벡터 v`의 크기를 알고 있다면 아래의 그림처럼 v를 정규화시킨 단위 벡터 v^를 구하고 그 크기를 곱하면 투영된 벡터를 얻을 수 있습니다.

이를 수식으로 표현하면 다음과 같습니다.


투영할 벡터 v의 크기가 1이면 위 식은 다음과 같이 단순하게 정리됩니다.
