• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 9244753701

26 May 2024 01:22PM UTC coverage: 79.407% (+0.02%) from 79.389%
9244753701

push

github

Razakhel
[Shaders] Removed the manual gamma decoding on texture read

- As the color-purposed textures should now properly be treated as sRGB(A), it is not needed anymore

- Added a temporary gamma encoding after each shader when missing, as now everything is properly computed linearly and needs to be corrected before being displayed
  - This will later be replaced by a final render pass

7959 of 10023 relevant lines covered (79.41%)

2118.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.95
/src/RaZ/Data/ObjLoad.cpp
1
#include "RaZ/Data/Color.hpp"
2
#include "RaZ/Data/Image.hpp"
3
#include "RaZ/Data/ImageFormat.hpp"
4
#include "RaZ/Data/Mesh.hpp"
5
#include "RaZ/Data/ObjFormat.hpp"
6
#include "RaZ/Render/MeshRenderer.hpp"
7
#include "RaZ/Utils/FilePath.hpp"
8
#include "RaZ/Utils/FileUtils.hpp"
9
#include "RaZ/Utils/Logger.hpp"
10

11
#include "tracy/Tracy.hpp"
12

13
#include <fstream>
14
#include <map>
15
#include <sstream>
16

17
namespace Raz::ObjFormat {
18

19
namespace {
20

21
inline Texture2DPtr loadTexture(const FilePath& textureFilePath, const Color& defaultColor, bool shouldUseSrgb = false) {
34✔
22
  ZoneScopedN("[ObjLoad]::loadTexture");
23

24
  if (!FileUtils::isReadable(textureFilePath)) {
34✔
25
    Logger::warn("[ObjLoad] Cannot load texture '" + textureFilePath + "'; either the file does not exist or it cannot be opened.");
×
26
    return Texture2D::create(defaultColor);
×
27
  }
28

29
  // Always apply a vertical flip to imported textures, since OpenGL maps them upside down
30
  return Texture2D::create(ImageFormat::load(textureFilePath, true), true, shouldUseSrgb);
68✔
31
}
32

33
inline void loadMtl(const FilePath& mtlFilePath,
5✔
34
                    std::vector<Material>& materials,
35
                    std::unordered_map<std::string, std::size_t>& materialCorrespIndices) {
36
  ZoneScopedN("[ObjLoad]::loadMtl");
37

38
  Logger::debug("[ObjLoad] Loading MTL file ('" + mtlFilePath + "')...");
5✔
39

40
  std::ifstream file(mtlFilePath, std::ios_base::in | std::ios_base::binary);
5✔
41

42
  if (!file) {
5✔
43
    Logger::error("[ObjLoad] Could not open the MTL file '" + mtlFilePath + "'.");
×
44
    materials.emplace_back(MaterialType::COOK_TORRANCE);
×
45
    return;
×
46
  }
47

48
  Material material;
5✔
49
  MaterialType materialType = MaterialType::BLINN_PHONG;
5✔
50

51
  while (!file.eof()) {
82✔
52
    std::string tag;
77✔
53
    std::string nextValue;
77✔
54
    file >> tag >> nextValue;
77✔
55

56
    if (tag[0] == 'K') {                 // Standard properties [K*]
77✔
57
      std::string secondValue;
17✔
58
      std::string thirdValue;
17✔
59
      file >> secondValue >> thirdValue;
17✔
60

61
      const Vec3f values(std::stof(nextValue), std::stof(secondValue), std::stof(thirdValue));
17✔
62

63
      if (tag[1] == 'd')                 // Diffuse/albedo factor [Kd]
17✔
64
        material.getProgram().setAttribute(values, MaterialAttribute::BaseColor);
6✔
65
      else if (tag[1] == 'e')            // Emissive factor [Ke]
11✔
66
        material.getProgram().setAttribute(values, MaterialAttribute::Emissive);
5✔
67
      else if (tag[1] == 'a')            // Ambient factor [Ka]
6✔
68
        material.getProgram().setAttribute(values, MaterialAttribute::Ambient);
3✔
69
      else if (tag[1] == 's')            // Specular factor [Ks]
3✔
70
        material.getProgram().setAttribute(values, MaterialAttribute::Specular);
3✔
71
    } else if (tag[0] == 'P') {          // PBR properties [P*]
77✔
72
      const float factor = std::stof(nextValue);
6✔
73

74
      if (tag[1] == 'm')                 // Metallic factor [Pm]
6✔
75
        material.getProgram().setAttribute(factor, MaterialAttribute::Metallic);
3✔
76
      else if (tag[1] == 'r')            // Roughness factor [Pr]
3✔
77
        material.getProgram().setAttribute(factor, MaterialAttribute::Roughness);
3✔
78

79
      materialType = MaterialType::COOK_TORRANCE;
6✔
80
    } else if (tag[0] == 'm') {          // Import texture [map_*]
54✔
81
      const FilePath textureFilePath = mtlFilePath.recoverPathToFile() + nextValue;
62✔
82

83
      if (tag[4] == 'K') {               // Standard maps [map_K*]
31✔
84
        if (tag[5] == 'd')               // Diffuse/albedo map [map_Kd]
19✔
85
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::White, true), MaterialTexture::BaseColor);
6✔
86
        else if (tag[5] == 'e')          // Emissive map [map_Ke]
13✔
87
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::White, true), MaterialTexture::Emissive);
5✔
88
        else if (tag[5] == 'a')          // Ambient/ambient occlusion map [map_Ka]
8✔
89
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::White, true), MaterialTexture::Ambient);
5✔
90
        else if (tag[5] == 's')          // Specular map [map_Ks]
