จากจุดพิกัดสู่โลก 3D: เรื่องเล่าระบบพิกัดและ Transformation ที่นักพัฒนาเกมต้องรู้
เคยสงสัยไหมว่าทำไมตัวละครในเกมของคุณถึงเดินทะลุพื้น หรือกล้องหมุนแล้วมึนงง? คำตอบอาจซ่อนอยู่ในระบบพิกัดและการแปลงพิกัดที่คุณยังไม่เข้าใจจริง
สารบัญ

เมื่อตัวละครเดินทะลุพื้น: เรื่องราวเริ่มต้นจากจุดพิกัด
เมื่อสามปีก่อน ผมนั่งแก้บั๊กอยู่หน้าจอทั้งคืน ตัวละครในเกม 3D ของผมเดินไปข้างหน้าแล้วลอยขึ้นฟ้า กล้องหมุนไปรอบๆ แล้วภาพกระตุก ราวกับว่าโลกในเกมกำลังแกล้งผม
สาเหตุจริงๆ ไม่ใช่บั๊กของ engine แต่เป็นเพราะผมไม่เข้าใจ game math 3D coordinate system อย่างแท้จริง
บทความนี้จะพาคุณเดินทะลุผ่านระบบพิกัดต่างๆ ที่ใช้ในเกม 3D ทำความเข้าใจ transformation matrix และเห็นภาพว่าโลกในเกมถูกสร้างขึ้นจากตัวเลขอย่างไร
ระบบพิกัดในเกม 3D: มากกว่าแค่ X Y Z
Cartesian Coordinate System: รากฐานของทุกอย่าง
ระบบพิกัดคาร์ทีเซียนคือสิ่งที่นักพัฒนาเกมทุกคนคุ้นเคย มีแกน X (ซ้าย-ขวา), Y (บน-ล่าง), Z (หน้า-หลัง) แต่สิ่งที่หลายคนมองข้ามคือ มือต้นแบบของระบบ
- Left-handed system (เช่น Unity, DirectX): แกน Z ชี้เข้าหาจอ
- Right-handed system (เช่น OpenGL, Godot): แกน Z ชี้ออกจากจอ
ความต่างนี้สำคัญมาก เพราะถ้าคุณย้ายโค้ดจาก Unity ไป Godot โดยไม่แปลงพิกัด ทุกอย่างจะกลับด้าน
# ตัวอย่างการสร้างจุดในระบบ Cartesian
point_3d = {
'x': 5.0, # ขวา
'y': 2.0, # บน
'z': -3.0 # ถอยหลัง (Unity left-handed)
}
Polar Coordinate System: เหมาะกับการหมุนใน 2D
ระบบพิกัดเชิงขั้วใช้ระยะทาง (r) และมุม (θ) ในเกม 3D เราใช้ในกรณีจำกัด เช่น
- การคำนวณตำแหน่งศัตรูรอบผู้เล่น
- การสร้างวงกลมเลือด (health ring)
- การกระจายอนุภาค (particle) เป็นวง
import math
# แปลงจาก Polar เป็น Cartesian
r = 10.0
theta = math.radians(45) # 45 องศา
x = r * math.cos(theta)
y = r * math.sin(theta)
print(f"ตำแหน่ง: ({x:.2f}, {y:.2f})")
# ตำแหน่ง: (7.07, 7.07)
Spherical Coordinate System: โลกกลมในมือคุณ
ระบบพิกัดทรงกลมใช้สามค่า: r (รัศมี), θ (theta - มุมแนวนอน), φ (phi - มุมแนวตั้ง)
นี่คือหัวใจของ:
- การวางกล้องรอบตัวละคร (orbit camera)
- การสร้างท้องฟ้าและดวงดาว
- การคำนวณเสียง 3D (spatial audio)
import math
# แปลงจาก Spherical เป็น Cartesian
r = 15.0
theta = math.radians(60) # มุมแนวนอน
phi = math.radians(30) # มุมแนวตั้ง
x = r * math.sin(phi) * math.cos(theta)
y = r * math.cos(phi)
z = r * math.sin(phi) * math.sin(theta)
print(f"ตำแหน่ง 3D: ({x:.2f}, {y:.2f}, {z:.2f})")
Transformation Matrix: ภาษาลับของโลก 3D
ทุกการเคลื่อนไหวในเกม 3D ล้วนเป็นการคูณเมทริกซ์ ไม่ว่าจะเดิน หมุน หรือย่อ-ขยาย
Translate: การเคลื่อนย้าย
การเคลื่อนย้ายคือการบวกค่าเข้าไปในพิกัด แต่ในโลกเมทริกซ์ เราใช้ homogeneous coordinate เพิ่มแกนที่สี่เข้าไป
Translation Matrix (4x4):
| 1 0 0 Tx |
| 0 1 0 Ty |
| 0 0 1 Tz |
| 0 0 0 1 |
Rotate: การหมุนรอบแกน
การหมุนมีสามแกนหลัก แต่ละแกนมีเมทริกซ์เฉพาะ
import numpy as np
# เมทริกซ์หมุนรอบแกน Y (มุมในหน่วย radian)
def rotation_y(angle):
c, s = np.cos(angle), np.sin(angle)
return np.array([
[ c, 0, s, 0],
[ 0, 1, 0, 0],
[-s, 0, c, 0],
[ 0, 0, 0, 1]
])
# หมุนจุด (1, 0, 0) ไป 90 องศา
point = np.array([1, 0, 0, 1])
rotated = rotation_y(np.radians(90)) @ point
print(f"หลังหมุน: {rotated[:3]}")
# หลังหมุน: [0, 0, 1] (ในระบบ right-handed)
Scale: การย่อ-ขยาย
# เมทริกซ์ย่อ-ขยาย
def scale_matrix(sx, sy, sz):
return np.array([
[sx, 0, 0, 0],
[0, sy, 0, 0],
[0, 0, sz, 0],
[0, 0, 0, 1]
])
ลำดับการคูณสำคัญ!
ในการประกอบ transformation ลำดับการคูณเปลี่ยนผลลัพธ์
# กรณี A: หมุนก่อนแล้วค่อยเคลื่อนย้าย
result_A = translate_matrix @ rotate_matrix @ point
# กรณี B: เคลื่อนย้ายก่อนแล้วค่อยหมุน
result_B = rotate_matrix @ translate_matrix @ point
กรณี A ตัวละครหมุนในที่แล้วเดินไปทิศที่หัน กรณี B ตัวละครเดินไปก่อนแล้ววงกลมหมุนรอบจุดกำเนิด
ความเข้าใจนี้คือสิ่งที่ทำให้ตัวละครเดินไปข้างหน้าถูกทิศ ไม่ใช่เดินทะลุพื้น
World Space กับ Local Space: สองโลกในหนึ่งเกม
World Space: พิกัดสัมบัติของโลกเกม
จุด (0,0,0) ใน world space คือจุดกำเนิดของฉาก ทุก object มีพิกัดสัมบัติที่ไม่เปลี่ยนแปลงไม่ว่ากล้องจะอยู่ที่ไหน
Local Space: พิกัดสัมพัทธ์ของวัตถุ
แขนของตัวละครมี local space ของมันเอง เมื่อตัวละครหมุน แขนก็หมุนตาม
การแปลงระหว่างสองโลก
// Unity: แปลงจาก local space เป็น world space
Vector3 worldPos = transform.TransformPoint(localPos);
// Unity: แปลงจาก world space เป็น local space
Vector3 localPos = transform.InverseTransformPoint(worldPos);
ตัวอย่างจริง: สมมติคุณมีปืนติดอยู่ที่มือตัวละคร ปืนมี local position (0.3, 1.2, 0.5) เมื่อตัวละครหมุน ปืนต้องยังอยู่ที่มือ ไม่ใช่ลอยไปกลางอากาศ
// โครงสร้าง hierarchy ใน Unity
// Player (root)
// └── Arm
// └── Gun
// ปืนอยู่ใน local space ของแขน
// เมื่อแขนหมุน ปืนตามไปอัตโนมัติ
transform.localPosition = new Vector3(0.3f, 0f, 0.5f);
ตัวอย่างโค้ด: การเคลื่อนที่ในโลก 3D
Python: สร้างระบบเคลื่อนที่พื้นฐาน
import numpy as np
class GameObject3D:
def __init__(self, name, position=(0,0,0), rotation=(0,0,0), scale=(1,1,1)):
self.name = name
self.position = np.array(position, dtype=float)
self.rotation = np.array(rotation, dtype=float) # องศา
self.scale = np.array(scale, dtype=float)
def get_world_matrix(self):
"""สร้าง model matrix สำหรับแปลง local space เป็น world space"""
T = self._translation_matrix()
R = self._rotation_matrix()
S = self._scale_matrix()
return T @ R @ S # Scale ก่อน หมุน แล้วค่อยเคลื่อนย้าย
def _translation_matrix(self):
x, y, z = self.position
return np.array([
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
])
def _rotation_matrix(self):
rx, ry, rz = np.radians(self.rotation)
# หมุนรอบแกน Y ก่อน (มาตรฐานในเกม)
cy, sy = np.cos(ry), np.sin(ry)
cx, sx = np.cos(rx), np.sin(rx)
cz, sz = np.cos(rz), np.sin(rz)
rot_y = np.array([
[ cy, 0, sy, 0],
[ 0, 1, 0, 0],
[-sy, 0, cy, 0],
[ 0, 0, 0, 1]
])
rot_x = np.array([
[1, 0, 0, 0],
[0, cx,-sx, 0],
[0, sx, cx, 0],
[0, 0, 0, 1]
])
rot_z = np.array([
[cz,-sz, 0, 0],
[sz, cz, 0, 0],
[ 0, 0, 1, 0],
[ 0, 0, 0, 1]
])
return rot_y @ rot_x @ rot_z
def _scale_matrix(self):
sx, sy, sz = self.scale
return np.array([
[sx, 0, 0, 0],
[0, sy, 0, 0],
[0, 0, sz, 0],
[0, 0, 0, 1]
])
def move_forward(self, distance):
"""เดินไปข้างหน้าตามทิศที่หัน"""
ry = np.radians(self.rotation[1])
forward = np.array([np.sin(ry), 0, np.cos(ry)])
self.position += forward * distance
# ใช้งาน
player = GameObject3D("Player", position=(0, 0, 0), rotation=(0, 45, 0))
player.move_forward(10.0)
print(f"ตำแหน่งหลังเดิน: {player.position}")
# ตำแหน่งหลังเดิน: [7.07, 0, 7.07]
Unity C#: การเคลื่อนที่แบบมืออาชีพ
using UnityEngine;
public class PlayerMovement3D : MonoBehaviour
{
public float moveSpeed = 5f;
public float rotationSpeed = 180f;
void Update()
{
// รับค่าจากผู้เล่น
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// สร้างทิศทางใน local space
Vector3 moveDirection = new Vector3(horizontal, 0f, vertical).normalized;
// แปลงเป็น world space โดยอ้างอิงการหันของตัวละคร
Vector3 worldMoveDirection = transform.TransformDirection(moveDirection);
// เคลื่อนย้ายใน world space
transform.position += worldMoveDirection * moveSpeed * Time.deltaTime;
// หมุนตัวละครด้วย mouse
float mouseX = Input.GetAxis("Mouse X");
transform.Rotate(0f, mouseX * rotationSpeed * Time.deltaTime, 0f);
}
}
สังเกตว่า Unity จัดการเมทริกซ์ให้อัตโนมัติ แต่ถ้าคุณเข้าใจหลักการ คุณจะ debug ได้เร็วขึ้นมาก
Gimbal Lock: ปัญหาที่หลายคนเจอแต่ไม่รู้ชื่อ
อาการของ Gimbal Lock
ลองนึกถึงกล้องในเกมที่หมุนได้รอบทิศ แต่เมื่อคุณมองขึ้นฟ้าจนสุดแล้วกลับลงมา ภาพกระตุก ทิศทางสับสน นี่คือ gimbal lock
สาเหตุคือเมื่อใช้ Euler angles (สามมุม: pitch, yaw, roll) ถ้ามุมหนึ่งถึง 90 องศา แกนอีกสองแกนจะทับซ้อนกัน ทำให้สูญเสียองศาอิสระไปหนึ่งแกน
Quaternion: ทางออกที่สะอาดที่สุด
Quaternion คือตัวเลขสี่ตัวที่แทนการหมุนใน 3D โดยไม่มีปัญหา gimbal lock
// Unity: ใช้ Quaternion แทน Euler angles
// หลีกเลี่ยงแบบนี้ (มี gimbal lock)
transform.rotation = Quaternion.Euler(90f, 0f, 0f);
// ใช้แบบนี้แทน
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
Time.deltaTime * 5f
);
# Python: สร้าง quaternion สำหรับหมุนรอบแกน
import numpy as np
def axis_angle_to_quaternion(axis, angle_degrees):
"""แปลง axis-angle เป็น quaternion"""
angle = np.radians(angle_degrees)
half_angle = angle / 2.0
axis = np.array(axis, dtype=float)
axis = axis / np.linalg.norm(axis) # normalize
w = np.cos(half_angle)
x, y, z = axis * np.sin(half_angle)
return np.array([w, x, y, z])
def quaternion_to_matrix(q):
"""แปลง quaternion เป็น rotation matrix 4x4"""
w, x, y, z = q
return np.array([
[1-2*(y*y+z*z), 2*(x*y-w*z), 2*(x*z+w*y), 0],
[2*(x*y+w*z), 1-2*(x*x+z*z), 2*(y*z-w*x), 0],
[2*(x*z-w*y), 2*(y*z+w*x), 1-2*(x*x+y*y), 0],
[0, 0, 0, 1]
])
# หมุน 90 องศารอบแกน Y
q = axis_angle_to_quaternion([0, 1, 0], 90)
rot_matrix = quaternion_to_matrix(q)
print("Rotation matrix from quaternion:")
print(rot_matrix)
ข้อดีของ Quaternion
- ไม่มี gimbal lock
- การ interpolate การหมุนราบรื่น (slerp)
- ใช้พื้นที่น้อยกว่าเมทริกซ์ (4 ตัวเลข vs 9 ตัวเลข)
- คูณ quaternion รวมการหมุนได้
การประยุกต์ใช้จริง: กรณีศึกษา
กรณีที่ 1: กล้องอิสระแบบ Orbit
// Unity: Orbit camera รอบตัวละคร
public class OrbitCamera : MonoBehaviour
{
public Transform target;
public float distance = 10f;
public float sensitivity = 2f;
private float yaw = 0f;
private float pitch = 30f;
void LateUpdate()
{
yaw += Input.GetAxis("Mouse X") * sensitivity;
pitch -= Input.GetAxis("Mouse Y") * sensitivity;
pitch = Mathf.Clamp(pitch, -80f, 80f); // จำกัดมุมเพื่อไม่ให้ flip
// ใช้ Spherical coordinate แปลงเป็น Cartesian
Quaternion rotation = Quaternion.Euler(pitch, yaw, 0f);
Vector3 offset = rotation * Vector3.forward * distance;
transform.position = target.position - offset;
transform.LookAt(target);
}
}
กรณีที่ 2: การยิงกระสุนในทิศที่กล้องมอง
// Unity: ยิงจากกล้องไปยังจุดที่เล็ง
public class ShootFromCamera : MonoBehaviour
{
public Camera playerCamera;
public GameObject bulletPrefab;
public float bulletSpeed = 50f;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// ทิศทางจากกล้อง (world space)
Vector3 shootDirection = playerCamera.transform.forward;
// สร้างกระสุนที่ตำแหน่งกล้อง
GameObject bullet = Instantiate(
bulletPrefab,
playerCamera.transform.position,
Quaternion.LookRotation(shootDirection)
);
// ให้ความเร็วในทิศที่กล้องมอง
Rigidbody rb = bullet.GetComponent<Rigidbody>();
rb.velocity = shootDirection * bulletSpeed;
}
}
}
สรุป: ทำความเข้าใจให้ลึก แก้บั๊กได้เร็ว
การเข้าใจ game math 3D coordinate system ไม่ใช่แค่ทฤษฎีในตำรา แต่คือเครื่องมือที่ช่วยให้คุณ:
- แก้บั๊กการเคลื่อนที่ได้เร็วขึ้น
- เขียนระบบกล้องและการควบคุมได้สะอาดขึ้น
- ย้ายโค้ดระหว่าง engine ได้โดยไม่สับสน
- สื่อสารกับทีมได้ตรงประเด็น
จำไว้: ทุกอย่างในโลก 3D คือตัวเลขและการคูณเมทริกซ์ เมื่อคุณเข้าใจระบบพิกัดและ transformation คุณจะมองเห็นโครงสร้างที่ซ่อนอยู่ใต้ภาพกราฟิกสวยๆ
พร้อมลงมือทำหรือยัง?
ลองเปิดโปรเจกต์เกมของคุณแล้วตรวจสอบดูว่า:
- คุณใช้ระบบพิกัดแบบไหน (left-handed หรือ right-handed)
- มีจุดไหนที่ใช้ Euler angles แล้วควรเปลี่ยนเป็น quaternion
- ลำดับการคูณเมทริกซ์ถูกต้องหรือไม่
ถ้าคุณเพิ่งเริ่มต้น ลองสร้างวัตถุ 3D ตัวเล็กๆ แล้วเขียนโค้ดควบคุมการเคลื่อนที่ด้วยตัวเอง อย่าพึ่งพึ่ง engine ทุกอย่าง การเขียนเมทริกซ์ด้วยมือสักครั้งจะทำให้คุณเข้าใจระบบลึกซึ้งขึ้น
คุณเคยเจอปัญหา gimbal lock ในเกมของคุณไหม? แชร์ประสบการณ์มาคุยกันได้ในคอมเมนต์ หรือถ้ามีคำถามเรื่องระบบพิกัดและ transformation ฝากไว้ได้เลย ผมจะมาตอบให้ครับ
เนื้อหาที่จัดทำโดยมี AI ช่วยจะมีป้ายกำกับ "เรียบเรียงโดยมี AI ช่วย" เพื่อให้คุณทราบอย่างชัดเจน เราถือว่าความโปร่งใสเรื่องการใช้ AI เป็นสิ่งสำคัญต่อความไว้วางใจของผู้อ่าน
ความคิดเห็น (0)
ยังไม่มีความคิดเห็น — มาเป็นคนแรกกันเถอะ!