SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
OpenGLIBL.cpp
Go to the documentation of this file.
3#include <Core/Logger.hpp>
4#include <Math/Matrix.hpp>
5#include <Math/Vector.hpp>
6#include <cstring>
7
8namespace Sleak {
9namespace RenderEngine {
10
11namespace {
12
13// Unit cube vertices — each face as two triangles, position only.
14// 6 faces * 2 triangles * 3 verts = 36 vertices, 3 floats each.
15constexpr float kCubeVertices[] = {
16 // -Z
17 -1, -1, -1, 1, 1, -1, 1, -1, -1,
18 -1, -1, -1, -1, 1, -1, 1, 1, -1,
19 // +Z
20 -1, -1, 1, 1, -1, 1, 1, 1, 1,
21 -1, -1, 1, 1, 1, 1, -1, 1, 1,
22 // -X
23 -1, -1, -1, -1, -1, 1, -1, 1, 1,
24 -1, -1, -1, -1, 1, 1, -1, 1, -1,
25 // +X
26 1, -1, -1, 1, 1, 1, 1, -1, 1,
27 1, -1, -1, 1, 1, -1, 1, 1, 1,
28 // -Y
29 -1, -1, -1, 1, -1, -1, 1, -1, 1,
30 -1, -1, -1, 1, -1, 1, -1, -1, 1,
31 // +Y
32 -1, 1, -1, -1, 1, 1, 1, 1, 1,
33 -1, 1, -1, 1, 1, 1, 1, 1, -1,
34};
35
36// 6 capture views (LH, Y-up). The "up" vectors are chosen so that the
37// resulting cubemap face orientation matches GL_TEXTURE_CUBE_MAP_*
38// when the captured cube is sampled with a unit direction vector.
39/// Builds the look-at view matrix for one of the 6 cubemap capture directions.
40Math::Matrix4 BuildCaptureView(int face) {
41 using V3 = Math::Vector<float, 3>;
42 const V3 origin{0.0f, 0.0f, 0.0f};
43 V3 target, up;
44 switch (face) {
45 case 0: target = V3{ 1, 0, 0}; up = V3{0, -1, 0}; break; // +X
46 case 1: target = V3{-1, 0, 0}; up = V3{0, -1, 0}; break; // -X
47 case 2: target = V3{ 0, 1, 0}; up = V3{0, 0, 1}; break; // +Y
48 case 3: target = V3{ 0,-1, 0}; up = V3{0, 0, -1}; break; // -Y
49 case 4: target = V3{ 0, 0, 1}; up = V3{0, -1, 0}; break; // +Z
50 case 5: target = V3{ 0, 0,-1}; up = V3{0, -1, 0}; break; // -Z
51 default: target = V3{ 1, 0, 0}; up = V3{0, -1, 0}; break;
52 }
53 return Math::Matrix4::LookAt(origin, target, up);
54}
55
56} // namespace
57
59
61 if (m_irradianceCubemap) { glDeleteTextures(1, &m_irradianceCubemap); m_irradianceCubemap = 0; }
62 if (m_prefilterCubemap) { glDeleteTextures(1, &m_prefilterCubemap); m_prefilterCubemap = 0; }
63 if (m_brdfLUT) { glDeleteTextures(1, &m_brdfLUT); m_brdfLUT = 0; }
64 if (m_fbo) { glDeleteFramebuffers(1, &m_fbo); m_fbo = 0; }
65 if (m_rbo) { glDeleteRenderbuffers(1, &m_rbo); m_rbo = 0; }
66 if (m_cubeVBO) { glDeleteBuffers(1, &m_cubeVBO); m_cubeVBO = 0; }
67 if (m_cubeVAO) { glDeleteVertexArrays(1, &m_cubeVAO); m_cubeVAO = 0; }
68 if (m_quadVAO) { glDeleteVertexArrays(1, &m_quadVAO); m_quadVAO = 0; }
69 if (m_faceUBO) { glDeleteBuffers(1, &m_faceUBO); m_faceUBO = 0; }
70 if (m_prefilterUBO) { glDeleteBuffers(1, &m_prefilterUBO); m_prefilterUBO = 0; }
71 delete m_irradianceShader; m_irradianceShader = nullptr;
72 delete m_prefilterShader; m_prefilterShader = nullptr;
73 delete m_brdfShader; m_brdfShader = nullptr;
74 m_initialized = false;
75}
76
77bool OpenGLIBL::CreateCubemapTarget(GLuint& tex, uint32_t size, uint32_t mipLevels) {
78 glGenTextures(1, &tex);
79 glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
80 for (int face = 0; face < 6; ++face) {
81 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGB16F,
82 size, size, 0, GL_RGB, GL_FLOAT, nullptr);
83 }
84 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
85 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
86 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
87 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
88 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
89 mipLevels > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
90 if (mipLevels > 1) glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
91 glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
92 return true;
93}
94
95bool OpenGLIBL::Initialize(GLuint sourceCubemap) {
96 if (m_initialized) return true;
97 if (sourceCubemap == 0) {
98 SLEAK_WARN("OpenGLIBL: source cubemap is 0 — IBL precompute skipped");
99 return false;
100 }
101
102 // ---- Cube VAO ----
103 glGenVertexArrays(1, &m_cubeVAO);
104 glGenBuffers(1, &m_cubeVBO);
105 glBindVertexArray(m_cubeVAO);
106 glBindBuffer(GL_ARRAY_BUFFER, m_cubeVBO);
107 glBufferData(GL_ARRAY_BUFFER, sizeof(kCubeVertices), kCubeVertices, GL_STATIC_DRAW);
108 glEnableVertexAttribArray(0);
109 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
110 glBindVertexArray(0);
111
112 // ---- Empty quad VAO for fullscreen BRDF pass ----
113 glGenVertexArrays(1, &m_quadVAO);
114
115 // ---- Shared FBO + depth RBO (resized per pass) ----
116 glGenFramebuffers(1, &m_fbo);
117 glGenRenderbuffers(1, &m_rbo);
118
119 // ---- UBOs ----
120 glGenBuffers(1, &m_faceUBO);
121 glBindBuffer(GL_UNIFORM_BUFFER, m_faceUBO);
122 glBufferData(GL_UNIFORM_BUFFER, 64, nullptr, GL_DYNAMIC_DRAW); // mat4
123 glBindBuffer(GL_UNIFORM_BUFFER, 0);
124
125 glGenBuffers(1, &m_prefilterUBO);
126 glBindBuffer(GL_UNIFORM_BUFFER, m_prefilterUBO);
127 glBufferData(GL_UNIFORM_BUFFER, 80, nullptr, GL_DYNAMIC_DRAW); // mat4 + 4 floats
128 glBindBuffer(GL_UNIFORM_BUFFER, 0);
129
130 // ---- Shaders ----
131 m_irradianceShader = new OpenGLShader();
132 if (!m_irradianceShader->compile("assets/shaders/ibl_irradiance_gl.vert",
133 "assets/shaders/ibl_irradiance_gl.frag")) {
134 SLEAK_ERROR("OpenGLIBL: failed to compile irradiance shader");
135 Cleanup();
136 return false;
137 }
138
139 m_prefilterShader = new OpenGLShader();
140 if (!m_prefilterShader->compile("assets/shaders/ibl_prefilter_gl.vert",
141 "assets/shaders/ibl_prefilter_gl.frag")) {
142 SLEAK_ERROR("OpenGLIBL: failed to compile prefilter shader");
143 Cleanup();
144 return false;
145 }
146
147 m_brdfShader = new OpenGLShader();
148 if (!m_brdfShader->compile("assets/shaders/ibl_brdf_lut_gl.vert",
149 "assets/shaders/ibl_brdf_lut_gl.frag")) {
150 SLEAK_ERROR("OpenGLIBL: failed to compile BRDF LUT shader");
151 Cleanup();
152 return false;
153 }
154
155 // ---- Targets ----
156 CreateCubemapTarget(m_irradianceCubemap, IRRADIANCE_SIZE, 1);
157 CreateCubemapTarget(m_prefilterCubemap, PREFILTER_SIZE, PREFILTER_MIPS);
158
159 glGenTextures(1, &m_brdfLUT);
160 glBindTexture(GL_TEXTURE_2D, m_brdfLUT);
161 glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, BRDF_LUT_SIZE, BRDF_LUT_SIZE,
162 0, GL_RG, GL_FLOAT, nullptr);
163 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
164 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
165 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
166 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
167 glBindTexture(GL_TEXTURE_2D, 0);
168
169 // ---- Save GL state we're about to clobber ----
170 GLint prevFBO = 0, prevViewport[4] = {0,0,0,0};
171 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFBO);
172 glGetIntegerv(GL_VIEWPORT, prevViewport);
173
174 glDisable(GL_DEPTH_TEST);
175 glDisable(GL_BLEND);
176 glDisable(GL_CULL_FACE);
177
178 bool ok = BakeIrradiance(sourceCubemap)
179 && BakePrefilter(sourceCubemap)
180 && BakeBRDFLUT();
181
182 // ---- Restore ----
183 glBindFramebuffer(GL_FRAMEBUFFER, prevFBO);
184 glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]);
185 glEnable(GL_DEPTH_TEST);
186 glEnable(GL_CULL_FACE);
187
188 if (!ok) {
189 SLEAK_ERROR("OpenGLIBL: precompute failed");
190 Cleanup();
191 return false;
192 }
193
194 m_initialized = true;
195 SLEAK_INFO("OpenGLIBL: precompute complete (irradiance {}², prefilter {}² × {} mips, BRDF LUT {}²)",
197 return true;
198}
199
200bool OpenGLIBL::BakeIrradiance(GLuint sourceCubemap) {
202 1.5707963f /* 90° */, 1.0f, 0.1f, 10.0f);
203
204 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
205 glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
206 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
208 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
209 GL_RENDERBUFFER, m_rbo);
210 glViewport(0, 0, IRRADIANCE_SIZE, IRRADIANCE_SIZE);
211
212 m_irradianceShader->bind();
213
214 // Bind source cubemap via the conventional sampler name. The shader
215 // uses default texture-unit-0 sampler (no explicit binding qualifier),
216 // so we bind to unit 0 and set the sampler uniform once.
217 glActiveTexture(GL_TEXTURE0);
218 glBindTexture(GL_TEXTURE_CUBE_MAP, sourceCubemap);
219 GLint envLoc = glGetUniformLocation(m_irradianceShader->GetProgram(), "environmentMap");
220 if (envLoc >= 0) glUniform1i(envLoc, 0);
221
222 glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_faceUBO);
223
224 glBindVertexArray(m_cubeVAO);
225 for (int face = 0; face < 6; ++face) {
226 Math::Matrix4 view = BuildCaptureView(face);
227 Math::Matrix4 vp = proj * view;
228 glBindBuffer(GL_UNIFORM_BUFFER, m_faceUBO);
229 glBufferSubData(GL_UNIFORM_BUFFER, 0, 64, &vp(0,0));
230
231 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
232 GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
233 m_irradianceCubemap, 0);
234 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
235 SLEAK_ERROR("OpenGLIBL: irradiance FBO incomplete on face {}", face);
236 return false;
237 }
238 glClearColor(0,0,0,1);
239 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
240 glDrawArrays(GL_TRIANGLES, 0, 36);
241 }
242 glBindVertexArray(0);
243 return true;
244}
245
246bool OpenGLIBL::BakePrefilter(GLuint sourceCubemap) {
248 1.5707963f /* 90° */, 1.0f, 0.1f, 10.0f);
249
250 m_prefilterShader->bind();
251
252 glActiveTexture(GL_TEXTURE0);
253 glBindTexture(GL_TEXTURE_CUBE_MAP, sourceCubemap);
254 GLint envLoc = glGetUniformLocation(m_prefilterShader->GetProgram(), "environmentMap");
255 if (envLoc >= 0) glUniform1i(envLoc, 0);
256
257 glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_prefilterUBO);
258
259 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
260 glBindVertexArray(m_cubeVAO);
261
262 struct PrefilterUBOData {
263 float vp[16];
264 float roughness;
265 float pad[3];
266 };
267
268 for (uint32_t mip = 0; mip < PREFILTER_MIPS; ++mip) {
269 uint32_t mipSize = PREFILTER_SIZE >> mip;
270 glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
271 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
272 mipSize, mipSize);
273 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
274 GL_RENDERBUFFER, m_rbo);
275 glViewport(0, 0, mipSize, mipSize);
276
277 float roughness = static_cast<float>(mip) /
278 static_cast<float>(PREFILTER_MIPS - 1);
279
280 for (int face = 0; face < 6; ++face) {
281 Math::Matrix4 view = BuildCaptureView(face);
282 Math::Matrix4 vp = proj * view;
283
284 PrefilterUBOData ubo{};
285 std::memcpy(ubo.vp, &vp(0,0), 64);
286 ubo.roughness = roughness;
287 glBindBuffer(GL_UNIFORM_BUFFER, m_prefilterUBO);
288 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ubo), &ubo);
289
290 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
291 GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
292 m_prefilterCubemap, mip);
293 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
294 SLEAK_ERROR("OpenGLIBL: prefilter FBO incomplete (mip {}, face {})",
295 mip, face);
296 return false;
297 }
298 glClearColor(0,0,0,1);
299 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
300 glDrawArrays(GL_TRIANGLES, 0, 36);
301 }
302 }
303 glBindVertexArray(0);
304 return true;
305}
306
307bool OpenGLIBL::BakeBRDFLUT() {
308 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
309 glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
310 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
312 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
313 GL_RENDERBUFFER, m_rbo);
314 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
315 GL_TEXTURE_2D, m_brdfLUT, 0);
316 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
317 SLEAK_ERROR("OpenGLIBL: BRDF LUT FBO incomplete");
318 return false;
319 }
320
321 glViewport(0, 0, BRDF_LUT_SIZE, BRDF_LUT_SIZE);
322 glClearColor(0,0,0,1);
323 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
324
325 m_brdfShader->bind();
326 glBindVertexArray(m_quadVAO);
327 glDrawArrays(GL_TRIANGLES, 0, 3);
328 glBindVertexArray(0);
329 return true;
330}
331
332void OpenGLIBL::Bind() const {
333 if (!m_initialized) return;
334 glActiveTexture(GL_TEXTURE0 + UNIT_IRRADIANCE);
335 glBindTexture(GL_TEXTURE_CUBE_MAP, m_irradianceCubemap);
336 glActiveTexture(GL_TEXTURE0 + UNIT_PREFILTER);
337 glBindTexture(GL_TEXTURE_CUBE_MAP, m_prefilterCubemap);
338 glActiveTexture(GL_TEXTURE0 + UNIT_BRDF_LUT);
339 glBindTexture(GL_TEXTURE_2D, m_brdfLUT);
340 glActiveTexture(GL_TEXTURE0);
341}
342
343} // namespace RenderEngine
344} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
static Matrix< float, 4, 4 > Perspective(float fovY, float aspectRatio, float nearPlane, float farPlane)
Definition Matrix.hpp:211
static Matrix< float, 4, 4 > LookAt(const Vector< float, 3 > &eye, const Vector< float, 3 > &center, const Vector< float, 3 > &up)
Definition Matrix.hpp:250
static constexpr uint32_t UNIT_BRDF_LUT
Definition OpenGLIBL.hpp:31
static constexpr uint32_t PREFILTER_SIZE
Definition OpenGLIBL.hpp:24
static constexpr uint32_t BRDF_LUT_SIZE
Definition OpenGLIBL.hpp:26
static constexpr uint32_t UNIT_PREFILTER
Definition OpenGLIBL.hpp:30
static constexpr uint32_t UNIT_IRRADIANCE
Definition OpenGLIBL.hpp:29
static constexpr uint32_t PREFILTER_MIPS
Definition OpenGLIBL.hpp:25
bool Initialize(GLuint sourceCubemap)
Bakes the irradiance map, prefiltered specular map, and BRDF LUT from a source cubemap.
Definition OpenGLIBL.cpp:95
static constexpr uint32_t IRRADIANCE_SIZE
Definition OpenGLIBL.hpp:23
GLSL shader program compiled from either a combined source file or separate vertex/fragment files.
void bind() override
Binds this program as the active shader for subsequent draws.
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10