Skip to content

Commit a27f10c

Browse files
committed
Load single pixel textures
1 parent ca3d149 commit a27f10c

File tree

3 files changed

+212
-96
lines changed

3 files changed

+212
-96
lines changed

src/common/gltf_model.cpp

Lines changed: 200 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "assert.hpp"
22
#include "gltf_model.hpp"
33
#include "texture.hpp"
4-
#include "vector_set.hpp"
54

65
#include <cgltf.h>
76
#include <fmt/core.h>
@@ -12,6 +11,7 @@
1211
#include <stb_image.h>
1312

1413
#include <algorithm>
14+
#include <cstddef>
1515
#include <cstdint>
1616
#include <cstring>
1717
#include <filesystem>
@@ -70,6 +70,195 @@ void traverseNodeHierarchy(
7070
}
7171
}
7272
}
73+
74+
Texture textureFromGltfImage(const cgltf_image* const image, const fs::path& gltfPath)
75+
{
76+
if (image->buffer_view)
77+
{
78+
const auto pixelData = [image]() -> std::span<const std::uint8_t> {
79+
// NOTE: cgltf_buffer_view_data used as a reference for this function
80+
const cgltf_buffer_view* const bufferView = image->buffer_view;
81+
NLRS_ASSERT(bufferView != nullptr);
82+
const std::size_t byteLength = bufferView->size;
83+
84+
// See cgltf_buffer_view::data comment, overrides buffer->data if
85+
// present
86+
if (bufferView->data != nullptr)
87+
{
88+
const std::uint8_t* const bufferPtr =
89+
static_cast<const std::uint8_t*>(bufferView->data);
90+
return std::span(bufferPtr, byteLength);
91+
}
92+
93+
NLRS_ASSERT(bufferView->buffer != nullptr);
94+
const std::size_t bufferOffset = bufferView->offset;
95+
const std::uint8_t* const bufferPtr =
96+
static_cast<const std::uint8_t*>(bufferView->buffer->data);
97+
98+
return std::span(bufferPtr + bufferOffset, byteLength);
99+
}();
100+
return Texture::fromMemory(pixelData);
101+
}
102+
else
103+
{
104+
NLRS_ASSERT(image->uri);
105+
const fs::path imagePath = gltfPath.parent_path() / image->uri;
106+
if (!fs::exists(imagePath))
107+
{
108+
throw std::runtime_error(
109+
fmt::format("The image {} does not exist.", imagePath.string()));
110+
}
111+
112+
const std::size_t fileSize = static_cast<std::size_t>(fs::file_size(imagePath));
113+
NLRS_ASSERT(fileSize > 0);
114+
std::ifstream file(imagePath, std::ios::binary);
115+
NLRS_ASSERT(file.is_open());
116+
117+
std::vector<std::uint8_t> fileData(fileSize, 0);
118+
file.read(reinterpret_cast<char*>(fileData.data()), fileSize);
119+
return Texture::fromMemory(std::span<const std::uint8_t>(fileData));
120+
}
121+
}
122+
123+
std::uint32_t fnv1a(const void* data, std::size_t size)
124+
{
125+
const std::uint32_t fnvPrime = 16777619;
126+
const std::uint32_t fnvOffsetBasis = 2166136261;
127+
const unsigned char* ptr = static_cast<const unsigned char*>(data);
128+
std::uint32_t hash = fnvOffsetBasis;
129+
for (std::size_t i = 0; i < size; ++i)
130+
{
131+
hash ^= ptr[i];
132+
hash *= fnvPrime;
133+
}
134+
return hash;
135+
}
136+
137+
template<std::size_t N>
138+
std::uint32_t fnv1a(const float (&data)[N])
139+
{
140+
return fnv1a(data, N * sizeof(float));
141+
}
142+
143+
class BaseColorTextureBuilder
144+
{
145+
public:
146+
BaseColorTextureBuilder(const fs::path gltfPath, const std::span<const cgltf_image> gltfImages)
147+
: mGltfPath(gltfPath),
148+
mImages(gltfImages),
149+
mTextures(),
150+
mImageLookups(),
151+
mBaseColorFactorLookups(),
152+
mMeshTextureIndices()
153+
{
154+
}
155+
~BaseColorTextureBuilder() = default;
156+
157+
BaseColorTextureBuilder(const BaseColorTextureBuilder&) = delete;
158+
BaseColorTextureBuilder& operator=(const BaseColorTextureBuilder&) = delete;
159+
160+
BaseColorTextureBuilder(BaseColorTextureBuilder&&) = delete;
161+
BaseColorTextureBuilder& operator=(BaseColorTextureBuilder&&) = delete;
162+
163+
std::tuple<std::vector<std::size_t>, std::vector<Texture>> build()
164+
{
165+
mImageLookups.clear();
166+
mBaseColorFactorLookups.clear();
167+
return std::make_tuple(std::move(mMeshTextureIndices), std::move(mTextures));
168+
}
169+
170+
void addBaseColor(const cgltf_pbr_metallic_roughness& pbrMetallicRoughness)
171+
{
172+
NLRS_ASSERT(pbrMetallicRoughness.base_color_texture.texcoord == 0);
173+
NLRS_ASSERT(pbrMetallicRoughness.base_color_texture.has_transform == false);
174+
175+
const std::size_t textureIdx = [&pbrMetallicRoughness, this]() -> std::size_t {
176+
if (pbrMetallicRoughness.base_color_texture.texture != nullptr)
177+
{
178+
const cgltf_texture& baseColorTexture =
179+
*pbrMetallicRoughness.base_color_texture.texture;
180+
// Look up OpenGL texture wrap modes:
181+
// https://registry.khronos.org/OpenGL/api/GL/glcorearb.h
182+
// GL_REPEAT: 10497
183+
// GL_MIRRORED_REPEAT: 33648
184+
// GL_CLAMP_TO_EDGE: 33071
185+
// GL_CLAMP_TO_BORDER: 33069
186+
NLRS_ASSERT(baseColorTexture.sampler->wrap_s == 10497);
187+
NLRS_ASSERT(baseColorTexture.sampler->wrap_t == 10497);
188+
NLRS_ASSERT(baseColorTexture.image);
189+
190+
const cgltf_image* const baseColorImage = baseColorTexture.image;
191+
const auto distance = std::distance(mImages.data(), baseColorImage);
192+
NLRS_ASSERT(distance >= 0);
193+
const std::size_t imageIndex = static_cast<std::size_t>(distance);
194+
NLRS_ASSERT(imageIndex < mImages.size());
195+
const auto imageLookup = std::find_if(
196+
mImageLookups.begin(),
197+
mImageLookups.end(),
198+
[imageIndex](const ImageLookup& lookup) -> bool {
199+
return lookup.gltfImageIndex == imageIndex;
200+
});
201+
if (imageLookup == mImageLookups.end())
202+
{
203+
const std::size_t textureIdx = mTextures.size();
204+
mImageLookups.push_back({imageIndex, textureIdx});
205+
mTextures.emplace_back(textureFromGltfImage(baseColorImage, mGltfPath));
206+
return textureIdx;
207+
}
208+
else
209+
{
210+
return imageLookup->textureIndex;
211+
}
212+
}
213+
else
214+
{
215+
const std::uint32_t hash = fnv1a(pbrMetallicRoughness.base_color_factor);
216+
const auto colorLookup = std::find_if(
217+
mBaseColorFactorLookups.begin(),
218+
mBaseColorFactorLookups.end(),
219+
[hash](const BaseColorFactorLookup& lookup) -> bool {
220+
return lookup.hash == hash;
221+
});
222+
if (colorLookup == mBaseColorFactorLookups.end())
223+
{
224+
const std::size_t textureIdx = mTextures.size();
225+
mBaseColorFactorLookups.push_back({hash, textureIdx});
226+
227+
const float r = pbrMetallicRoughness.base_color_factor[0];
228+
const float g = pbrMetallicRoughness.base_color_factor[1];
229+
const float b = pbrMetallicRoughness.base_color_factor[2];
230+
const float a = pbrMetallicRoughness.base_color_factor[3];
231+
mTextures.emplace_back(Texture::fromPixel(r, g, b, a));
232+
return textureIdx;
233+
}
234+
else
235+
{
236+
return colorLookup->textureIndex;
237+
}
238+
}
239+
}();
240+
mMeshTextureIndices.push_back(textureIdx);
241+
}
242+
243+
private:
244+
struct ImageLookup
245+
{
246+
std::size_t gltfImageIndex;
247+
std::size_t textureIndex;
248+
};
249+
250+
struct BaseColorFactorLookup
251+
{
252+
std::uint32_t hash;
253+
std::size_t textureIndex;
254+
};
255+
fs::path mGltfPath;
256+
std::span<const cgltf_image> mImages;
257+
std::vector<Texture> mTextures;
258+
std::vector<ImageLookup> mImageLookups;
259+
std::vector<BaseColorFactorLookup> mBaseColorFactorLookups;
260+
std::vector<std::size_t> mMeshTextureIndices;
261+
};
73262
} // namespace
74263

