view API
パッケージ Luna-Flow/geometry3d/view は、点をワールド空間から画面へ運びます。ルックアット(look-at)カメラ、ビューポートへの透視投影と正射影、すべてのラスタライザが使う透視補正された深度補間、そして焦点距離から投影スケールを導く物理カメラモデル(センサー、レンズ、ワールド単位)を含みます。ラスタライズは行わず、ターミナルや DOM については何も知りません。
import {
"Luna-Flow/geometry3d/core",
"Luna-Flow/geometry3d/view",
"Luna-Flow/linear-algebra/mutable" @la,
}
座標は一貫して 1 つの規約に従います。カメラ空間では が右、 が上、 が前を向くので、見える点は です。画面上では、ビューポートのピクセル(またはターミナルのセル)単位で は右へ、 は下へ増えます。このページの式はすべて 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) は 3 つのベクトルから検証なしでカメラを作ります。
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 トレイトの実装ではありません。
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 はビューポートへのピンホール投影で、スケール は の 1 単位あたりのピクセル数です。
pub struct PerspectiveProjection {
viewport : Viewport
scale : Double
}
PerspectiveProjection::new
PerspectiveProjection::new(viewport, scale) は投影を作ります。レンズから scale を導くには ScientificCamera::to_perspective_projection を使います。
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 の場合、または和が 0 から 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 はワールド空間の 1 単位が何メートルを表すかを示します。
pub struct WorldUnit {
meters_per_unit : Double
}
WorldUnit::new, WorldUnit::unitless
WorldUnit::new(m) は単位を作り、正でない値を 1.0 に置き換えます。WorldUnit::unitless() は 1 単位あたり 1 メートルです。
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) は、フルサイズセンサー、50 mm レンズ、単位なしのワールド単位を備えた Camera3::default(4.5) を返します。
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")
}