概述
本文档详细记录了在 SoftRasterizer 项目中实现 diffuse 材质加载的全过程,解决了初始加载失败的问题,使得模型能够正确显示纹理效果。
核心修改
1. Texture 类扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Texture { public: int width = 0; int height = 0; std::vector<vec3f> pixels;
bool loadFromTGA(const std::string& filename); vec3f sample(float u, float v) const; bool empty() const { return pixels.empty() || width == 0 || height == 0; } };
bool Texture::loadFromTGA(const std::string& filename) { std::vector<unsigned char> data; if (!loadTGA(filename, width, height, data)) { return false; }
pixels.resize(width * height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int idx = (y * width + x) * 3; pixels[y * width + x] = vec3f( data[idx] / 255.0f, data[idx + 1] / 255.0f, data[idx + 2] / 255.0f ); } } return true; }
|
2. 支持 RLE 压缩的 TGA 加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| bool loadTGA(const std::string& filename, int& width, int& height, std::vector<unsigned char>& data) { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) return false;
TGAHeader header; file.read(reinterpret_cast<char*>(&header), sizeof(header));
if ((header.datatypecode != 2 && header.datatypecode != 10) || header.bitsperpixel != 24) { std::cerr << "Unsupported TGA format" << std::endl; return false; }
width = header.width; height = header.height; data.resize(width * height * 3);
file.seekg(header.idlength + header.colormaplength * (header.colormapdepth / 8), std::ios::cur);
if (header.datatypecode == 2) { file.read(reinterpret_cast<char*>(data.data()), data.size()); } else if (header.datatypecode == 10) { size_t pixelCount = width * height; size_t currentPixel = 0; unsigned char pixel[3];
while (currentPixel < pixelCount) { unsigned char chunkHeader; file.read(reinterpret_cast<char*>(&chunkHeader), 1);
if (chunkHeader < 128) { size_t count = chunkHeader + 1; for (size_t i = 0; i < count && currentPixel < pixelCount; ++i) { file.read(reinterpret_cast<char*>(pixel), 3); data[currentPixel * 3] = pixel[0]; data[currentPixel * 3 + 1] = pixel[1]; data[currentPixel * 3 + 2] = pixel[2]; currentPixel++; } } else { size_t count = chunkHeader - 127; file.read(reinterpret_cast<char*>(pixel), 3); for (size_t i = 0; i < count && currentPixel < pixelCount; ++i) { data[currentPixel * 3] = pixel[0]; data[currentPixel * 3 + 1] = pixel[1]; data[currentPixel * 3 + 2] = pixel[2]; currentPixel++; } } } }
for (size_t i = 0; i < data.size(); i += 3) { std::swap(data[i], data[i + 2]); } return true; }
|
3. Model 类集成
1 2 3 4 5 6 7 8 9 10 11 12
| class Model { public: Texture diffuseTexture; bool loadDiffuseTexture(const std::string& filename) { return diffuseTexture.loadFromTGA(filename); } void renderSolid(Framebuffer& fb, const vec3f& lightDir, const vec3f& eye) { } };
|
4. 主程序调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int main() { Framebuffer framebuffer(800, 800); framebuffer.clear(vec3f(0.5f, 0.5f, 0.5f)); framebuffer.clearZBuffer();
Model model; if (!model.loadFromObj("resources/obj/african_head.obj")) { std::cerr << "Failed to load model" << std::endl; return 1; }
if (!model.loadDiffuseTexture("resources/diffuse/african_head_diffuse.tga")) { std::cerr << "Failed to load texture" << std::endl; return 1; }
model.renderSolid(framebuffer, vec3f(1.0f, 1.0f, 1.0f), vec3f(0.0f, 0.0f, 1.0f)); framebuffer.flipVertical(); framebuffer.saveToTGA("output.tga");
return 0; }
|
技术要点
- TGA 格式支持:扩展 loadTGA 函数,支持 datatypecode == 2(未压缩 RGB)和 datatypecode == 10(RLE 压缩 RGB)。
- RLE 解码:实现 RLE 压缩的解码逻辑,处理 raw 和 RLE 数据包。
- 颜色转换:将 TGA 文件的 BGR 格式转换为 RGB 格式。
- 错误处理:添加详细的调试输出,确保加载失败时能定位问题。
验证方法
- 检查输出图像 output.tga,确认模型表面显示正确的 diffuse 纹理。
- 验证纹理坐标 (u, v) 的插值是否正确,纹理无拉伸或错位。
- 确保 RLE 压缩的 TGA 文件能够正常加载并渲染。
- 检查程序运行时无 “Failed to load texture” 错误输出。
修改过程回顾
最初,程序因 “Failed to load texture” 而失败,原因是 african_head_diffuse.tga 文件使用了 RLE 压缩(datatypecode == 10),而原始代码只支持未压缩格式(datatypecode == 2)。通过调试输出确认问题后,我扩展了 loadTGA 函数,添加了对 RLE 压缩的支持,最终成功加载并渲染了 diffuse 材质。