Считайте каждую отдельную грань текстуры кубической карты в массив с плавающей запятой на ЦП, чтобы я мог сохранить каждую грань на диск в виде файла изображения.
glGetTexImage возвращает данные, не соответствующие тому, что я наблюдаю на стороне графического процессора (подробнее позже).
Контекст: Я реализую зонды отражения для своего игрового движка OpenGL.
Вот краткий псевдокод, показывающий порядок работы:
entityList = LoadScene(); // Load entities along with their geometry, textures, shaders and other non-graphics components
bakedData.InitialiseReflectionProbes(std::vector<> positions); // Tell the bakedData class where the reflection probes are located
BakeReflectionProbes() { // Run the reflection probe baking system (more details later)
// Render scene into cubemap from each probe position
// Process cubemap render
// Write textures to files
};
Теперь подробнее о самой проблеме.
Когда я вызываю glGetTexImage() с правильной привязкой текстуры, только первая грань кубической карты фактически возвращает правильные данные в массив с плавающей запятой, остальные грани — все 0 и полностью черные.
Это несовместимо с тем, что я вижу на стороне графического процессора. Я наблюдаю за кадром в RenderDoc при каждом вызове отрисовки. Я вижу каждый этап процесса запекания, он правильно захватывает сцену, и я прекрасно вижу каждую грань кубической карты. В вызовах API я вижу, что правильная текстура также привязана перед вызовом glGetTexImage.
Теперь я покажу, как я это делаю в коде, а затем покажу еще код, который приведет к этому моменту, например, как генерируются текстуры:
void BakedData::WriteReflectionProbesToFile()
{
unsigned int numProbes = reflectionProbes.size();
for (unsigned int i = 0; i < reflectionProbes.size(); i++) {
ReflectionProbe* probe = reflectionProbes[i];
ReflectionProbeEnvironmentMap envMap = probe->GetProbeEnvMap();
unsigned int probeID = probe->GetFileID();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
faceWidth = probe->GetFaceWidth();
faceHeight = probe->GetFaceHeight();
GLfloat* floatData = new GLfloat[faceWidth * faceHeight * 3];
glBindTexture(GL_TEXTURE_CUBE_MAP, envMap.cubemapID);
glFinish();
for (unsigned int j = 0; j < 6; j++) {
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGB, GL_FLOAT, floatData);
glFinish();
// Debugging: Print the first pixel of each face
std::cout << "Face " << j << " first pixel RGB: "
<< floatData[0] << ", "
<< floatData[1] << ", "
<< floatData[2] << std::endl;
}
delete[] floatData;
}
}
Приведенный выше код для каждого датчика отражения будет правильно считывать только грань кубической карты GL_TEXTURE_CUBE_MAP_POSITIVE_X
. Остальные все читаются как черные.
Ниже показано, как настраиваются текстуры отражающего зонда:
void ReflectionProbe::SetupTextureMaps()
{
// ------ Set up base cubemap ------
// ---------------------------------
glGenTextures(1, &envMap.cubemapID);
glBindTexture(GL_TEXTURE_CUBE_MAP, envMap.cubemapID);
for (unsigned int i = 0; i < 6; i++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, faceWidth, faceHeight, 0, GL_RGB, GL_FLOAT, nullptr);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
// ----- Set up irradiance map -----
// ---------------------------------
glGenTextures(1, &envMap.irradianceID);
glBindTexture(GL_TEXTURE_CUBE_MAP, envMap.irradianceID);
for (unsigned int i = 0; i < 6; i++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// ----- Set up pre-filter map -----
// ---------------------------------
glGenTextures(1, &envMap.prefilterID);
glBindTexture(GL_TEXTURE_CUBE_MAP, envMap.prefilterID);
for (unsigned int i = 0; i < 6; i++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128 * 2, 128 * 2, 0, GL_RGB, GL_FLOAT, nullptr);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
}
Интересно, что все текстуры кубической карты освещенности читаются совершенно нормально и выполняются точно так же, как текстуры кубической карты скайбокса:
void BakedData::WriteReflectionProbesToFile()
{
...
// - Write irradiance -
// ---------------------
std::cout << std::endl << "Writing irradiance map" << std::endl;
std::cout << "----------------------" << std::endl;
floatData = new GLfloat[32 * 32 * 3];
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, envMap.irradianceID);
glFinish();
for (unsigned int j = 0; j < 6; j++) {
//path = "Data/ReflectionProbe/" + probe->GetSceneName() + "/" + probeString + "/Irradiance/" += cubemapFaceToString[GL_TEXTURE_CUBE_MAP_POSITIVE_X + j] + ".hdr";
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGB, GL_FLOAT, floatData);
glFinish();
// Debugging: Print the first pixel of each face
std::cout << "Face " << j << " first pixel RGB: "
<< floatData[0] << ", "
<< floatData[1] << ", "
<< floatData[2] << std::endl;
//stbi_write_hdr(path.c_str(), 32, 32, 3, floatData);
}
delete[] floatData;
...
}
Я покажу немного больше деталей о системе запекания, о том, как датчики на самом деле захватывают сцену:
Я понимаю, что это много кода, я не ожидаю, что кто-нибудь сможет воспроизвести и исправить эту проблему напрямую, я просто хочу убедиться, что вы можете видеть полное время жизни текстур, то, как они визуализируются, генерируются и т. д. прочитать и т. д., чтобы иметь полную картину
for (int i = 0; i \< numProbes; i++) {
ReflectionProbe\* probe = probes\[i\];
std::cout \<\< " - Baking probe " \<\< i + 1 \<\< " / " \<\< numProbes \<\< std::endl;
// Capture scene
width = probe->GetFaceWidth();
height = probe->GetFaceHeight();
glViewport(0, 0, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, reflectionFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, probe->GetProbeEnvMap().cubemapID, 0);
// Resize depth texture buffer
glBindTexture(GL_TEXTURE_CUBE_MAP, depthBuffer);
for (unsigned int j = 0; j < 6; j++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
}
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthBuffer, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// error check
int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
std::cout << "ERROR::RENDERMANAGER::Cubemap FBO incomplete, status: 0x\%x\n" << status << std::endl;
}
reflectionShader->Use();
glm::vec3 position = probe->GetWorldPosition();
glm::mat4 projection = glm::perspective(glm::radians(90.0f), (float)width / (float)height, probe->GetNearClip(), probe->GetFarClip());
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
for (unsigned int i = 0; i < 6; ++i) {
reflectionShader->setMat4("shadowMatrices[" + std::to_string(i) + "]", cubemapTransforms[i]); // Ignore the "shadowMatrices", this is just copied from a shadowmapping shader
}
reflectionShader->setVec3("viewPos", position);
for (Entity* entity : entityList) {
OnAction(entity); // This function just sets up shader uniforms from the geometry component of the entity, binds vertex data and material textures and draws the object
}
cubemapTransforms.clear();
// Process capture
glDisable(GL_CULL_FACE);
// Convolute env map
glBindFramebuffer(GL_FRAMEBUFFER, cubeCaptureFBO);
glBindRenderbuffer(GL_RENDERBUFFER, cubeCaptureRBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);
// Set up projections for each cubemap face
glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f);
glm::mat4 captureViews[] = {
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)),
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)),
glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f))
};
// Set up irradiance shader uniforms and bind env cupemap
Shader* irradianceShader = resourceManager->CreateIrradianceShader();
irradianceShader->Use();
irradianceShader->setInt("environmentMap", 0);
irradianceShader->setMat4("projection", captureProjection);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap);
glViewport(0, 0, 32, 32);
glBindFramebuffer(GL_FRAMEBUFFER, cubeCaptureFBO);
for (unsigned int j = 0; j < 6; ++j)
{
irradianceShader->setMat4("view", captureViews[j]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, irradianceMap, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
resourceManager->DefaultCube().DrawWithNoMaterial();
}
// Prefilter
// Capture prefilter mipmap levels
// -------------------------------
Shader* prefilterShader = resourceManager->CreatePrefilterShader();
prefilterShader->Use();
prefilterShader->setInt("environmentMap", 0);
prefilterShader->setMat4("projection", captureProjection);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glBindFramebuffer(GL_FRAMEBUFFER, cubeCaptureFBO);
unsigned int maxMipLevels = 5;
for (unsigned int mip = 0; mip < maxMipLevels; mip++) {
// resize framebuffer
unsigned int mipWidth = (128 * 2) * std::pow(0.5, mip);
unsigned int mipHeight = (128 * 2) * std::pow(0.5, mip);
glBindRenderbuffer(GL_RENDERBUFFER, cubeCaptureRBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight);
glViewport(0, 0, mipWidth, mipHeight);
float roughness = (float)mip / (float)(maxMipLevels - 1);
prefilterShader->setFloat("roughness", roughness);
for (unsigned int j = 0; j < 6; j++) {
prefilterShader->setMat4("view", captureViews[j]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, prefilterMap, mip);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
resourceManager->DefaultCube().DrawWithNoMaterial();
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_CULL_FACE);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, renderManager->ScreenWidth(), renderManager->ScreenHeight());
// Write to file
// -------------
renderManager->GetBakedData().WriteReflectionProbesToFile();
Ниже приведены снимки экрана из RenderDoc, они взяты из вызова отрисовки на этапе захвата сцены в системе запекания зонда отражения, которая визуализирует текстуру кубической карты, из которой я пытаюсь прочитать. Как вы можете видеть, лицо положительного и отрицательногоX отображается правильно. Я также могу подтвердить, что другие лица также отображаются правильно:
Положительный X Скриншот RenderDoc, показывающий визуализированную текстуру текстуры с положительным X-гранью CubemapID
Отрицательный X Скриншот RenderDoc, показывающий отрендеренную текстуру текстуры CubemapID с отрицательным лицом X
Но при чтении их обратно в процессор только положительная сторона X дает мне правильные значения.
Я также хочу уточнить, что я пробовал вызывать glGetError до и после чтения текстуры в ЦП, никаких ошибок не возвращалось.
Я перепробовал все, что мог придумать:
Обновлено:
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
, без других вызовов glGetTexImage(), та же проблема.Решено:
Проблема возникла из-за того, как я изначально визуализировал текстуру кубической карты.
reflectionShader->Use();
glm::vec3 position = probe->GetWorldPosition();
glm::mat4 projection = glm::perspective(glm::radians(90.0f), (float)width / (float)height, probe->GetNearClip(), probe->GetFarClip());
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
cubemapTransforms.push_back(projection * glm::lookAt(position, position + glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)));
for (unsigned int i = 0; i < 6; ++i) {
reflectionShader->setMat4("shadowMatrices[" + std::to_string(i) + "]", cubemapTransforms[i]); // Ignore the "shadowMatrices", this is just copied from a shadowmapping shader
}
reflectionShader->setVec3("viewPos", position);
for (Entity* entity : entityList) {
OnAction(entity); // This function just sets up shader uniforms from the geometry component of the entity, binds vertex data and material textures and draws the object
}
cubemapTransforms.clear();
Вышеупомянутый метод, используемый в исходном коде, работает за счет использования одного вызова отрисовки для каждой сетки для одновременного рендеринга всех 6 граней кубической карты. Это делается в геометрическом шейдере. По какой-то причине это вызывает проблемы, когда вы хотите прочитать текстуру обратно в процессор.
Несмотря на то, что текстура явно правильно заполнена в памяти графического процессора, каждое лицо можно правильно использовать в сэмплерах шейдеров, и все лица четко просматриваются в таком программном обеспечении, как RenderDoc, оно не считывается как таковое, поскольку только одно лицо явно визуализируется в когда он изначально был привязан к фреймбуферу, а остальные грани создавались в геометрическом шейдере.
Решение:
Явно визуализируйте каждую грань кубической карты с помощью нового вызова отрисовки для каждой грани и сетки:
glViewport(0, 0, width, height);
for (unsigned int j = 0; j < 6; ++j)
{
reflectionShaderTest->setMat4("view", captureViews[j]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, probe->GetProbeEnvMap().cubemapID, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, depthBuffer, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for (Entity* entity : entityList) {
OnAction(entity);
}
}
Таким образом, каждая грань кубической карты теперь правильно визуализируется напрямую.
OpenGL определенно не так должен вести себя в этой ситуации. Я думаю, что это вызвано ошибкой драйвера. Мне было бы очень интересно услышать, является ли первоначальная проблема даже при выполнении на графическом процессоре другого производителя.
Первоначально это было сделано на Nvidia 3080ti, попробовал еще раз на своем ноутбуке с картой AMD, проблема не исчезла. Это очень странная проблема, я действительно думал, что схожу с ума @BDL
@BDL да, я пробовал это. Сейчас добавлю это в пост. Но да, та же проблема и при прямом нацеливании на NEGATIVE_X. То же самое с любыми другими гранями, кроме положительного x.