infancy / ky

single file, pbrt style, educational ray tracing renderer🖼️
MIT License
37 stars 1 forks source link

透视相机的“右方向” (right) 是怎么构造的? #1

Closed xiaomx32 closed 2 months ago

xiaomx32 commented 1 year ago

在透视相机的构造函数中有这样两行代码:

right = Normalize(Cross(front, up)) * tan_fov * Aspect(); this->up = Normalize(Cross(right, front)) * tan_fov;

请问,在构造 rightup时,为什么还有乘以 tan_fovAspect() 呢? PS :我看过 ray tracing in one weekend 和 pbrt 的相机部分,感觉两个相差很大,您设计相机感觉和前者相似,但还是有些不太一样:似乎“视口”和“最终图像”合二为一了,是这样吗?

infancy commented 1 year ago

您好, 是这样的, samllpt_rewrite.cpp 的代码有很多简化的地方, 它的相机压缩了中间的坐标变换, 直接把图像平面的坐标变换到世界空间的光线了.

我注释了一下 ray tracing in one weekend 中 Listing 64: [camera.h] Positionable and orientable camera 的代码, 您可以发现两者其实是一样的:

class camera {
    public:
        camera(
            point3 lookfrom,
            point3 lookat,
            vec3   vup,

            double vfov, // vertical field-of-view in degrees
            double aspect_ratio
        ) {
            auto theta = degrees_to_radians(vfov);
            auto h = tan(theta/2);
            auto viewport_height = 2.0 * h;
            auto viewport_width = aspect_ratio * viewport_height;

            auto w = unit_vector(lookfrom - lookat); // front
            auto u = unit_vector(cross(vup, w)); // right
            auto v = cross(w, u); // up

            origin = lookfrom;
            horizontal = viewport_width * u;
            vertical = viewport_height * v;
            lower_left_corner = origin - horizontal/2 - vertical/2 - w;
        }

        ray get_ray(double s, double t) const {
            return ray(origin, lower_left_corner + s * horizontal + t * vertical - origin);
            // for `lower_left_corner + s * horizontal + t * vertical - origin`
            // => s * horizontal + t * vertical - horizontal/2 - vertical/2 - w
            // => (s-0.5) * horizontal + (t-0.5) * vertical - w
            // => (s-0.5) * viewport_width * u + (t-0.5) * viewport_height * v - w
            // => (s-0.5) * aspect_ratio * 2.0 * tan_fov * u + (t-0.5) * 2.0 * tan_fov * v - w
            // same to `front + right * (s - 0.5) + up * (0.5 - t)` in `samllpt_rewrite.cpp`
        }

    private:
        point3 origin;
        point3 lower_left_corner;
        vec3 horizontal;
        vec3 vertical;
};

在这里 samllpt_rewrite.cpp 和 ray tracing in one weekend 还有些细节上的差异, 比如 samllpt_rewrite.cpp 从上往下遍历图像竖直方向, 使用左手的相机坐标系; ray tracing in one weekend 从下往上遍历, 使用右手系等.

因为 samllpt_rewrite.cpp 和 ky.cpp 主要想解释的是积分器, 所以只实现了一个简单的针孔相机, 和 pbrt 的代码对不上, 但它们的原理仍然是一样的. 后者的相机阅读起来有些费脑筋🙄, 我记过一点笔记: camera.h, perspective.cpp, 您有兴趣的话可以看看.

have fun~

xiaomx32 commented 1 year ago

感谢您的解答。看完您的推导后,发现相机的构造确实是一样的。 在 ray tracing in one weekend 中 horizontal 和 viewport_width 表示的意思是一样的,所以对我这样的读者造成了一定的误解。其中,没有将点、向量、标量长度严格区分开,也不是一个好习惯。不过很感谢您的笔记,帮了我很多,谢谢。 祝您生活愉快!