75264
GltfModel::GltfModel(const fs::path gltfPath)
@@ -121,8 +310,9 @@ GltfModel::GltfModel(const fs::path gltfPath)
121310
std::vector<std::vector<glm::vec3>> meshNormals;
122311
std::vector<std::vector<glm::vec2>> meshTexCoords;
123312
std::vector<std::vector<std::uint32_t>> meshIndices;
124-
std::vector<const cgltf_image*> meshBaseColorImageAttributes;
125-
VectorSet<const cgltf_image*> uniqueBaseColorImages;
313+
314+
BaseColorTextureBuilder baseColorTextureBuilder{
315+
gltfPath, std::span<const cgltf_image>(data->images, data->images_count)};
126316

127317
const std::size_t meshCount = data->meshes_count;
128318
for (std::size_t meshIdx = 0; meshIdx < meshCount; ++meshIdx)
@@ -139,25 +329,7 @@ GltfModel::GltfModel(const fs::path gltfPath)
139329
NLRS_ASSERT(primitive.material->has_pbr_metallic_roughness);
140330
const cgltf_pbr_metallic_roughness& pbrMetallicRoughness =
141331
primitive.material->pbr_metallic_roughness;
142-
143-
NLRS_ASSERT(pbrMetallicRoughness.base_color_texture.texcoord == 0);
144-
NLRS_ASSERT(pbrMetallicRoughness.base_color_texture.scale == 1.f);
145-
NLRS_ASSERT(pbrMetallicRoughness.base_color_texture.texture);
146-
147-
const cgltf_texture& baseColorTexture =
148-
*pbrMetallicRoughness.base_color_texture.texture;
149-
// Look up OpenGL texture wrap modes:
150-
// https://registry.khronos.org/OpenGL/api/GL/glcorearb.h
151-
// GL_REPEAT: 10497
152-
// GL_MIRRORED_REPEAT: 33648
153-
// GL_CLAMP_TO_EDGE: 33071
154-
// GL_CLAMP_TO_BORDER: 33069
155-
NLRS_ASSERT(baseColorTexture.sampler->wrap_s == 10497);
156-
NLRS_ASSERT(baseColorTexture.sampler->wrap_t == 10497);
157-
NLRS_ASSERT(baseColorTexture.image);
158-
const cgltf_image* const baseColorImage = baseColorTexture.image;
159-
meshBaseColorImageAttributes.push_back(baseColorImage);
160-
uniqueBaseColorImages.insert(baseColorImage);
332+
baseColorTextureBuilder.addBaseColor(pbrMetallicRoughness);
161333
}
162334

