从零实现光线追踪器(0)- 将数据保存为图片

2018-10-22  本文已影响0人  不吃折耳根

前言


本人一直对图形学感兴趣。之前一直在scratchapixel.com学习图形学的知识。这个网站对相关的数学基础以及图形学入门概念讲解十分详细,推荐有一定英语能力的人阅读。但是读了那么多理论知识,总想着去自己动手实现一把。可是万事开头难啊,到底需要编写哪些类,设计哪些算法,让人头大。这时我去油管找到了一个2013年的教程使用C++实现了一个简易的光线追踪器,效果还挺好。在这里写下自己的学习过程供大家参考和交流。

这是该作者的教程地址共9节Raytracer from Scratch in C++,有兴趣的同学可以科学上网浏览一下。该作者的编码水平并不高,其中有很多可以优化的地方,但是他所展示的实现光线追踪器的一套流程还是十分值得学习。

下面我们正式开始。

1.保存bmp图片


第一步我们需要编写一个函数saveBmp()将图像数据保存为bmp图片。此处也可以去查阅相关的博客来编写该函数。而一个简单的bmp的格式为bmpFileHeader+bmpInfoHeader+data。创建一个头文件命名为func.h,在其中进行如下编写。

首先我们定义字长。此处第一行#pragma pack(2)是必不可少的。否则无法字节对齐,bmp格式相关参数会设置错误。

#pragma pack(2)
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef long LONG;

接下来我们定义一个bmpFileHeaderbmpInfoHeader结构体。

struct BmpFileHeader
{
    WORD    bfType;
    DWORD   bfSize;
    WORD    bfReserved1;
    WORD    bfReserved2;
    DWORD   bfOffBits;
};
struct BmpInfoHeader
{
    DWORD   biSize;             
    LONG    biWidth;            
    LONG    biHeight;           
    WORD    biPlanes;           
    WORD    biBitCount;        
    DWORD   biCompression;      
    DWORD   biSizeImage;        
    LONG    biXPelsPerMeter;    
    LONG    biYPelsPerMeter;    
    DWORD   biClrUsed;          
    DWORD   biClrImportant; 
};

此处各项变量的意义可以去查阅讲解bitmap位图格式的相关博客

接下来我们定义一个结构体用于保存RGB数据

struct RGBType
{
    unsigned char r;
    unsigned char g;
    unsigned char b;

    void set(unsigned char r1, unsigned char g1, unsigned char b1)
    {
        r = r1;
        g = g1;
        b = b1;
    }
};

这样我们就可以来编写saveBmp()函数了。

void saveBmp(const char *fileName, int w, int h, RGBType *data)
{
    int size = 4 * w * h;

    //设置bmpfileheader的各种数值
    BmpFileHeader fileHeader;
    fileHeader.bfType = 0x4D42;//0x4d42即字母 'B''M'
    fileHeader.bfReserved1 = 0;//保留位1
    fileHeader.bfReserved2 = 0;//保留位2
    fileHeader.bfSize = 54 + size;//文件总长度 文件头(54字节) + 图像数据(size)
    fileHeader.bfOffBits = 54;//文件头长度

    //设置infoheader的值
    BmpInfoHeader infoHeader = {0};
    infoHeader.biSize = 40;//infoheader的长度
    infoHeader.biHeight = -h;//高度
    infoHeader.biWidth = w;//宽度
    infoHeader.biPlanes = 1;
    infoHeader.biBitCount = 24;//颜色位数。一般为24
    infoHeader.biSizeImage = 0;//默认为0
    infoHeader.biCompression = 0;//压缩率 ,默认为0


    //将传入的RGB数值写入文件中
    FILE *output = fopen(fileName, "wb");
    if (output)
    {
        fwrite(&fileHeader, 14, 1, output);
        fwrite(&infoHeader, 40, 1, output);
        for (int i = 0; i < w * h; ++i)
        {
            RGBType rgb = data[i];
            unsigned char color[3] = {rgb.r, rgb.g, rgb.b};
            fwrite(color, 3, 1, output);
        }
        fclose(output);
    }
}

接下来我们进行简单的测试。

#include "func.h"
#include <iostream>
#include <cmath>
#include <fstream>
using namespace std;

int main(int argc, char *argv[])
{
    cout << "rendering";
    
    int width = 640;
    int height = 480;
    int n = width * height;

    RGBType *pixels = new RGBType[n];
    for (int x = 0; x < width; ++x)
    {
        for (int y = 0; y < height; ++y)
        {
            int i = y * width + x;
            //设置一个矩形
            if((x>100 && x<300)&&(y>200 && y<400)){
                 pixels[i].set(200,100,200);
            }else{
                 pixels[i].set(255,255,255);
            }
        }
    }
    //将RGB数据保存到bmp图片中
    saveBmp("pic.bmp",width,height,pixels);
}

编译运行一下。打开pic.bmp如下。可以使用windows自带图片查看器或者windows上的画图直接查看。

pic.png

那么这就是第一步。已经初见成果(可以看到一个矩形了哈哈)。那么离后面的成功就不远了~~

上一篇下一篇

猜你喜欢

热点阅读