Files
ngzz-mc/internal/camera/camera.go
NianGao Dev 108c803a75 fix(camera): 视图矩阵俯仰符号翻转——视角不跟随鼠标上下移动的根因
视图矩阵误用正向旋转 R 代替 Rᵀ(RotX(-pitch)·RotY(-yaw)),
低头渲染成抬头、抬头渲染成低头;yaw 因 RotY 对称性侥幸正确。
新增 TestViewPitchSteep/Up/Forward 验证陡俯仰与方向映射。
2026-08-16 10:08:33 +08:00

74 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package camera 第一人称相机:欧拉角(yaw/pitch)与视图/投影矩阵(渲染.md 配套)。
//
// 约定:yaw=0 朝向 -Z(GL 习惯),pitch>0 抬头;视图矩阵为列主序(GL 风格)。
package camera
import "math"
// Camera 第一人称相机。
type Camera struct {
Pos [3]float64 // 眼睛位置
Yaw float64 // 水平角(弧度)
Pitch float64 // 俯仰角(弧度)
FOV float64 // 垂直视场角(度)
}
// New 创建相机。
func New(pos [3]float64, yaw, pitch, fov float64) *Camera {
return &Camera{Pos: pos, Yaw: yaw, Pitch: pitch, FOV: fov}
}
// Forward 前方向(单位向量)。
func (c *Camera) Forward() [3]float64 {
cp, sp := math.Cos(c.Pitch), math.Sin(c.Pitch)
cy, sy := math.Cos(c.Yaw), math.Sin(c.Yaw)
return [3]float64{-sy * cp, sp, -cy * cp}
}
// Right 右方向(单位向量,水平)。
func (c *Camera) Right() [3]float64 {
cy, sy := math.Cos(c.Yaw), math.Sin(c.Yaw)
return [3]float64{cy, 0, -sy}
}
// View 视图矩阵(列主序 4×4,GL 用)。
// 推导:相机旋转 R = RotY(yaw)·RotX(pitch)(世界→相机为 Rᵀ = RotX(-pitch)·RotY(-yaw))。
// 视图 = Rᵀ·T(-pos)。
// 旧实现误用正向旋转 R:俯仰角符号被翻转(低头渲染成抬头),
// 表现是「视角不跟随鼠标上下移动」(上下视角失效根因)。
func (c *Camera) View() [16]float32 {
cp, sp := math.Cos(c.Pitch), math.Sin(c.Pitch)
cy, sy := math.Cos(c.Yaw), math.Sin(c.Yaw)
// V = RotX(-pitch)·RotY(-yaw),按列主序存储(列 0/1/2 为相机基在世界的分量)
var m [16]float32
m[0] = float32(cy) // col0.x
m[1] = float32(sp * sy) // col0.y
m[2] = float32(cp * sy) // col0.z
m[4] = 0 // col1.x
m[5] = float32(cp) // col1.y
m[6] = float32(-sp) // col1.z
m[8] = float32(-sy) // col2.x
m[9] = float32(sp * cy) // col2.y
m[10] = float32(cp*cy) // col2.z
m[15] = 1
// 平移:t = -V·pos
px, py, pz := float32(c.Pos[0]), float32(c.Pos[1]), float32(c.Pos[2])
m[12] = -(m[0]*px + m[4]*py + m[8]*pz)
m[13] = -(m[1]*px + m[5]*py + m[9]*pz)
m[14] = -(m[2]*px + m[6]*py + m[10]*pz)
return m
}
// Projection 透视投影矩阵(列主序,GL 用)。
func (c *Camera) Projection(aspect, near, far float64) [16]float32 {
f := 1.0 / math.Tan(c.FOV*math.Pi/360.0)
var m [16]float32
m[0] = float32(f / aspect)
m[5] = float32(f)
m[10] = float32((far + near) / (near - far))
m[11] = -1
m[14] = float32(2 * far * near / (near - far))
return m
}