view API
包 Luna-Flow/geometry3d/view 把点从世界空间变换到屏幕上。它包含观察(look-at)相机、到视口的透视投影和正交投影、所有光栅化器共用的透视校正深度插值,以及根据焦距推导投影比例的物理相机模型(传感器、镜头、世界单位)。它不做光栅化,对终端和 DOM 一无所知。
import {
"Luna-Flow/geometry3d/core",
"Luna-Flow/geometry3d/view",
"Luna-Flow/linear-algebra/mutable" @la,
}
坐标始终遵循同一约定。在相机空间中 向右、 向上、 向前,因此可见点满足 。在屏幕上, 向右增大、 向下增大,以视口的像素(或终端字符单元)为单位。view 设计推导了本页的所有公式。
相机
Camera3
Camera3 是观察相机:一个眼睛位置、一个目标点和一个近似的向上方向,均位于世界空间中。
pub struct Camera3 {
eye : @mutable.Vector[Double]
target : @mutable.Vector[Double]
up : @mutable.Vector[Double]
}
up 不必垂直于观察方向;相机会由它推导出一个正交标架。它不能与 target - eye 平行。
Camera3::look_at
Camera3::look_at(eye, target, up) 由三个向量构造相机,不做校验。
pub fn Camera3::look_at(@mutable.Vector[Double], @mutable.Vector[Double], @mutable.Vector[Double]) -> Self
Camera3::default
Camera3::default(distance) 把眼睛放在 ,朝向原点,向上方向 。
pub fn Camera3::default(Double) -> Self
它是普通的构造函数,而不是 Default trait 的实现。
Camera3::forward, Camera3::right, Camera3::true_up
这些方法返回相机的正交标架。
pub fn Camera3::forward(Self) -> @mutable.Vector[Double]
pub fn Camera3::right(Self) -> @mutable.Vector[Double]
pub fn Camera3::true_up(Self) -> @mutable.Vector[Double]
它们是
、、 是两两垂直的单位向量,且 。如果 up 与 平行, 和 都是零向量,相机无法使用。
Camera3::view_transform
camera.view_transform() 返回世界到相机的变换,即把眼睛移到原点、把标架 对齐到坐标轴 的刚体运动。
pub fn Camera3::view_transform(Self) -> @core.Transform3
其矩阵的各行为 、、 和 ,其中 是眼睛位置。
Camera3::world_to_camera_point, Camera3::world_to_camera_direction
这些方法把视图变换作用于一个点或一个方向。
pub fn Camera3::world_to_camera_point(Self, @mutable.Vector[Double]) -> @mutable.Vector[Double]
pub fn Camera3::world_to_camera_direction(Self, @mutable.Vector[Double]) -> @mutable.Vector[Double]
点变为 ;方向变为 。每次调用都会重建视图矩阵,因此映射大量点时请保留 view_transform 返回的变换。
test "camera" {
let camera = @view.Camera3::default(4.5)
inspect(camera.forward()[2], content="1")
inspect(camera.right()[0], content="1")
inspect(camera.true_up()[1], content="1")
let p = camera.world_to_camera_point(@core.vec3(1.0, 2.0, 0.0))
inspect(p[0], content="1")
inspect(p[1], content="2")
inspect(p[2], content="4.5")
let d = camera.world_to_camera_direction(@core.vec3(0.0, 0.0, 1.0))
inspect(d[2], content="1")
}
视口与投影后的顶点
Viewport
Viewport 是以像素或终端字符单元计的输出尺寸。
pub struct Viewport {
width : Int
height : Int
}
Viewport::new
Viewport::new(width, height) 构造视口,不做校验。
pub fn Viewport::new(Int, Int) -> Self
ProjectedVertex
ProjectedVertex 是投影后的顶点:以视口单位表示的屏幕坐标 x、y,以及原始点在相机空间中的 ,即 depth。
pub struct ProjectedVertex {
x : Double
y : Double
depth : Double
}
depth 是沿观察方向的距离,而不是规范化的设备深度。越小越近。
ProjectedVertex::new
ProjectedVertex::new(x, y, depth) 构造一个投影后的顶点,例如用于直接驱动光栅化器。
pub fn ProjectedVertex::new(Double, Double, Double) -> Self
投影
PerspectiveProjection
PerspectiveProjection 是到视口的针孔投影,比例 以每单位 对应的像素数计。
pub struct PerspectiveProjection {
viewport : Viewport
scale : Double
}
PerspectiveProjection::new
PerspectiveProjection::new(viewport, scale) 构造该投影。使用 ScientificCamera::to_perspective_projection 可以根据镜头推导 scale。
pub fn PerspectiveProjection::new(Viewport, Double) -> Self
PerspectiveProjection::project_point
projection.project_point(p) 把相机空间中的点映射到屏幕:
pub fn PerspectiveProjection::project_point(Self, @mutable.Vector[Double]) -> ProjectedVertex
其中 为视口尺寸。该点必须位于相机前方()。没有近平面,也没有裁剪: 会产生无穷大, 会产生镜像。
OrthographicProjection
OrthographicProjection 是到视口的平行投影,比例 以每个世界单位对应的像素数计。
pub struct OrthographicProjection {
viewport : Viewport
scale : Double
}
OrthographicProjection::new
OrthographicProjection::new(viewport, scale) 构造该投影。
pub fn OrthographicProjection::new(Viewport, Double) -> Self
OrthographicProjection::project_point
projection.project_point(p) 以 、 和 映射相机空间中的点。
pub fn OrthographicProjection::project_point(Self, @mutable.Vector[Double]) -> ProjectedVertex
project_perspective_vertices, project_orthographic_vertices
这些函数用给定投影投影数组中的每个点。
pub fn project_perspective_vertices(Array[@mutable.Vector[Double]], PerspectiveProjection) -> Array[ProjectedVertex]
pub fn project_orthographic_vertices(Array[@mutable.Vector[Double]], OrthographicProjection) -> Array[ProjectedVertex]
输出与输入长度和顺序相同,因此网格的面下标仍然有效。
test "projections" {
let viewport = @view.Viewport::new(80, 40)
let persp = @view.PerspectiveProjection::new(viewport, 20.0)
let near = persp.project_point(@core.vec3(1.0, 1.0, 2.0))
let far = persp.project_point(@core.vec3(1.0, 1.0, 4.0))
inspect(near.x, content="50")
inspect(near.y, content="10")
inspect(far.x, content="45")
inspect(far.depth, content="4")
let ortho = @view.OrthographicProjection::new(viewport, 20.0)
inspect(ortho.project_point(@core.vec3(1.0, 1.0, 4.0)).x, content="60")
let all = @view.project_perspective_vertices(
[@core.vec3(0.0, 0.0, 1.0), @core.vec3(0.0, 0.0, 2.0)],
persp,
)
inspect(all.length(), content="2")
}
深度插值
interpolate_perspective_depth
interpolate_perspective_depth(p0, p1, p2, b0, b1, b2) 返回投影三角形 中重心坐标为 的屏幕点处的深度。
pub fn interpolate_perspective_depth(ProjectedVertex, ProjectedVertex, ProjectedVertex, Double, Double, Double) -> Double
结果为
当三角形是用 PerspectiveProjection 投影的时,它恰好是 3D 三角形上对应点在相机空间中的深度。如果任一 DEPTH_EPSILON,或者和与零之差在 DEPTH_EPSILON 以内,则回退为线性插值 。重心坐标之和应为 1。
test "perspective-correct depth" {
let a = @view.ProjectedVertex::new(0.0, 0.0, 1.0)
let b = @view.ProjectedVertex::new(10.0, 0.0, 3.0)
let c = @view.ProjectedVertex::new(0.0, 10.0, 1.0)
// halfway along the edge from a to b on the screen
let z = @view.interpolate_perspective_depth(a, b, c, 0.5, 0.5, 0.0)
inspect(z, content="1.5")
// the linear average would have been 2.0
}
物理相机
SensorSpec
SensorSpec 是以毫米计的相机传感器尺寸。
pub struct SensorSpec {
width_mm : Double
height_mm : Double
}
SensorSpec::custom
SensorSpec::custom(width_mm, height_mm) 构造一个传感器;非正的维度会被替换为 1.0。
pub fn SensorSpec::custom(Double, Double) -> Self
SensorSpec::full_frame, SensorSpec::apsc, SensorSpec::medium_format
这些预设分别为 36 × 24 mm、23.5 × 15.6 mm 和 44 × 33 mm。
pub fn SensorSpec::full_frame() -> Self
pub fn SensorSpec::apsc() -> Self
pub fn SensorSpec::medium_format() -> Self
SensorSpec::diagonal_mm
sensor.diagonal_mm() 返回 。
pub fn SensorSpec::diagonal_mm(Self) -> Double
LensSpec
LensSpec 是由以毫米计的焦距给出的镜头。
pub struct LensSpec {
focal_length_mm : Double
}
LensSpec::new, LensSpec::normal_full_frame
LensSpec::new(f) 构造一个镜头,非正的焦距会被替换为 1.0;LensSpec::normal_full_frame() 是 50 mm 镜头。
pub fn LensSpec::new(Double) -> Self
pub fn LensSpec::normal_full_frame() -> Self
LensSpec::horizontal_fov, LensSpec::vertical_fov, LensSpec::diagonal_fov
这些方法返回沿传感器宽度、高度或对角线方向的视场角(弧度)。
pub fn LensSpec::horizontal_fov(Self, SensorSpec) -> Double
pub fn LensSpec::vertical_fov(Self, SensorSpec) -> Double
pub fn LensSpec::diagonal_fov(Self, SensorSpec) -> Double
对传感器尺寸 和焦距 ,每个都等于 。
WorldUnit
WorldUnit 表示一个世界空间单位代表多少米。
pub struct WorldUnit {
meters_per_unit : Double
}
WorldUnit::new, WorldUnit::unitless
WorldUnit::new(m) 构造该单位,非正值会被替换为 1.0;WorldUnit::unitless() 表示每单位一米。
pub fn WorldUnit::new(Double) -> Self
pub fn WorldUnit::unitless() -> Self
WorldUnit::millimeters_per_unit
unit.millimeters_per_unit() 返回 meters_per_unit * 1000。
pub fn WorldUnit::millimeters_per_unit(Self) -> Double
ScientificCamera
ScientificCamera 把一个 Camera3 与传感器、镜头和世界单位捆绑在一起。
pub struct ScientificCamera {
camera : Camera3
sensor : SensorSpec
lens : LensSpec
world_unit : WorldUnit
}
ScientificCamera::new
ScientificCamera::new(camera, sensor, lens, world_unit) 由各部分构造相机。
pub fn ScientificCamera::new(Camera3, SensorSpec, LensSpec, WorldUnit) -> Self
ScientificCamera::auto
ScientificCamera::auto(viewport) 返回 Camera3::default(4.5),搭配全画幅传感器、50 mm 镜头和无量纲的世界单位。
pub fn ScientificCamera::auto(Viewport) -> Self
视口参数目前被忽略。
ScientificCamera::with_camera, ScientificCamera::with_lens
这些方法返回替换了相机或镜头的副本;它们就是实现推拉或变焦动画的方式。
pub fn ScientificCamera::with_camera(Self, Camera3) -> Self
pub fn ScientificCamera::with_lens(Self, LensSpec) -> Self
ScientificCamera::projection_scale
camera.projection_scale(viewport) 返回透视比例 ,其中 为视口高度, 为焦距, 为传感器高度。
pub fn ScientificCamera::projection_scale(Self, Viewport) -> Double
使用该比例时,垂直视场角恰好覆盖视口高度。
ScientificCamera::to_perspective_projection
camera.to_perspective_projection(viewport) 返回 PerspectiveProjection::new(viewport, camera.projection_scale(viewport))。
pub fn ScientificCamera::to_perspective_projection(Self, Viewport) -> PerspectiveProjection
ScientificCamera::focal_length_world_units, ScientificCamera::sensor_height_world_units
这些方法把焦距和传感器高度从毫米换算为世界单位。
pub fn ScientificCamera::focal_length_world_units(Self) -> Double
pub fn ScientificCamera::sensor_height_world_units(Self) -> Double
它们仅供参考:投影比例只取决于比值 ,单位在其中相互抵消。
test "scientific camera" {
let sensor = @view.SensorSpec::full_frame()
let lens = @view.LensSpec::normal_full_frame()
let degrees = lens.vertical_fov(sensor) * 180.0 / @math.PI
inspect((degrees * 10.0).round() / 10.0, content="27")
let camera = @view.ScientificCamera::auto(@view.Viewport::new(640, 480))
inspect(camera.projection_scale(@view.Viewport::new(640, 480)), content="1000")
let mm = @view.ScientificCamera::new(
@view.Camera3::default(4500.0),
sensor,
lens,
@view.WorldUnit::new(0.001),
)
inspect(mm.focal_length_world_units(), content="50")
let tele = camera.with_lens(@view.LensSpec::new(100.0))
inspect(tele.projection_scale(@view.Viewport::new(640, 480)), content="2000")
}