3✔
91
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::White, true), MaterialTexture::Specular);
3✔
92
      } else if (tag[4] == 'P') {        // PBR maps [map_P*]
12✔
93
        if (tag[5] == 'm')               // Metallic map [map_Pm]
6✔
94
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::Red), MaterialTexture::Metallic);
3✔
95
        else if (tag[5] == 'r')          // Roughness map [map_Pr]
3✔
96
          material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::Red), MaterialTexture::Roughness);
3✔
97

98
        materialType = MaterialType::COOK_TORRANCE;
6✔
99
      } else if (tag[4] == 'd') {        // Opacity (dissolve) map [map_d]
6✔
100
        Texture2DPtr map = loadTexture(textureFilePath, ColorPreset::White);
3✔
101
        map->setFilter(TextureFilter::NEAREST, TextureFilter::NEAREST, TextureFilter::NEAREST);
3✔
102
        material.getProgram().setTexture(std::move(map), MaterialTexture::Opacity);
3✔
103
      } else if (tag[4] == 'b') {        // Bump map [map_bump]
6✔
104
        material.getProgram().setTexture(loadTexture(textureFilePath, ColorPreset::White), MaterialTexture::Bump);
3✔
105
      }
106
    } else if (tag[0] == 'd') {          // Opacity (dissolve) factor [d]
54✔
107
      material.getProgram().setAttribute(std::stof(nextValue), MaterialAttribute::Opacity);
1✔
108
    } else if (tag[0] == 'T') {
22✔
109
      if (tag[1] == 'r')                 // Transparency factor (alias, 1 - d) [Tr]
×
110
        material.getProgram().setAttribute(1.f - std::stof(nextValue), MaterialAttribute::Opacity);
×
111
    } else if (tag[0] == 'b') {          // Bump map (alias) [bump]
22✔
112
      material.getProgram().setTexture(loadTexture(mtlFilePath.recoverPathToFile() + nextValue, ColorPreset::White), MaterialTexture::Bump);
×
113
    } else if (tag[0] == 'n') {
22✔
114
      if (tag[1] == 'o') {               // Normal map [norm]
9✔
115
        material.getProgram().setTexture(loadTexture(mtlFilePath.recoverPathToFile() + nextValue, ColorPreset::MediumBlue), MaterialTexture::Normal);
3✔
116
      } else if (tag[1] == 'e') {        // New material [newmtl]
6✔
117
        materialCorrespIndices.emplace(nextValue, materialCorrespIndices.size());
6✔
118

119
        if (material.isEmpty())
6✔
120
          continue;
5✔
121

122
        material.loadType(materialType);
1✔
123
        materials.emplace_back(std::move(material));
1✔
124

125
        material     = Material();
1✔
126
        materialType = MaterialType::BLINN_PHONG;
1✔
127
      }
128
    } else {
129
      std::getline(file, tag); // Skip the rest of the line
13✔
130
    }
131
  }
82✔
132

133
  material.loadType(materialType);
5✔
134
  materials.emplace_back(std::move(material));
5✔
135

