Я пытаюсь заставить скайбокс работать с OpenGL 3.3 и GLSL версии 330.
Я не смог найти нигде в Интернете полностью современного учебника по скайбоксу OGL, поэтому модернизировал более старый (используя glVertexAttribPointer()
вместо gl_Vertex
вершин и т. Д.). В основном это работает, но для 2 основных деталей:
Скайбоксы больше похожи на небесные треугольники, а текстуры плохо деформированы и растянуты (предполагается, что это звездные поля, а я получаю линии на черном фоне). Я на 99% уверен, что это потому, что я не правильно портировал старые уроки.
Вот мой класс скайбокса:
static ShaderProgram* cubeMapShader = nullptr;
static const GLfloat vertices[] =
{
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f
};
Skybox::Skybox(const char* xp, const char* xn, const char* yp, const char* yn, const char* zp, const char* zn)
{
if (cubeMapShader == nullptr)
cubeMapShader = new ShaderProgram("cubemap.vert", "cubemap.frag");
texture = SOIL_load_OGL_cubemap(xp, xn, yp, yn, zp, zn, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
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);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
glGenVertexArrays(1, &vaoID);
glBindVertexArray(vaoID);
glGenBuffers(1, &vboID);
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glBindVertexArray(0);
scale = 1.0f;
}
Skybox::~Skybox()
{
}
void Skybox::Render()
{
ShaderProgram::SetActive(cubeMapShader);
glDisable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
cubeMapShader->Uniform1i("SkyTexture", 0);
cubeMapShader->UniformVec3("CameraPosition", Camera::ActiveCameraPosition());
cubeMapShader->UniformMat4("MVP", 1, GL_FALSE, Camera::GetActiveCamera()->GetProjectionMatrix() * Camera::GetActiveCamera()->GetViewMatrix() * glm::mat4(1.0));
glBindVertexArray(vaoID);
glDrawArrays(GL_QUADS, 0, 24);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}
Вершинный шейдер:
#version 330
layout(location = 0) in vec3 Vertex;
uniform vec3 CameraPosition;
uniform mat4 MVP;
out vec3 Position;
void main()
{
Position = Vertex.xyz;
gl_Position = MVP * vec4(Vertex.xyz + CameraPosition, 1.0);
}
Фрагмент шейдера:
#version 330 compatibility
uniform samplerCube SkyTexture;
in vec3 Position;
void main()
{
gl_FragColor = textureCube(SkyTexture, Position);
}
Вот пример глюков. Если бы кто-нибудь мог посмотреть, кто хорошо знает GLSL (я все еще учу его) или скайбоксы, я был бы признателен за любую помощь, которую вы могли бы оказать. Также, спасибо, если вы можете научить меня, как использовать устаревшие функции в фрагментном шейдере, чтобы мне не пришлось использовать профиль совместимости glsl 330.
РЕДАКТИРОВАТЬ: Сразу обнаружил проблему с растягиванием текстур: я использовал Position = Vertex.xy
x
вместо этого Position = Vertex.xy
z
в вершинном шейдере. К сожалению. Но ошибка треугольника все еще существует.
Ответы:
Хотя этот ответ не говорит о том, что не так с вашим подходом, он представляет более простой способ визуализации скайбоксов.
Традиционный способ (текстурированный куб)
Простой способ создания скайбоксов - визуализация текстурированного куба с центром в положении камеры. Каждая грань куба состоит из двух треугольников и двумерной текстуры (или части атласа). Из-за координат текстуры каждая грань требует собственных вершин. Этот подход имеет проблемы в швах смежных граней, где значения текстуры не интерполируются должным образом.
Куб с текстурой куба
Как и традиционным способом, вокруг камеры отображается текстурированный куб. Вместо использования шести 2D текстур используется одна текстура кубической карты. Поскольку камера центрирована внутри куба, координаты вершины отображаются один на один с векторами выборки кубической карты. Таким образом, координаты текстуры не нужны для данных сетки, и вершины могут быть разделены между гранями с помощью индексного буфера.
Этот подход также устраняет проблему швов, когда включен GL_TEXTURE_CUBE_MAP_SEAMLESS.
Более простой (лучший) способ
При рендеринге куба, когда камера находится внутри него, весь видовой экран заполняется. До пяти граней скайбокса могут быть частично видны в любое время. Треугольники граней куба проецируются и обрезаются до области просмотра, а векторы выборки кубической карты интерполируются между вершинами. Эта работа не нужна.
Можно заполнить один квад, заполняя весь видовой экран, и рассчитать векторы выборки кубической карты в углах. Поскольку векторы выборки кубической карты соответствуют координатам вершин, их можно вычислить, не проецируя координаты области просмотра в мировое пространство. Это противоположно проекции мировых координат в окно просмотра и может быть достигнуто путем инвертирования матриц. Также убедитесь, что вы отключили запись в z-буфер или записали значение, которое достаточно далеко.
Ниже приведен вершинный шейдер, который выполняет это:
aPosition
координаты вершины{-1,-1; 1,-1; 1,1; -1,1}
. Шейдер рассчитываетeyeDirection
с обратной матрицей модель-вид-проекция. Однако инверсия делится на проекционные матрицы и матрицы мира с камерой. Это связано с тем, что для устранения положения камеры должна использоваться только часть матрицы камеры 3х3. Это выравнивает камеру по центру скайбокса. Кроме того, поскольку моя камера не имеет масштабирования или сдвига, инверсию можно упростить до транспонирования. Инверсия матрицы проекции является дорогостоящей операцией и может быть рассчитана заранее, но, поскольку этот код выполняется вершинным шейдером, как правило, всего четыре раза за кадр, это обычно не проблема.Фрагментный шейдер просто выполняет поиск текстуры, используя
eyeDirection
вектор:Обратите внимание, что для того, чтобы избавиться от режима совместимости, вам нужно заменить его
textureCube
простоtexture
и указать выходную переменную самостоятельно.источник
inverse()