Реализация скайбокса с GLSL версии 330

14

Я пытаюсь заставить скайбокс работать с 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.xyxвместо этого Position = Vertex.xyzв вершинном шейдере. К сожалению. Но ошибка треугольника все еще существует.

sm81095
источник
1
Вам нужно только 4 вершины (полноэкранный четырехугольник) для рендеринга скайбокса с текстурой кубической карты. Вам просто нужен вершинный шейдер, который вычисляет правильные координаты текстуры на основе камеры и проекции.
msell
Это может быть проблемой выбраковки. Вы пытались отключить выборку задней поверхности, чтобы проверить, все ли у вас получилось?
pwny
@pwny, я не думал об этом. Я попробовал, и это не сработало, но я вижу, как это могло скинуть. Спасибо за предложение.
sm81095
@ msell, я слышал об этом подходе, но я не нашел учебника в Интернете для этого, и я все еще в процессе изучения glsl. Если бы вы могли предоставить пример или ссылку на пример о том, как это сделать, я был бы очень признателен за это.
sm81095

Ответы:

29

Хотя этот ответ не говорит о том, что не так с вашим подходом, он представляет более простой способ визуализации скайбоксов.

Традиционный способ (текстурированный куб)

Простой способ создания скайбоксов - визуализация текстурированного куба с центром в положении камеры. Каждая грань куба состоит из двух треугольников и двумерной текстуры (или части атласа). Из-за координат текстуры каждая грань требует собственных вершин. Этот подход имеет проблемы в швах смежных граней, где значения текстуры не интерполируются должным образом.

Куб с текстурой куба

Как и традиционным способом, вокруг камеры отображается текстурированный куб. Вместо использования шести 2D текстур используется одна текстура кубической карты. Поскольку камера центрирована внутри куба, координаты вершины отображаются один на один с векторами выборки кубической карты. Таким образом, координаты текстуры не нужны для данных сетки, и вершины могут быть разделены между гранями с помощью индексного буфера.

Этот подход также устраняет проблему швов, когда включен GL_TEXTURE_CUBE_MAP_SEAMLESS.

Более простой (лучший) способ

При рендеринге куба, когда камера находится внутри него, весь видовой экран заполняется. До пяти граней скайбокса могут быть частично видны в любое время. Треугольники граней куба проецируются и обрезаются до области просмотра, а векторы выборки кубической карты интерполируются между вершинами. Эта работа не нужна.

Можно заполнить один квад, заполняя весь видовой экран, и рассчитать векторы выборки кубической карты в углах. Поскольку векторы выборки кубической карты соответствуют координатам вершин, их можно вычислить, не проецируя координаты области просмотра в мировое пространство. Это противоположно проекции мировых координат в окно просмотра и может быть достигнуто путем инвертирования матриц. Также убедитесь, что вы отключили запись в z-буфер или записали значение, которое достаточно далеко.

Ниже приведен вершинный шейдер, который выполняет это:

#version 330
uniform mat4 uProjectionMatrix;
uniform mat4 uWorldToCameraMatrix;

in vec4 aPosition;

smooth out vec3 eyeDirection;

void main() {
    mat4 inverseProjection = inverse(uProjectionMatrix);
    mat3 inverseModelview = transpose(mat3(uWorldToCameraMatrix));
    vec3 unprojected = (inverseProjection * aPosition).xyz;
    eyeDirection = inverseModelview * unprojected;

    gl_Position = aPosition;
} 

aPositionкоординаты вершины {-1,-1; 1,-1; 1,1; -1,1}. Шейдер рассчитывает eyeDirectionс обратной матрицей модель-вид-проекция. Однако инверсия делится на проекционные матрицы и матрицы мира с камерой. Это связано с тем, что для устранения положения камеры должна использоваться только часть матрицы камеры 3х3. Это выравнивает камеру по центру скайбокса. Кроме того, поскольку моя камера не имеет масштабирования или сдвига, инверсию можно упростить до транспонирования. Инверсия матрицы проекции является дорогостоящей операцией и может быть рассчитана заранее, но, поскольку этот код выполняется вершинным шейдером, как правило, всего четыре раза за кадр, это обычно не проблема.

Фрагментный шейдер просто выполняет поиск текстуры, используя eyeDirectionвектор:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

void main() {
    fragmentColor = texture(uTexture, eyeDirection);
}

Обратите внимание, что для того, чтобы избавиться от режима совместимости, вам нужно заменить его textureCubeпросто textureи указать выходную переменную самостоятельно.

msell
источник
Я думаю, что вы также должны упомянуть, что инверсия матриц является дорогостоящим процессом, поэтому лучше это происходит в коде на стороне клиента.
Акалтар
1
Для 4 вершин полноэкранного квадроцикла я не думаю, что нам нужно сильно беспокоиться о стоимости инверсии (особенно если учесть, что GPU, делающий это 4 раза, все равно будет быстрее, чем процессор, делающий это один раз).
Максимус Минимус
1
Просто полезное замечание для людей, GLSL ES 1.0 (используется для GL ES 2.0) не реализуетinverse()
Стивен Лу
является uWorldToCameraMatrix MVP объекта преобразования камеры?
Сидар
@ Сидар Нет, это просто матрица ModelView, проекция - это отдельное.
msell