Ray Tracing in One Weekend01无法查看ppm的问题及一个C++字符缓冲传参引发的bug

最近在学习光线追踪的经典教程<<Ray Tracing in One Weekend—The Book Series>>,在这个系列中作者的程序运行后生成ppm格式的图片无奈本地的图片查看器包括Photoshop都无法查看作者生成的PPM格式,令人疑惑的是在闫令琪大佬的GAMES101-现代计算机图形学课程中生成的ppm格式文件文件用Photoshop打开就完全没问题,比如说这张斯坦福兔的图片:
Ray Tracing in One Weekend01无法查看ppm的问题及一个C++字符缓冲传参引发的bug
无奈只好换一个方向,在Github上找到了将RGB/RGBA 格式的数据压缩为PNG格式的repo,就下载过来试一下,还真的很好用----svpng
下载过来后将里面的附件svpng.inc放在和主程序同一级目录下.
这样作者原先写的代码就可以修改为

#include <iostream>
#include "svpng.inc"
int main() {

    // Image
    FILE *fp = fopen("firstppm.png", "wb");
    unsigned char rgb[256 * 256 * 3], *p = rgb;
    const int image_width = 256;
    const int image_height = 256;

    // Render

    for (int j = image_height-1; j >= 0; --j) {
        for (int i = 0; i < image_width; ++i) {
            auto r = double(i) / (image_width-1);
            auto g = double(j) / (image_height-1);
            auto b = 0.25;

            *p++ = (unsigned char)static_cast<int>(255.999 * r);
            *p++ = (unsigned char)static_cast<int>(255.999 * g);
            *p++ = (unsigned char)static_cast<int>(255.999 * b);
        }
    }
    svpng(fp, 256, 256, rgb, 0);
    fclose(fp);
    return 0;
}

编译完成后直接运行可执行文件就可以生成firstppm.png,像这样:
Ray Tracing in One Weekend01无法查看ppm的问题及一个C++字符缓冲传参引发的bug
后续的vec3.h和color.h也要按照作者源码做对应修改:
vec3.h:

#ifndef VEC3_H
#define VEC3_H

#include<cmath>
#include<iostream>

using std::sqrt;

class vec3
{
    public:
        vec3(): e{0,0,0}{}
        vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}

        double x() const { return e[0]; }
        double y() const { return e[1]; }
        double z() const { return e[2]; }

        vec3 operator-() const { return vec3(-e[0], e[1], -e[2]); }
        double operator[](int i) const { return e[i]; }
        double &operator[](int i) { return e[i]; }

        vec3& operator+=(const vec3 &v)
        {
            e[0] += v.e[0];
            e[1] += v.e[1];
            e[2] += v.e[2];
            return *this;
        }

        vec3& operator*=(const double t){
            e[0] *= t;
            e[1] *= t;
            e[2] *= t;
            return *this;
        }

        vec3& operator/= (const double t){
            return *this *= 1 / t;
        }

        double length() const {
            return sqrt(length_squared());
        }

        double length_squared() const
        {
            return e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
        }
    public:
        double e[3];
};

//Type aliases for vec3
using point3 = vec3;//3D point
using color = vec3; //RGB color

//vec3 Utility Functions
inline void operator<<(unsigned char *&out,const vec3 &v)
{
    *out++ = (unsigned char)(v.e[0]);
    *out++ = (unsigned char)(v.e[1]);
    *out++ = (unsigned char)(v.e[2]);
    return;
}

inline vec3 operator+(const vec3 &u,const vec3 &v)
{
    return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]);
}

inline vec3 operator-(const vec3 &u,const vec3 &v)
{
    return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]);
}

inline vec3 operator*(const vec3 &u,const vec3 &v)
{
    return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]);
}

inline vec3 operator*(double t,const vec3 &v)
{
    return vec3(t*v.e[0], t*v.e[1],t*v.e[2]);
}

inline vec3 operator*(const vec3 &v, double t) {
    return t * v;
}

inline vec3 operator/(vec3 v, double t) {
    return (1/t) * v;
}

inline double dot(const vec3 &u,const vec3 &v)
{
    return u.e[0] * v.e[0] + u.e[1] * v.e[1] + u.e[2] * v.e[2];
}

inline vec3 cross(const vec3 &u, const vec3 &v) {
    return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1],
                u.e[2] * v.e[0] - u.e[0] * v.e[2],
                u.e[0] * v.e[1] - u.e[1] * v.e[0]);
}

inline vec3 unit_vector(vec3 v) {
    return v / v.length();
}

#endif

color.h:

#ifndef COLOR_H
#define COLOR_H

#include "vec3.h"

#include <iostream>

void write_color(unsigned char *&out, color pixel_color) {
    // Write the translated [0,255] value of each color component.
    *out++ = (unsigned char)static_cast<int>(255.999 * pixel_color.x());
    *out++ = (unsigned char)static_cast<int>(255.999 * pixel_color.y());
    *out++ = (unsigned char)static_cast<int>(255.999 * pixel_color.z());
    return;
}

#endif

源程序也就变成这样了,注意svpng的引用路径变了:

#include"color.h"
#include "vec3.h"
#include <iostream>
#include "../svpng.inc"
int main() {

    // Image
    FILE *fp = fopen("firstppm.png", "wb");
    const int image_width = 256;
    const int image_height = 256;
    unsigned char rgb[image_width * image_height * 3], *p = rgb;
    // Render

    for (int j = image_height-1; j >= 0; --j) {
        for (int i = 0; i < image_width; ++i) {
            color pixel_color(double(i)/(image_width-1), double(j)/(image_height-1), 0.25);
            write_color(p, pixel_color);
        }
    }
    svpng(fp, 256, 256, rgb, 0);
    fclose(fp);
    return 0;
}

注意:在vec3.h中的重载运算符函数operator<<(unsigned char *&out,const vec3 &v),和color.h中定义的write_color(unsigned char *&out, color pixel_color) 一定要传入字符缓冲的引用,也就是unsigned char *&out,因为颜色的写入操作直接对应字符缓冲rgb的地址,不应有中间拷贝。
如果传入的是unsigned char *类型就会生成这么个奇怪的图片:
Ray Tracing in One Weekend01无法查看ppm的问题及一个C++字符缓冲传参引发的bug
随着课程的深入我会把作者写的源码继续改成svpng兼容的版本再把代码贴出来的,未完待续~

上一篇:Ray Tracing - Intersection


下一篇:Rust实用教程之 - Hello world