上一篇:经纬

为了便于实现天球上任意一点的经纬度坐标向三维坐标的转换,我定义了一个宏:

#macro UV_to_XYZ(U, V)
  vrotate(vrotate(-z, V * x), -U * y)
#end

有了这个宏,天球上任意一点的三维坐标唾手可得。例如

#declare A = UV_to_XYZ(118.41, 34.61);

现在,我想以 A 为原点,构造一个直角坐标系 (X_A, Y_A, Z_A)X_A 轴与 Y_A 轴分别与过 A 点的纬线和经线相切,Z_A 轴指向 <0, 0, 0>

Z_A 轴很容易构造出来:

#declare Z_A = vnormalize(<0, 0, 0> - A);

至于 X_AY_A,其实也很容易构造出来:

#declare X_A = vrotate(x, -118.41 * y);
#declare Y_A = vrotate(vrotate(y, 34.61 * x), -118.41 * y);

究其原理,如计算 A 的坐标的思路如出一辙,并不需要使用微分几何方面的知识。

可以使用之前定义的 MakeAxis 宏,定性地检验一下所得坐标系是否正确:

MakeFrame(A, X_A, Y_A, Z_A, 0.25, 0.01)

结果为

我打算在 A 点安装一颗卫星,用它监视天球之内的世界。这是我在这个世界安装的第一颗卫星,要给它取个有纪念意义的名字……就叫有纪念意义的名字吧。

实际上,有纪念意义的名字,不过是一个相机:

camera  {
  location A
  right X_A
  up Y_A
  direction Z_A
}

如果用这个相机来取代之前用来拍摄上述图像的相机,结果可得

镜头恰好被相交的经线和纬线挡住了,如果沿着 -Z_A 的方向移动一下相机,

camera  {
  location A - 3.5 * Z_A
  right X_A
  up Y_A
  direction Z_A
}

或者

camera  {
  location A
  right X_A
  up Y_A
  direction Z_A
  translate -3.5 * Z_A
}

就可以看到整个天球了,

但是,看上去怪怪的,似乎整个世界被压扁了一些。先不管这个问题,更应该关心的是,相机的设定不同以往。rightupdirection 这三个向量的值恰好分别就是我苦心孤诣构造的 X_AY_AZ_A,它们的含义是什么呢?

right 的含义是相机的右侧方向。up 的含义是相机的顶部方向。direction 的含义是相机镜头的方向。这三个方向再加上 location,便完全确定了相机的方位。至于这个相机拍摄到的图像为何是扁的,是因为相机认为要拍摄的画面是个正方形,即画面的宽度和高度相同,但是我给它的底片却是长方形的,所以相机不得不把它拍摄到的正方形画面压扁,印到底片上。

画面的宽度和高度信息,需要以比例值通过 right 向量传递给相机。POV Ray 有两个内部变量,image_widthimage_height,它们的值分别是底片的宽度值和高度值。通过 right 向量传递给相机的方法是

camera  {
  location A
  right (image_width/image_height) * X_A
  up Y_A
  direction Z_A
  translate -3.5 * Z_A
}

用这个新的相机,就可以拍摄到正确的结果:

从此,名字为有纪念意义的名字的卫星就可以工作了。不过,它的设定其实还可以更简单一些:

camera  {
  location A - 3.5 Z_A
  sky Y_A
  look_at <0, 0, 0>
}

POV Ray 能够根据 skylook_at 的值自动为我计算出上面的相机信息。

附录

完整的代码:

#version 3.7;
#include "colors.inc"
global_settings {
  assumed_gamma 1.0
}

// 天球
sphere {
  <0, 0, 0>, 1
  texture { pigment { color rgbt <0, 0, 1, 0.75> } }
  hollow // 令球体中空
}

#macro MakeAxis(Origin, Direction, Length, Thickness, Color)
  #local ArrowLength = 0.3 * 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

#macro MakeFrame(A, X_A, Y_A, Z_A, L, Thickness)
  object {
    union {
      sphere {
        A, 2 * Thickness
        texture { pigment { color Gray50 } }
      }
      MakeAxis(A, X_A, L, Thickness, Red)
      MakeAxis(A, Y_A, L, Thickness, Green)
      MakeAxis(A, Z_A, L, Thickness, Blue)
    }
  }
#end

// 世界坐标系
MakeFrame(<0, 0, 0>, x, y, z, 2, 0.025)

#declare line_thickness = 0.015;

torus {
  1, line_thickness
  texture { pigment { color Orange } }
}

torus {
  #local theta = (34.61 / 180) * pi;
  cos(theta), line_thickness
  texture { pigment { color Orange } }
  translate sin(theta) * y
}

#declare Monster = box { <-1.5, -1.5, 0>, <1.5, 1.5, 1.5> }
#declare PrimeMeridian = difference {
  torus {
    1, line_thickness
    rotate 90 * z
    texture { pigment { color Orange } }
  }
  Monster
}

// 本初子午线
PrimeMeridian

// 东经 118.41
object {
  PrimeMeridian
  rotate -118.41 * y
}

#macro UV_to_XYZ(U, V)
  vrotate(vrotate(-z, V * x), -U * y)
#end

#declare A = UV_to_XYZ(118.41, 34.61);
#declare X_A = vrotate(x, -118.41 * y);
#declare Y_A = vrotate(vrotate(y, 34.61 * x), -118.41 * y);
#declare Z_A = vnormalize(<0, 0, 0> - A);

MakeFrame(A, X_A, Y_A, Z_A, 0.5, 0.025)

light_source {
  <5, 5, -5>
  color White
  shadowless // 无影灯
}

camera  {
  location A
  right (image_width / image_height) * X_A
  up Y_A
  direction Z_A
  translate -3.5 * Z_A
}
下一篇:天网

garfileo
6k 声望1.9k 粉丝

这里可能不会再更新了。


« 上一篇
经纬
下一篇 »
天网

引用和评论

2 篇内容引用
0 条评论