163335
// Indices
@@ -265,81 +437,13 @@ GltfModel::GltfModel(const fs::path gltfPath)
265437
}
266438
}
267439

268-
// Mesh texture indices
269-
270-
NLRS_ASSERT(meshPositions.size() == meshBaseColorImageAttributes.size());
271-
std::vector<std::uint32_t> meshBaseColorTextureIndices;
272-
meshBaseColorTextureIndices.reserve(meshBaseColorImageAttributes.size());
273-
std::transform(
274-
meshBaseColorImageAttributes.begin(),
275-
meshBaseColorImageAttributes.end(),
276-
std::back_inserter(meshBaseColorTextureIndices),
277-
[&](const cgltf_image* image) -> std::uint32_t {
278-
const auto imageIter = uniqueBaseColorImages.find(image);
279-
NLRS_ASSERT(imageIter != uniqueBaseColorImages.end());
280-
const auto distance = std::distance(uniqueBaseColorImages.begin(), imageIter);
281-
NLRS_ASSERT(distance >= 0);
282-
const auto textureIdx = static_cast<std::uint32_t>(distance);
283-
NLRS_ASSERT(textureIdx < meshBaseColorImageAttributes.size());
284-
return textureIdx;
285-
});
286-
287-
// Model textures
288-
289-
auto bufferViewData =
290-
[](const cgltf_buffer_view* const bufferView) -> std::span<const std::uint8_t> {
291-
// NOTE: cgltf_buffer_view_data used as a reference for this function
292-
NLRS_ASSERT(bufferView != nullptr);
293-
const std::size_t byteLength = bufferView->size;
294-
295-
// See cgltf_buffer_view::data comment, overrides buffer->data if present
296-
if (bufferView->data != nullptr)
297-
{
298-
const std::uint8_t* const bufferPtr =
299-
static_cast<const std::uint8_t*>(bufferView->data);
300-
return std::span(bufferPtr, byteLength);
301-
}
302-
303-
NLRS_ASSERT(bufferView->buffer != nullptr);
304-
const std::size_t bufferOffset = bufferView->offset;
305-
const std::uint8_t* const bufferPtr =
306-
static_cast<const std::uint8_t*>(bufferView->buffer->data);
307-
308-
return std::span(bufferPtr + bufferOffset, byteLength);
309-
};
310-
311-
baseColorTextures.reserve(uniqueBaseColorImages.size());
312-
std::transform(
313-
uniqueBaseColorImages.begin(),
314-
uniqueBaseColorImages.end(),
315-
std::back_inserter(baseColorTextures),
316-
[&](const cgltf_image* image) -> Texture {
317-
if (image->buffer_view)
318-
{
319-
const auto pixelData = bufferViewData(image->buffer_view);
320-
return Texture::fromMemory(pixelData);
321-
}
322-
else
323-
{
324-
NLRS_ASSERT(image->uri);
325-
const fs::path imagePath = gltfPath.parent_path() / image->uri;
326-
if (!fs::exists(imagePath))
327-
{
328-
throw std::runtime_error(
329-
fmt::format("The image {} does not exist.", imagePath.string()));
330-
}
331-
332-
const std::size_t fileSize = static_cast<std::size_t>(fs::file_size(imagePath));
333-
NLRS_ASSERT(fileSize > 0);
334-
std::ifstream file(imagePath, std::ios::binary);
335-
NLRS_ASSERT(file.is_open());
336-
337-
std::vector<std::uint8_t> fileData(fileSize, 0);
338-
file.read(reinterpret_cast<char*>(fileData.data()), fileSize);
339-
return Texture::fromMemory(std::span(fileData));
340-
}
341-
});
440+
auto [meshBaseColorTextureIndices, textures] = baseColorTextureBuilder.build();
441+
baseColorTextures = std::move(textures);
342442

