乐趣区

关于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
}

当初看到的现象是

当初将相机向四面体拉近一些,并将光源设成无影:

// 相机
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 绕向量 b 按左手定则转动角度 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 = 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 方向,拍到的现象如下图所示:

平移变换

上述内容探讨的相机设定,为了简略起见,将视点设为世界坐标系的原点,并围绕该点设定相机和光源。实际上,若想让网格模型呈现在画面核心区域,该当将视点视为网格模型突围球的核心 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
}

拍到的现象如下图所示,与上一节相比,仅仅是视图的核心有所变动。

拍摄零碎

通过上述一番「推导」,便可结构一个用于拍摄网格模型的零碎了。该零碎只须要三个参数:网格模型突围球的放大倍数、相机的经纬度以及相机 up 轴向的偏转角度。

作业

用 Rust 语言实现上述的拍摄零碎:

impl<T: fmt::Display
     + convert::From<f64>> Mesh<T> {
    pub fn output_povray_view(&self, path: &str,
                              model_name: &str,
                              u: T, /* 经度 */
                              v: T, /* 纬度 */
                              n: T, /* 突围球放大倍数 */
                              a: T  /* 相机 up 轴向偏转角度 */) {// ... ... ...}
}

或者等我写出 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
}
退出移动版