实现OpenGL渲染器语法篇(二)——文件处理及TGA文件的使用

最近在Github上复现了一个渲染器render的项目:
Github链接:tiny render

我希望在博客上可以记录自己的学习过程,博客主要分为两大类:《原理篇》和《语法篇》。
原理篇则主要讲的是——实现渲染器过程中所需要的图形学渲染算法知识。
语法篇则主要讲的是——实现渲染器过程中使用到的C++的语法知识。


一、文件处理的C++语法

1.1 读取file的代码——Using std::fstream for File Handling

fstream是c++为(相对地)独立于平台的文件访问提供的一个类class。

  • std::fstream继承了std::ofstream,它用于写文件。
  • std::ifstream用于读文件。

换句话说,std::fstream为大家提供了读写功能

TIP:
要使用类std::fstream或它的基类,包括header: #include fstream

1.1.1 Opening and Closing a File Using open() and close()

要使用fstream、ofstream或ifstream类,需要使用open()方法打开一个文件:

open()有两个参数:

  1. 第一个是要打开的文件的路径和名称。(如果你不提供路径,则将使用该应用程序的当前目录设置)
  2. 第二种是打开文件的模式。选择的模式允许创建文件,即使存在一个(ios base::trunc),并允许读写文件(in | out)。(trunc表示将数字截尾取整)
  • 注意使用is_open()来测试open()是否成功。
  • 使用close()关闭stream(流)对于保存文件非常重要。

还有另一种打开文件流的方法,即通过构造函数myFile——