136
  Logger::debug("[ObjLoad] Loaded MTL file (" + std::to_string(materials.size()) + " material(s) loaded)");
5✔
137
}
5✔
138

139
} // namespace
140

141
std::pair<Mesh, MeshRenderer> load(const FilePath& filePath) {
8✔
142
  ZoneScopedN("ObjFormat::load");
143

144
  Logger::debug("[ObjLoad] Loading OBJ file ('" + filePath + "')...");
8✔
145

146
  std::ifstream file(filePath, std::ios_base::in | std::ios_base::binary);
8✔
147

148
  if (!file)
8✔
149
    throw std::invalid_argument("Error: Couldn't open the OBJ file '" + filePath + '\'');
×
150

151
  Mesh mesh;
8✔
152
  MeshRenderer meshRenderer;
8✔
153

154
  mesh.addSubmesh();
8✔
155
  meshRenderer.addSubmeshRenderer();
8✔
156

157
  std::unordered_map<std::string, std::size_t> materialCorrespIndices;
8✔
158

159
  std::vector<Vec3f> positions;
8✔
160
  std::vector<Vec2f> texcoords;
8✔
161
  std::vector<Vec3f> normals;
8✔
162

163
  std::vector<std::vector<int64_t>> posIndices(1);
8✔
164
  std::vector<std::vector<int64_t>> texcoordsIndices(1);
8✔
165
  std::vector<std::vector<int64_t>> normalsIndices(1);
8✔
166

167
  while (!file.eof()) {
3,766✔
168
    std::string line;
3,758✔
169
    file >> line;
3,758✔
170

171
    if (line[0] == 'v') {
3,758✔
172
      if (line[1] == 'n') { // Normal
2,508✔
173
        Vec3f normalTriplet;
794✔
174

175
        file >> normalTriplet.x()
794✔
176
             >> normalTriplet.y()
794✔
177
             >> normalTriplet.z();
794✔
178

179
        normals.push_back(normalTriplet);
794✔
180
      } else if (line[1] == 't') { // Texcoords
1,714✔
181
        Vec2f texcoordsTriplet;
906✔
182

183
        file >> texcoordsTriplet.x()
906✔
184
             >> texcoordsTriplet.y();
906✔
185

186
        texcoords.push_back(texcoordsTriplet);
906✔
187
      } else { // Position
188
        Vec3f positionTriplet;
808✔
189

190
        file >> positionTriplet.x()
808✔
191
             >> positionTriplet.y()
808✔
192
             >> positionTriplet.z();
808✔
193

194
        positions.push_back(positionTriplet);
808✔
195
      }
196
    } else if (line[0] == 'f') { // Faces
1,250✔
197
      std::getline(file, line);
1,212✔
198

199
      constexpr char delim  = '/';
1,212✔
200
      const auto nbVertices = static_cast<uint16_t>(std::count(line.cbegin(), line.cend(), ' '));
1,212✔
201
      const auto nbParts    = static_cast<uint8_t>(std::count(line.cbegin(), line.cend(), delim) / nbVertices + 1);
1,212✔
202
      const bool quadFaces  = (nbVertices == 4);
1,212✔
203

204
      std::stringstream indicesStream(line);
1,212✔
205
      std::vector<int64_t> partIndices(nbParts * nbVertices);
1,212✔
206
      std::string vertex;
1,212✔
207

208
      for (std::size_t vertIndex = 0; vertIndex < nbVertices; ++vertIndex) {
5,208✔
209
        indicesStream >> vertex;
3,996✔
210

211
        std::stringstream vertParts(vertex);
3,996✔
212
        std::string part;
3,996✔
213
        uint8_t partIndex = 0;
3,996✔
214

215
        while (std::getline(vertParts, part, delim)) {
15,984✔
216
          if (!part.empty())
11,988✔
217
            partIndices[partIndex * nbParts + vertIndex + (partIndex * quadFaces)] = std::stol(part);
11,988✔
218

219
          ++partIndex;
11,988✔
220
        }
221
      }
3,996✔
222

223
      if (quadFaces) {
1,212✔
224
        posIndices.back().emplace_back(partIndices[0]);
360✔
225
        posIndices.back().emplace_back(partIndices[2]);
360✔
226
        posIndices.back().emplace_back(partIndices[3]);
360✔
227

228
        texcoordsIndices.back().emplace_back(partIndices[4]);
360✔
229
        texcoordsIndices.back().emplace_back(partIndices[6]);
360✔
230
        texcoordsIndices.back().emplace_back(partIndices[7]);
360✔
231

232
        normalsIndices.back().emplace_back(partIndices[8]);
360✔
233
        normalsIndices.back().emplace_back(partIndices[10]);
360✔
234
        normalsIndices.back().emplace_back(partIndices[11]);
360✔
235
      }
236

237
      posIndices.back().emplace_back(partIndices[0]);
1,212✔
238
      posIndices.back().emplace_back(partIndices[1]);
1,212✔
239
      posIndices.back().emplace_back(partIndices[2]);
1,212✔
240

241
      texcoordsIndices.back().emplace_back(partIndices[3 + quadFaces]);
1,212✔
242
      texcoordsIndices.back().emplace_back(partIndices[4 + quadFaces]);
1,212✔
243
      texcoordsIndices.back().emplace_back(partIndices[5 + quadFaces]);
1,212✔
244

245
      const auto quadStride = static_cast<uint8_t>(quadFaces * 2);
1,212✔
246

247
      normalsIndices.back().emplace_back(partIndices[6 + quadStride]);
1,212✔
248
      normalsIndices.back().emplace_back(partIndices[7 + quadStride]);
1,212✔
249
      normalsIndices.back().emplace_back(partIndices[8 + quadStride]);
1,212✔
250
    } else if (line[0] == 'm') { // Material import (mtllib)
1,250✔
251
      std::string mtlFileName;
5✔
252
      file >> mtlFileName;
5✔
253

254
      const std::string mtlFilePath = filePath.recoverPathToFile() + mtlFileName;
5✔
255
      loadMtl(mtlFilePath, meshRenderer.getMaterials(), materialCorrespIndices);
5✔
256
    } else if (line[0] == 'u') { // Material usage (usemtl)
38✔
257
      if (materialCorrespIndices.empty())
7✔
258
        continue;
1✔
259

260
      std::string materialName;
6✔
261
      file >> materialName;
6✔
262

263
      const auto correspMaterial = materialCorrespIndices.find(materialName);
6✔
264

265
      if (correspMaterial == materialCorrespIndices.cend())
6✔
266
        Logger::error("[ObjLoad] No corresponding material found with the name '" + materialName + "'.");
×
267
      else
268
        meshRenderer.getSubmeshRenderers().back().setMaterialIndex(correspMaterial->second);
6✔
269
    } else if (line[0] == 'o' || line[0] == 'g') {
32✔
270
      if (!posIndices.front().empty()) {
12✔
271
        const std::size_t newSize = posIndices.size() + 1;
2✔
272
        posIndices.resize(newSize);
2✔
273
        texcoordsIndices.resize(newSize);
2✔
274
        normalsIndices.resize(newSize);
2✔
275

276
        mesh.addSubmesh();
2✔
277
        meshRenderer.addSubmeshRenderer().setMaterialIndex(std::numeric_limits<std::size_t>::max());
2✔
278
      }
279

280
      std::getline(file, line);
12✔
281
    } else {
282
      std::getline(file, line); // Skip the rest of the line
14✔
283
    }
284
  }
3,758✔
285

286
  const auto posCount  = static_cast<int64_t>(positions.size());
8✔
287
  const auto texCount  = static_cast<int64_t>(texcoords.size());
8✔
288
  const auto normCount = static_cast<int64_t>(normals.size());
8✔
289

290
  std::map<std::array<std::size_t, 3>, unsigned int> indicesMap;
8✔
291

292
  for (std::size_t submeshIndex = 0; submeshIndex < mesh.getSubmeshes().size(); ++submeshIndex) {
18✔
293
    Submesh& submesh = mesh.getSubmeshes()[submeshIndex];
10✔
294
    indicesMap.clear();
10✔
295

296
    for (std::size_t partIndex = 0; partIndex < posIndices[submeshIndex].size(); ++partIndex) {
1,582✔
297
      // Face (vertices indices triplets), containing position/texcoords/normals
298
      // vertIndices[i][j] -> vertex i, feature j (j = 0 -> position, j = 1 -> texcoords, j = 2 -> normal)
299
      std::array<std::array<std::size_t, 3>, 3> vertIndices {};
1,572✔
300

301
      // First vertex information
302
      int64_t tempIndex = posIndices[submeshIndex][partIndex];
1,572✔
303
      vertIndices[0][0] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + posCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
304

305
      tempIndex = texcoordsIndices[submeshIndex][partIndex];
1,572✔
306
      vertIndices[0][1] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + texCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
307

308
      tempIndex = normalsIndices[submeshIndex][partIndex];
1,572✔
309
      vertIndices[0][2] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + normCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
310

311
      ++partIndex;
1,572✔
312

313
      // Second vertex information
314
      tempIndex = posIndices[submeshIndex][partIndex];
1,572✔
315
      vertIndices[1][0] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + posCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
316

317
      tempIndex = texcoordsIndices[submeshIndex][partIndex];
1,572✔
318
      vertIndices[1][1] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + texCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
319

320
      tempIndex = normalsIndices[submeshIndex][partIndex];
1,572✔
321
      vertIndices[1][2] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + normCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
322

323
      ++partIndex;
1,572✔
324

325
      // Third vertex information
326
      tempIndex = posIndices[submeshIndex][partIndex];
1,572✔
327
      vertIndices[2][0] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + posCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
328

329
      tempIndex = texcoordsIndices[submeshIndex][partIndex];
1,572✔
330
      vertIndices[2][1] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + texCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
331

332
      tempIndex = normalsIndices[submeshIndex][partIndex];
1,572✔
333
      vertIndices[2][2] = (tempIndex < 0 ? static_cast<std::size_t>(tempIndex + normCount) : static_cast<std::size_t>(tempIndex - 1));
1,572✔
334

335
      const std::array<Vec3f, 3> facePositions = { positions[vertIndices[0][0]],
1,572✔
336
                                                   positions[vertIndices[1][0]],
1,572✔
337
                                                   positions[vertIndices[2][0]] };
3,144✔
338

339
      std::array<Vec2f, 3> faceTexcoords {};
1,572✔
340
      if (!texcoords.empty()) {
1,572✔
341
        faceTexcoords[0] = texcoords[vertIndices[0][1]];
1,572✔
342
        faceTexcoords[1] = texcoords[vertIndices[1][1]];
1,572✔
343
        faceTexcoords[2] = texcoords[vertIndices[2][1]];
1,572✔
344
      }
345

346
      std::array<Vec3f, 3> faceNormals {};
1,572✔
347
      if (!normals.empty()) {
1,572✔
348
        faceNormals[0] = normals[vertIndices[0][2]];
1,572✔
349
        faceNormals[1] = normals[vertIndices[1][2]];
1,572✔
350
        faceNormals[2] = normals[vertIndices[2][2]];
1,572✔
351
      }
352

353
      for (uint8_t vertPartIndex = 0; vertPartIndex < 3; ++vertPartIndex) {
6,288✔
354
        const auto indexIter = indicesMap.find(vertIndices[vertPartIndex]);
4,716✔
355

356
        if (indexIter != indicesMap.cend()) {
4,716✔
357
          submesh.getTriangleIndices().emplace_back(indexIter->second);
3,730✔
358
          continue;
3,730✔
359
        }
360

361
        const Vertex vert {
362
          facePositions[vertPartIndex],
986✔
363
          faceTexcoords[vertPartIndex],
986✔
364
          faceNormals[vertPartIndex]
986✔
365
        };
2,958✔
366

367
        submesh.getTriangleIndices().emplace_back(static_cast<unsigned int>(indicesMap.size()));
986✔
368
        indicesMap.emplace(vertIndices[vertPartIndex], static_cast<unsigned int>(indicesMap.size()));
986✔
369
        submesh.getVertices().emplace_back(vert);
986✔
370
      }
371
    }
372
  }
373

374
  mesh.computeTangents();
8✔
375

376
  // Creating the mesh renderer from the mesh's data
377
  meshRenderer.load(mesh);
8✔
378

379
  Logger::debug("[ObjLoad] Loaded OBJ file (" + std::to_string(mesh.getSubmeshes().size()) + " submesh(es), "
24✔
380
                                              + std::to_string(mesh.recoverVertexCount()) + " vertices, "
32✔
381
                                              + std::to_string(mesh.recoverTriangleCount()) + " triangles, "
32✔
382
                                              + std::to_string(meshRenderer.getMaterials().size()) + " material(s))");
32✔
383

384
  return { std::move(mesh), std::move(meshRenderer) };
16✔
385
}
8✔
386

387
} // namespace Raz::ObjFormat
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc