由於已經為網格模型(多面體)實現了計算包圍球的方法,基於網格的包圍球構造天球,使用經緯座標,便可將相機定位於天球球面上的任意一點,亦即將三維空間裡的相機定位問題轉化為二維球面定位問題,從而將問題的難度降低一個數量級。在解決該問題之前,需要先熱 POV Ray 場景語言的身。
天球
天球是網格模型的外接球的放大。假設網格模型外接球的中心為 bs_center
,半徑為 bs_r
,放大倍數為 n
,參考文獻 [1],可用 POV Ray 場景語言可將天球表示為
// 天球
sphere {
bs_center, bs_r * n
texture {pigment {color rgbt <0, 0, 1, 0.75>}}
hollow
}
例如四面體
mesh2 {
vertex_vectors {
4,
<0, 0, 0>, <1, 0, 0>, <0, 1, 0>, <0, 0, -1>
}
face_indices {
4,
<0, 1, 2>, <0, 1, 3>, <1, 2, 3>, <0, 2, 3>
}
texture {pigment {color Red}}
}
其包圍球中心為 <0.25, 0.25, -0.25>
,半徑約為 0.829
,若放大倍數為 3,則天球可表示為
#declare bs_center = <0.25, 0.25, -0.25>;
#declare bs_r = 0.829;
#declare n = 3;
#declare ss_r = bs_r * n;
sphere {
bs_center, ss_r
texture {pigment {color rgbt <0, 0, 1, 0.75>}}
hollow
}
在場景中繪製天球,僅僅是便於直觀呈現網格模型的觀察空間。但是現在還不太好方便觀察天球以及網格,因為相機和光源的定位問題尚未得到妥善解決。
座標系
為了能在直覺上把握網格模型、相機和光源的相對位置,需要構建世界座標系。文 [1] 給出了使用 POV Ray 場景語言繪製三維座標系的方法,在此直接應用:
// 用於繪製任意軸向的巨集
#macro MakeAxis(Origin, Direction, Length, Thickness, Color)
#local ArrowLength = 0.125 * Length;
#local Direction = vnormalize(Direction);
#local Begin = Origin;
#local End = Begin + (Length - ArrowLength) * Direction;
#local ArrowBegin = End;
#local ArrowEnd = ArrowBegin + ArrowLength * Direction;
object {
union {
cylinder {
Begin, End
Thickness
texture { pigment { color Color } }
}
cone {
ArrowBegin, 2 * Thickness
ArrowEnd, 0
texture { pigment { color Color } }
}
}
}
#end
// 繪製世界座標系
object {
union {
#local Origin = <0, 0, 0>;
sphere {
Origin, 0.025 * ss_r
texture { pigment { color Gray20 } }
}
#local Length = 0.5 * ss_r;
#local Thickness = 0.0125 * ss_r;
MakeAxis(Origin, x, Length, Thickness, Red)
MakeAxis(Origin, y, Length, Thickness, Green)
MakeAxis(Origin, z, Length, Thickness, Blue)
}
}
隨便找個位置觀看
先較為隨意地將相機安放在能在頭腦中直接想出來的一個位置,只要保證這個位置距離四面體足夠遠即可,例如
camera {
location <5, 5, 5>
look_at <0, 0, 0>
}
相機的視點(拍攝位置)look_at
是世界座標系原點。
光源也可以隨意安放,只要保證四面體是在它的籠罩下即可,例如
light_source {
<1, 3, 10>
color White
}
現在看到的景象是
現在將相機向四面體拉近一些,並將光源設成無影:
// 相機
camera {
location 0.5 * <5, 5, 5>
look_at bs_center
}
// 光源
light_source {
<1, 3, 10>
color White
shadowless
}
看到的景象變為
日出東南隅
現在假設 <0, 0, -1>
為正南方向,<1, 0, 0>
為正東方向。將相機放在正南方位,視點為原點,光源放在東南方位的上空:
// 相機
camera {
location 4 * <0, 0, -1>
look_at <0, 0, 0>
}
// 光源
light_source {
10 * <1, 1, 1>
color White
shadowless
}
相機拍攝的正北影像如下圖所示:
此時,只能看到 x 軸(紅軸)和 y 軸(綠軸),看不到 z 軸,因為 z 軸垂直指向螢幕內部。將相機所在的這個位置定義為經度為 0 度, 維度為 0 度。相對於相機的位置,則光源所在的位置,經度為東經 45 度,維度為北緯 45 度。
若用 U 和 V 分別表示經緯度,則相機的位置可表示為
camera {
// n 是調整相機與視點(拍攝位置)之間距離遠近的係數
location n * vorate(vrotate(<0, 0, -1>, -U * y), V * x);
look_at <0, 0, 0>
}
例如,倘若將相機放在西經 20 度,北緯 30 度的位置,只需
#declare U = -20; // 負數表示西經,正數表示東經
#declare V = 30; // 負數表示南維,負數表示北緯
camera {
location 5 * vrotate(vrotate(-z, -U * y), V * x)
look_at <0, 0, 0>
}
注意,在 POV Ray 場景語言裡,<0, 0, -1>
可寫為 -z
,即 -<0, 0, 1>
。vrotate(v, a * b)
是 POV Ray 場景語言內定的巨集,用於將向量 v
繞 x
,y
或 z
軸轉動轉動角度 a
。
同理,光源也可以採用經緯度的方式設定,例如位於東經 45 度和北緯 45 度上空的光源,可定義為
light_source {
10 * vrotate(vrotate(-z, -45 * y), 45 * x)
color White
shadowless
}
現在相機拍到的畫面如下圖所示。
建立光源和相機的關係
相機和光源可以存在一個固定的關係。在 POV Ray 場景裡,相機本質上是如下圖所示的區域性座標系
將相機的 up 軸平移,令其過原點,然後將相機的位置繞 up 軸按左手定則轉 -45 度,所得位置可作為光源位置。
相機的 up 軸如何得到呢?由於相機一開始是在 -z
處,此時它的 up 軸與 y 軸平行,且方向相同。只需將 y 軸按經緯度旋轉,便可將其變換為相機所在經緯度的 up 軸,亦即
#declare up_dir = vrotate(vrotate(y, V * x), -U * y);
將相機位置繞 up_dir
方向按左手定則轉 -45 度
#declare sky_xyz = 5 * vrotate(vrotate(-z, -U * y), V * x);
#declare sun_xyz = vaxis_rotate(sky_xyz, up_dir, -45);
POV Ray 語言內定的巨集 vaxis_rotate(a, b, c)
用於將向量 a
圍繞向量 b
遵循左手定則轉動角度 c
。
上述過程構造的相機位置和光源位置之間的關係,猶如我們右手舉著手電筒觀察暗處的物品。
但是,上述過程構造的 up_dir
只是無數個方向中的一個。在日常中我們使用相機,倘若是正著拍照,上述的 up_dir
是合適的,但是倘若歪相機拍——類似於歪著頭看東西,可以讓 up_dir
繞視點到 sky_xyz
的方向構成的軸向一個角度來實現,例如向右側歪斜 15 度角:
#declare U = -20; // 負數表示西經,正數表示東經
#declare V = 30; // 負數表示南緯,正數表示北緯
#declare up_dir = vrotate(vrotate(y, V * x), -U * y);
#declare sky_xyz = 5 * vrotate(vrotate(-z, V * x), -U * y);
#declare sun_xyz = vaxis_rotate(sky_xyz, up_dir, -45);
camera {
location sky_xyz
sky vaxis_rotate(up_dir, sky_xyz, 15)
look_at <0, 0, 0>
}
上述程式碼中,sky
用於設定相機的 up
方向,拍到的景象如下圖所示:
平移變換
上述內容討論的相機設定,為了簡單起見,將視點設為世界座標系的原點,並圍繞該點設定相機和光源。實際上,若想讓網格模型出現在畫面中心區域,應當將視點視為網格模型包圍球的中心 bs_center
。為此,需將上述設定的相機和光源位置進行平移變換:
// 相機和光源
#declare U = -20; // 負數表示西經,正數表示東經
#declare V = 30; // 負數表示南緯,正數表示北緯
#declare up_dir = vrotate(vrotate(y, V * x), -U * y);
#declare sky_xyz = 5 * vrotate(vrotate(-z, V * x), -U * y);
#declare oblique_dir = vaxis_rotate(up_dir, sky_xyz, 15);
#declare sun_xyz = vaxis_rotate(sky_xyz, oblique_dir, -45);
#declare sky_xyz = sky_xyz + bs_center;
#declare sun_xyz = sun_xyz + bs_center;
camera {
location sky_xyz
sky oblique_dir
look_at bs_center
}
light_source {
sun_xyz
color White
shadowless
}
拍到的景象如下圖所示,與上一節相比,僅僅是檢視的中心有所變化。
拍攝系統
經過上述一番「推導」,便可構造一個用於拍攝網格模型的系統了。該系統只需要三個引數:網格模型包圍球的放大倍數、相機的經緯度以及相機 up 軸向的偏轉角度。
作業
用 Rust 語言實現上述的拍攝系統。
我的實現見:https://gitee.com/garfileo/rskynet
作業完成後,我給我家的汗血寶馬拍了一張照片:
參考
[1] https://segmentfault.com/a/11...
[2] https://segmentfault.com/a/11...
附錄
foo.inc:
#declare foo = mesh2 {
vertex_vectors {
4,
<0, 0, -0>,
<1, 0, -0>,
<0, 1, -0>,
<0, 0, -1>
}
face_indices {
4,
<0, 1, 2>,
<0, 1, 3>,
<1, 2, 3>,
<0, 2, 3>
}
}
foo.pov:
#version 3.7;
#include "colors.inc"
global_settings {
assumed_gamma 1.0
}
#macro MakeAxis(Origin, Direction, Length, Thickness, Color)
#local ArrowLength = 0.125 * Length;
#local Direction = vnormalize(Direction);
#local Begin = Origin;
#local End = Begin + (Length - ArrowLength) * Direction;
#local ArrowBegin = End;
#local ArrowEnd = ArrowBegin + ArrowLength * Direction;
object {
union {
cylinder {
Begin, End
Thickness
texture { pigment { color Color } }
}
cone {
ArrowBegin, 2 * Thickness
ArrowEnd, 0
texture { pigment { color Color } }
}
}
}
#end
// 四面體
#include "foo.inc"
object {
foo
texture {pigment {color Red}}
}
// 天球
#declare bs_center = <0.25, 0.25, -0.25>;
#declare bs_r = 0.829;
#declare sky_n = 3;
#declare sky_r = bs_r * sky_n;
sphere {
bs_center,3 * sky_r
texture {pigment {color rgbt <0, 0, 1, 0.75>}}
hollow
}
object {
union {
#local Origin = <0, 0, 0>;
sphere {
Origin, 0.025 * sky_r
texture { pigment { color Gray20 } }
}
#local Length = 0.75 * sky_r;
#local Thickness = 0.0125 * sky_r;
MakeAxis(Origin, x, Length, Thickness, Red)
MakeAxis(Origin, y, Length, Thickness, Green)
MakeAxis(Origin, z, Length, Thickness, Blue)
}
}
// 相機和光源
#declare U = -20; // 負數表示西經,正數表示東經
#declare V = 30; // 負數表示南緯,正數表示北緯
#declare up_dir = vrotate(vrotate(y, V * x), -U * y);
#declare sky_xyz = 5 * vrotate(vrotate(-z, V * x), -U * y);
#declare oblique_dir = vaxis_rotate(up_dir, sky_xyz, 15);
#declare sun_xyz = vaxis_rotate(sky_xyz, oblique_dir, -45);
#declare sky_xyz = sky_xyz + bs_center;
#declare sun_xyz = sun_xyz + bs_center;
camera {
location sky_xyz
sky oblique_dir
look_at bs_center
}
light_source {
sun_xyz
color White
shadowless
}