fstream myFile("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);

或者,如果你想打开一个仅用于写入的文件,请使用以下命令——

ofstream myFile("HelloFile.txt", ios_base::out);

如果要打开文件进行读取,请使用以下命令——

ifstream myFile("HelloFile.txt", ios_base::in);

TIP:
无论你是使用构造函数还是成员方法open(),建议你在继续使用相应的file stream对象之前,通过is_open()检查文件是否成功打开。

可以打开文件流的各种模式如下:

■ ios_base::app—Appends to the end of existing files rather than truncating them (附加到现有文件的末尾,而不是将其截断)

■ ios_base::ate—Places you at the end of the file, but you can write data anywhere in the file (将您置于文件的末尾,但是您可以在文件的任何地方写入数据)

■ ios_base::trunc—Causes existing files to be truncated; the default(导致现有文件被截断;默认值)

■ ios_base::binary—Creates a binary file (default is text) (创建一个二进制文件(默认是文本))

■ ios_base::in—Opens file for read operations only (仅为读取操作打开文件)

■ ios_base::out—Opens file for write operations only (仅为写操作打开文件)


1.2 使用 open() 和 operator<< 来创建和写一个文本文件

在你打开文件流之后,可以使用 操作符<< 对其进行操作,如下所示。

// LISTING 27.8 
// Creating a New Text File and Writing Text into It Using ofstream

#include
#include
using namespace std;

int main()
{
    ofstream myFile;
    myFile.open("HelloFile.txt", ios_base::out);
    
    if (myFile.is_open())
    {
        cout << "File open successful" << endl;
        
        myFile << "My first text file!" << endl;
        myFile << "Hello file!";
        
        cout << "Finished writing to file, will close now" << endl;
        myFile.close();
    }
    
    return 0;
}
  • Content of file HelloFile.txt:My first text file! Hello file!
    • 行17-18

第11行以ios base::out模式打开文件,也就是说,只用于操作。

在第13行,测试open()是否成功,然后使用插入操作符 operator << 继续写入文件流,如第17和18行所示。

最后,在第21行关闭,然后返回。

NOTE:

清单27.8演示了如何像 使用cout写入标准输出(控制台) 一样的方式 写入文件流

这表明了c++中的streams如何支持处理不同设备的类似方法,即通过cout将文本写入显示框,就像通过ofstream写入文件一样。


1.3 使用open() 和 operator>> 来读取一个文本文件

要读取一个文件,你可以使用fstream并使用ios base::in或使用ifstream打开它。
清单27.9演示读取清单27.8中创建的文件HelloFile.txt。

// LISTING 27.9
// Reading Text from File HelloFile.txt Created in Listing 27.8

#include
#include
#include
using namespace std;

int main()
{
    ifstream myFile;
    myFile.open("HelloFile.txt", ios_base::in);
    
    if (myFile.is_open())
    {
        cout << "File open successful. It contains: " << endl;
        string fileContents;
        
        while (myFile.good())
        {
            getline (myFile, fileContents);
            cout << fileContents << endl;
        }
        
        cout << "Finished reading file, will close now" << endl;
        myFile.close();
    }
    else
        cout << "open() failed: check if file is in right folder" << endl;
    
    return 0;
}

NOTE:

清单27.9读取了使用清单27.8创建的文本文件 “HelloFile.txt”,你既可以将该文件移动到这个项目的工作目录,或者将此代码合并到前一个目录中。

Analysis:

与往常一样,执行check检查 is_open() 来验证行12的open()的调用是否成功。

请注意提取 操作符operator >> 在读取文件内容时的用法:将文件直接转换成字符串,然后在第22行中使用cout进行显示。

在本例中,我们使用getline()从文件流中读取输入,其方法与清单27.7中从用户读取输入的方法完全相同,一次读取一行


1.4 如何处理二进制文件?

写入二进制文件的实际过程与你到目前为止学到的内容并没有太大的不同。

在打开文件时,使用ios_base::binary flag 作为 掩码mask是很重要的。

你通常使用ofstream::write或ifstream::read,如清单27.10以下所示。

// LISTING 27.10
// Writing a struct to a Binary File and Reconstructing It from the Same
// 将一个struct写入一个二进制文件并从相同的结构中重新构造它

#include
#include
#include
#include
using namespace std;

struct Human
{
    Human() {};
    Human(const char* inName, int inAge, const char* inDOB) : age(inAge)
    {
        strcpy(name, inName);
        strcpy(DOB, inDOB);
    }
    
    char name[30];
    int age;
    char DOB[20];
};

int main()
{
    Human Input("Siddhartha Rao", 101, "May 1916");
    
    ofstream fsOut ("MyBinary.bin", ios_base::out | ios_base::binary);
    
    if (fsOut.is_open())
    {
        cout << "Writing one object of Human to a binary file" << endl;
        fsOut.write(reinterpret_cast(&Input), sizeof(Input));
        fsOut.close();
    }
    
    ifstream fsIn ("MyBinary.bin", ios_base::in | ios_base::binary);
    
    if(fsIn.is_open())
    {
        Human somePerson;
        fsIn.read((char*)&somePerson, sizeof(somePerson));
        
        cout << "Reading information from binary file: " << endl;
        cout << "Name = " << somePerson.name << endl;
        cout << "Age = " << somePerson.age << endl;
        cout << "Date of Birth = " << somePerson.DOB << endl;
    }
    
    return 0;
}

Analysis:

在行27~36中,我们创建了struct Human的一个实例,该实例包含名称、年龄和DOB,并使用 ofstream 将其保存到一个二进制文件 MyBinary.bin 中的磁盘中。然后在行38~48中使用另一个类型为ifstream的流对象读取该信息

名称等属性的输出是通过从二进制文件中读取的信息实现的。该示例还演示了ifstream和ofstream使用ifstream :: read和ofstream :: write来读写文件时使用的分别。

请注意,在第34行中使用reinterpret_cast实质上是强制编译器将 struct 解释为 char * 。在第43行中,你使用了第34行中使用的c样式的强制转换版本。

NOTE:

如果不是出于解释的目的,我宁愿坚持struct Human及其所有属性都在XML文件中。
XML是一个基于文本和标记的存储格式,这种格式具有信息持久化方式的灵活性和可伸缩性。

如果struct Human在这个版本中delivered,并且在交付之后,如果要向其添加新属性(例如numChildren),你需要担心的是,ifstream::read功能是否能够正确读取使用旧版本创建的二进制数据。


二、文件处理在tgaimage.cpp/.h中的应用

2.1 读取tga文件

tga文件是这次渲染器的重点,所以我们先要学会读取它:

bool TGAImage::read_tga_file(const char* filename) 
{
    if (data) 
        delete[] data;

    data = NULL;

    std::ifstream in;
    in.open(filename, std::ios::binary);
    if (!in.is_open()) 
    {
        std::cerr << "can't open file " << filename << "\n";
        in.close();
        return false;
    }

    TGA_Header header;                      
    in.read((char*)&header, sizeof(header));
    if (!in.good()) 
    {
        in.close();
        std::cerr << "an error occured while reading the header\n";
        return false;
    }

    unsigned long nbytes = bytespp * width * height;

    data = new unsigned char[nbytes];

    if (3 == header.datatypecode || 2 == header.datatypecode) // 3 2?
    {
        in.read((char*)data, nbytes);
        if (!in.good()) 
        {
            in.close();
            std::cerr << "an error occured while reading the data\n";
            return false;
        }
    }

    else if (10 == header.datatypecode || 11 == header.datatypecode)  // 10 11?
    {
        if (!load_rle_data(in)) 
        {
            in.close();

            std::cerr << "an error occured while reading the data\n";
            return false;
        }
    }

    else 
    {
        in.close();

        std::cerr << "unknown file format " << (int)header.datatypecode << "\n";
        return false;
    }

    in.close();

    return true;
}

2.2 加载游程编码数据

有一个成员函数:load_rle_data()
里面将类ifstream的对象in作为形参传入

bool TGAImage::load_rle_data(std::ifstream& in) 
{
    unsigned long pixelcount = width * height;
    unsigned long currentpixel = 0;
    unsigned long currentbyte = 0;

    TGAColor colorbuffer;

    do 
    {
        unsigned char chunkheader = 0;
        chunkheader = in.get();   // get()针对二进制文件的读写:http://c.biancheng.net/cpp/biancheng/view/2231.html 和 http://c.biancheng.net/view/1534.html
        
        if (!in.good()) 
        {
            std::cerr << "an error occured while reading the data\n";
            return false;
        }
                            ......

2.3 写入tga文件

我们来康康tga是如何写进去的,函数write_tga_file()——

bool TGAImage::write_tga_file(const char* filename, bool rle) 
{
    unsigned char developer_area_ref[4] = { 0, 0, 0, 0 };
    unsigned char extension_area_ref[4] = { 0, 0, 0, 0 };

    unsigned char footer[18] = { 'T','R','U','E','V','I','S','I','O','N',
        '-','X','F','I','L','E','.','\0' };
    
    std::ofstream out;
    out.open(filename, std::ios::binary);

    if (!out.is_open()) 
    {
        std::cerr << "can't open file " << filename << "\n";
        out.close();
        return false;
    }

    out.write((char*)&header, sizeof(header));

    if (!out.good()) 
    {
        out.close();
        std::cerr << "can't dump the tga file\n";

        return false;
    }
                 ...

    out.close();
    return true;
}

2.4 卸载游程编码数据

接下来的函数是unload_rle_data(),卸载的参数就是out,加载的参数就是in。

bool TGAImage::unload_rle_data(std::ofstream& out) 
{
    const unsigned char max_chunk_length = 128;
    unsigned long npixels = width * height;
    unsigned long curpix = 0;

    while (curpix < npixels) 
    {
        unsigned long chunkstart = curpix * bytespp;
        unsigned long curbyte = curpix * bytespp;
        unsigned char run_length = 1;

        out.put(raw ? run_length - 1 : run_length + 127);

        out.write((char*)(data + chunkstart), (raw ? run_length * bytespp : bytespp));
        
                          ......

        if (!out.good()) 
        {
            std::cerr << "can't dump the tga file\n";
            return false;
        }
    }
    return true;
}

你可能感兴趣的:(实现OpenGL渲染器语法篇(二)——文件处理及TGA文件的使用)