SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
OpenGLCubemapTexture.cpp
Go to the documentation of this file.
2#include <Core/Logger.hpp>
3#include <stb_image.h>
4#include <vector>
5#include <cmath>
6
7namespace Sleak {
8namespace RenderEngine {
9
11
13 if (m_texture != 0) {
14 glDeleteTextures(1, &m_texture);
15 m_texture = 0;
16 }
17}
18
19bool OpenGLCubemapTexture::LoadCubemap(const std::array<std::string, 6>& facePaths) {
20 glGenTextures(1, &m_texture);
21 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
22
23 stbi_set_flip_vertically_on_load(false);
24
25 for (int i = 0; i < 6; ++i) {
26 int w, h, channels;
27 unsigned char* pixels = stbi_load(facePaths[i].c_str(), &w, &h, &channels, 3);
28 if (!pixels) {
29 SLEAK_ERROR("OpenGLCubemapTexture: Failed to load face {}: {}",
30 i, facePaths[i]);
31 glDeleteTextures(1, &m_texture);
32 m_texture = 0;
33 return false;
34 }
35
36 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
37 0, GL_RGB,
38 w, h, 0,
39 GL_RGB, GL_UNSIGNED_BYTE, pixels);
40
41 if (i == 0) {
42 m_width = static_cast<uint32_t>(w);
43 m_height = static_cast<uint32_t>(h);
44 }
45
46 stbi_image_free(pixels);
47 }
48
49 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
50 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
51 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
52 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
53 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
54
55 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
56 m_format = TextureFormat::RGB8;
57 return true;
58}
59
60bool OpenGLCubemapTexture::LoadEquirectangular(const std::string& path,
61 uint32_t faceSize) {
62 stbi_set_flip_vertically_on_load(false);
63
64 // Detect HDR via filename suffix — stb_image's stbi_is_hdr is more
65 // authoritative but also more side-effecty. Path check is enough here.
66 bool isHDR = false;
67 if (path.size() >= 4) {
68 const std::string ext = path.substr(path.size() - 4);
69 if (ext == ".hdr" || ext == ".HDR") isHDR = true;
70 }
71
72 int panW = 0, panH = 0, channels = 0;
73 unsigned char* panoramaLDR = nullptr;
74 float* panoramaHDR = nullptr;
75 if (isHDR) {
76 panoramaHDR = stbi_loadf(path.c_str(), &panW, &panH, &channels, 3);
77 if (!panoramaHDR) {
78 SLEAK_ERROR("OpenGLCubemapTexture: Failed to load HDR panorama: {}", path);
79 return false;
80 }
81 } else {
82 panoramaLDR = stbi_load(path.c_str(), &panW, &panH, &channels, 3);
83 if (!panoramaLDR) {
84 SLEAK_ERROR("OpenGLCubemapTexture: Failed to load panorama: {}", path);
85 return false;
86 }
87 }
88
89 glGenTextures(1, &m_texture);
90 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
91
92 m_width = faceSize;
93 m_height = faceSize;
94
95 std::vector<unsigned char> faceLDR;
96 std::vector<float> faceHDR;
97 if (isHDR) faceHDR.resize(faceSize * faceSize * 3);
98 else faceLDR.resize(faceSize * faceSize * 3);
99
100 auto sampleChannel = [&](int x0, int y0, int x1, int y1,
101 float fx, float fy, int c) -> float {
102 if (isHDR) {
103 float c00 = panoramaHDR[(y0 * panW + x0) * 3 + c];
104 float c10 = panoramaHDR[(y0 * panW + x1) * 3 + c];
105 float c01 = panoramaHDR[(y1 * panW + x0) * 3 + c];
106 float c11 = panoramaHDR[(y1 * panW + x1) * 3 + c];
107 return c00 * (1 - fx) * (1 - fy) + c10 * fx * (1 - fy)
108 + c01 * (1 - fx) * fy + c11 * fx * fy;
109 } else {
110 float c00 = panoramaLDR[(y0 * panW + x0) * 3 + c];
111 float c10 = panoramaLDR[(y0 * panW + x1) * 3 + c];
112 float c01 = panoramaLDR[(y1 * panW + x0) * 3 + c];
113 float c11 = panoramaLDR[(y1 * panW + x1) * 3 + c];
114 return c00 * (1 - fx) * (1 - fy) + c10 * fx * (1 - fy)
115 + c01 * (1 - fx) * fy + c11 * fx * fy;
116 }
117 };
118
119 // For each cubemap face, map each pixel to a 3D direction,
120 // convert to equirectangular UV, and sample the panorama.
121 for (int face = 0; face < 6; ++face) {
122 for (uint32_t y = 0; y < faceSize; ++y) {
123 for (uint32_t x = 0; x < faceSize; ++x) {
124 float u = (2.0f * (x + 0.5f) / faceSize) - 1.0f;
125 float v = (2.0f * (y + 0.5f) / faceSize) - 1.0f;
126
127 float dx, dy, dz;
128 switch (face) {
129 case 0: dx = 1.0f; dy = -v; dz = -u; break; // +X
130 case 1: dx = -1.0f; dy = -v; dz = u; break; // -X
131 case 2: dx = u; dy = 1.0f; dz = v; break; // +Y
132 case 3: dx = u; dy = -1.0f; dz = -v; break; // -Y
133 case 4: dx = u; dy = -v; dz = 1.0f; break; // +Z
134 case 5: dx = -u; dy = -v; dz = -1.0f; break; // -Z
135 default: dx = dy = dz = 0.0f; break;
136 }
137
138 float len = std::sqrt(dx * dx + dy * dy + dz * dz);
139 dx /= len; dy /= len; dz /= len;
140
141 float lon = std::atan2(dz, dx);
142 float lat = std::asin(std::clamp(dy, -1.0f, 1.0f));
143
144 float panU = 0.5f + lon / (2.0f * 3.14159265f);
145 float panV = 0.5f - lat / 3.14159265f;
146
147 float srcX = panU * (panW - 1);
148 float srcY = panV * (panH - 1);
149 int x0 = static_cast<int>(srcX);
150 int y0 = static_cast<int>(srcY);
151 int x1 = std::min(x0 + 1, panW - 1);
152 int y1 = std::min(y0 + 1, panH - 1);
153 x0 = std::clamp(x0, 0, panW - 1);
154 y0 = std::clamp(y0, 0, panH - 1);
155 float fx = srcX - x0;
156 float fy = srcY - y0;
157
158 size_t idx = (y * faceSize + x) * 3;
159 for (int c = 0; c < 3; ++c) {
160 float val = sampleChannel(x0, y0, x1, y1, fx, fy, c);
161 if (isHDR) {
162 faceHDR[idx + c] = val;
163 } else {
164 faceLDR[idx + c] = static_cast<unsigned char>(
165 std::clamp(val, 0.0f, 255.0f));
166 }
167 }
168 }
169 }
170
171 if (isHDR) {
172 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
173 0, GL_RGB16F,
174 faceSize, faceSize, 0,
175 GL_RGB, GL_FLOAT, faceHDR.data());
176 } else {
177 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
178 0, GL_RGB,
179 faceSize, faceSize, 0,
180 GL_RGB, GL_UNSIGNED_BYTE, faceLDR.data());
181 }
182 }
183
184 if (panoramaHDR) stbi_image_free(panoramaHDR);
185 if (panoramaLDR) stbi_image_free(panoramaLDR);
186
187 // Mipmaps are required for the IBL prefilter pass which calls
188 // textureLod(env, dir, mipLevel) with mipLevel > 0 to combat
189 // bright-pixel aliasing on importance sampling.
190 glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
191 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
192 GL_LINEAR_MIPMAP_LINEAR);
193 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
194 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
195 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
196 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
197
198 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
199 // Public TextureFormat enum has no float variant — tag as RGB8 either way;
200 // the actual GL internal format (RGB16F vs RGB) is what matters for sampling.
201 m_format = TextureFormat::RGB8;
202
203 SLEAK_INFO("OpenGLCubemapTexture: Loaded {} panorama ({}x{} -> {} face)",
204 isHDR ? "HDR" : "LDR", panW, panH, faceSize);
205 return true;
206}
207
208bool OpenGLCubemapTexture::LoadGradient(float topR, float topG, float topB,
209 float midR, float midG, float midB,
210 float botR, float botG, float botB,
211 uint32_t resolution) {
212 glGenTextures(1, &m_texture);
213 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
214
215 m_width = resolution;
216 m_height = resolution;
217
218 std::vector<unsigned char> faceData(resolution * resolution * 3);
219
220 for (int face = 0; face < 6; ++face) {
221 for (uint32_t y = 0; y < resolution; ++y) {
222 // Map y to a direction: top of face = +Y, bottom = -Y
223 float v = 1.0f - 2.0f * static_cast<float>(y) / (resolution - 1);
224
225 float r, g, b;
226 if (v >= 0.0f) {
227 // Lerp from mid to top
228 r = midR + (topR - midR) * v;
229 g = midG + (topG - midG) * v;
230 b = midB + (topB - midB) * v;
231 } else {
232 // Lerp from mid to bottom
233 float t = -v;
234 r = midR + (botR - midR) * t;
235 g = midG + (botG - midG) * t;
236 b = midB + (botB - midB) * t;
237 }
238
239 for (uint32_t x = 0; x < resolution; ++x) {
240 size_t idx = (y * resolution + x) * 3;
241 faceData[idx + 0] = static_cast<unsigned char>(r * 255.0f);
242 faceData[idx + 1] = static_cast<unsigned char>(g * 255.0f);
243 faceData[idx + 2] = static_cast<unsigned char>(b * 255.0f);
244 }
245 }
246
247 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
248 0, GL_RGB,
249 resolution, resolution, 0,
250 GL_RGB, GL_UNSIGNED_BYTE, faceData.data());
251 }
252
253 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
254 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
255 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
256 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
257 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
258
259 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
260 m_format = TextureFormat::RGB8;
261 return true;
262}
263
264bool OpenGLCubemapTexture::LoadFromMemory(const void* /*data*/, uint32_t /*width*/,
265 uint32_t /*height*/, TextureFormat /*format*/) {
266 SLEAK_ERROR("OpenGLCubemapTexture: LoadFromMemory not supported, use LoadCubemap()");
267 return false;
268}
269
270bool OpenGLCubemapTexture::LoadFromFile(const std::string& /*filePath*/) {
271 SLEAK_ERROR("OpenGLCubemapTexture: LoadFromFile not supported, use LoadCubemap()");
272 return false;
273}
274
275void OpenGLCubemapTexture::Bind(uint32_t slot) const {
276 glActiveTexture(GL_TEXTURE0 + slot);
277 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
278}
279
281 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
282}
283
285 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
286 switch (filter) {
288 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
289 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
290 break;
292 default:
293 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
294 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
295 break;
296 }
297 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
298}
299
301 // Cubemaps always use CLAMP_TO_EDGE
302 glBindTexture(GL_TEXTURE_CUBE_MAP, m_texture);
303 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
304 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
305 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
306 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
307}
308
309} // namespace RenderEngine
310} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
bool LoadCubemap(const std::array< std::string, 6 > &facePaths)
Load 6 face images: +X, -X, +Y, -Y, +Z, -Z.
void Bind(uint32_t slot=0) const override
Binds the cubemap to a texture unit.
bool LoadFromMemory(const void *data, uint32_t width, uint32_t height, TextureFormat format) override
Unused for cubemaps; always returns false, load via LoadCubemap/LoadEquirectangular instead.
bool LoadFromFile(const std::string &filePath) override
Unused for cubemaps; always returns false, load via LoadCubemap/LoadEquirectangular instead.
bool LoadEquirectangular(const std::string &path, uint32_t faceSize=512)
Load from a single equirectangular panorama and convert to cubemap.
void SetWrapMode(TextureWrapMode wrapMode) override
void SetFilter(TextureFilter filter) override
bool LoadGradient(float topR, float topG, float topB, float midR, float midG, float midB, float botR, float botG, float botB, uint32_t resolution=64)
Generate a procedural gradient cubemap (top/mid/bottom colors).
TextureFormat
Definition Texture.hpp:10
TextureFilter
Definition Texture.hpp:28
TextureWrapMode
Definition Texture.hpp:43
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10