// 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 }