与 Rust 勾心斗角 · 作业

【与 Rust 勾心斗角 · 作业】由于已经为网格模型(多面体)实现了计算包围球的方法,基于网格的包围球构造天球,使用经纬坐标,便可将相机定位于天球球面上的任意一点,亦即将三维空间里的相机定位问题转化为二维球面定位问题,从而将问题的难度降低一个数量级。在解决该问题之前,需要先热 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 }

现在看到的景象是
与 Rust 勾心斗角 · 作业
文章图片

现在将相机向四面体拉近一些,并将光源设成无影:
// 相机 camera { location 0.5 * <5, 5, 5> look_at bs_center }// 光源 light_source { <1, 3, 10> color White shadowless }

看到的景象变为
与 Rust 勾心斗角 · 作业
文章图片

日出东南隅 现在假设 <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 }

相机拍摄的正北图像如下图所示:
与 Rust 勾心斗角 · 作业
文章图片

此时,只能看到 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 绕向量 b 按左手定则转动角度 a
同理,光源也可以采用经纬度的方式设定,例如位于东经 45 度和北纬 45 度上空的光源,可定义为
light_source { 10 * vrotate(vrotate(-z, -45 * y), 45 * x) color White shadowless }

现在相机拍到的画面如下图所示。
与 Rust 勾心斗角 · 作业
文章图片

建立光源和相机的关系 相机和光源可以存在一个固定的关系。在 POV Ray 场景里,相机本质上是如下图所示的局部坐标系
与 Rust 勾心斗角 · 作业
文章图片

将相机的 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 = vrotate(sky_xyz, -45 * up_dir);

上述过程构造的相机位置和光源位置之间的关系,犹如我们右手举着手电筒观察暗处的物品。
但是,上述过程构造的 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 = vrotate(sky_xyz, -45 * up_dir); camera { location sky_xyz sky vrotate(up_dir, 15 * vnormalize(sky_xyz)) look_at <0, 0, 0> }

上述代码中,sky 用于设定相机的 up 方向,拍到的景象如下图所示:
与 Rust 勾心斗角 · 作业
文章图片

平移变换 上述内容讨论的相机设定,为了简单起见,将视点设为世界坐标系的原点,并围绕该点设定相机和光源。实际上,若想让网格模型出现在画面中心区域,应当将视点视为网格模型包围球的中心 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 = vrotate(up_dir, 15 * vnormalize(sky_xyz)); #declare sun_xyz = vrotate(sky_xyz, -45 * oblique_dir); #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 }

拍到的景象如下图所示,与上一节相比,仅仅是视图的中心有所变化。
与 Rust 勾心斗角 · 作业
文章图片

拍摄系统 经过上述一番「推导」,便可构造一个用于拍摄网格模型的系统了。该系统只需要三个参数:网格模型包围球的放大倍数、相机的经纬度以及相机 up 轴向的偏转角度。
作业 用 Rust 实现上述的拍摄系统……或者等我写出 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 = vrotate(up_dir, -15 * vnormalize(sky_xyz)); #declare sun_xyz = vrotate(sky_xyz, -45 * oblique_dir); #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 }

    推荐阅读