443+
NLRS_ASSERT(meshPositions.size() == meshNormals.size());
444+
NLRS_ASSERT(meshPositions.size() == meshTexCoords.size());
445+
NLRS_ASSERT(meshPositions.size() == meshIndices.size());
446+
NLRS_ASSERT(meshPositions.size() == meshBaseColorTextureIndices.size());
343447
meshes.reserve(meshPositions.size());
344448
for (std::size_t i = 0; i < meshPositions.size(); ++i)
345449
{

src/common/texture.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,15 @@ Texture Texture::fromMemory(std::span<const std::uint8_t> data)
5252
std::move(pixels),
5353
Dimensions{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)});
5454
}
55+
56+
Texture Texture::fromPixel(float r, float g, float b, float a)
57+
{
58+
const std::uint32_t r8 = static_cast<std::uint32_t>(r * 255.0f);
59+
const std::uint32_t g8 = static_cast<std::uint32_t>(g * 255.0f);
60+
const std::uint32_t b8 = static_cast<std::uint32_t>(b * 255.0f);
61+
const std::uint32_t a8 = static_cast<std::uint32_t>(a * 255.0f);
62+
63+
return Texture(
64+
std::vector<BgraPixel>{b8 | (g8 << 8) | (r8 << 16) | (a8 << 24)}, Dimensions{1, 1});
65+
}
5566
} // namespace nlrs

src/common/texture.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Texture
3939

4040
// `data` is expected to be in RGBA or RGB format, with each component 8 bits.
4141
static Texture fromMemory(std::span<const std::uint8_t> data);
42+
static Texture fromPixel(float r, float g, float b, float a);
4243

4344
private:
4445
std::vector<BgraPixel> mPixels;

0 commit comments

Comments
 (0)