Впровадження Skybox з GLSL версії 330


14

Я намагаюся отримати Skybox, що працює з OpenGL 3.3 та GLSL версії 330.

Я не міг знайти повністю сучасного навчального посібника з програми OGL skybox ніде в Інтернеті, тому я модернізував старіший (використовуючи glVertexAttribPointer()замість gl_Vertexвершин і т.д.). Це в основному працює, але для двох основних деталей:

Хмарочоси більше схожі на небесні трикутники, а текстури погано вигнуті і розтягнуті (вони повинні бути зоряними полями, я отримую, поки лінії на чорному тлі). Я на 99% впевнений, що це тому, що я не переклав старі підручники повністю правильно.

Ось мій клас skybox:

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 (я досі його вивчаю), або skyboxes, я буду вдячний за будь-яку допомогу, яку ви могли б надати. Крім того, кудо, якщо ви можете навчити мене використовувати непридатні функції в фрагменті шейдера, щоб мені не довелося використовувати профіль сумісності glsl 330.


EDIT: Відразу знайшли проблему з текстурами розтягування: я використовував Position = Vertex.xyxзамість Position = Vertex.xyzшейдера вершин. На жаль Але помилка трикутника все ж існує.


1
Вам потрібно лише 4 вершини (повноекранний квадратик), щоб візуалізація зобразила небо з текстурою кубічної карти. Вам просто потрібен вершинний шейдер, який обчислює правильні координати текстури на основі камери та проекції.
msell

Це може бути проблемою відбракування. Ви намагалися відключити вилучення задньої поверхні, щоб перевірити, чи отримаєте ви повне поле?
pwny

@pwny, я про це не думав. Я спробував це, і це не вийшло, але я бачу, як це могло це відкинути. Дякую за пропозицію.
sm81095

@msell, я чув про такий підхід, але не знайшов підручника для цього в Інтернеті, і я все ще в процесі вивчення glsl. Якщо ви можете надати приклад або посилання на приклад про те, як це зробити, я дуже вдячний за це.
sm81095

Відповіді:


29

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

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

Безпосереднім способом створення небесних скриньок є виведення текстурованого куба в центрі положення камери. Кожна грань куба складається з двох трикутників та 2D текстури (або частини атласу). Завдяки текстурним координатам кожне обличчя вимагає власних вершин. Цей підхід має проблеми в швах суміжних граней, де значення текстури не інтерполюються належним чином.

Куб з текстурою кубемапи

Як і традиційним способом, навколо камери рендерується текстурований куб. Замість використання шести 2D текстур використовується одна текстура кубічної карти. Оскільки камера орієнтована всередині куба, вершина координує карту одна на одну з векторами вибірки кубічної карти. Таким чином, координати текстур не потрібні для даних сітки, і вершини можна розділити між гранями, використовуючи індексний буфер.

Цей підхід також вирішує проблему швів, коли GL_TEXTURE_CUBE_MAP_SEAMLESS увімкнено.

Простіший (кращий) спосіб

Коли візуалізується куб і камера лежить всередині нього, весь огляд огляду заповнюється. У будь-який час частково можна побачити до п'яти облич skybox. Трикутники граней куба проектуються і затискаються до вікна перегляду, а між вершинами інтерполюються вектори вибірки кубічної карти. Ця робота непотрібна.

Можна заповнити один квадратик, заповнивши весь вікно перегляду, і обчислити вектори вибірки кубічної карти в кутах. Оскільки вектори вибірки кубічної мапи відповідають координатам вершин, їх можна обчислити, відкинувши координати вікна перегляду у світовий простір. Це протилежне проектуванню світових координат на область перегляду і може бути досягнуто шляхом інвертування матриць. Також переконайтесь, що ви або відключили 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за допомогою інверсії матриці перегляду-проектування-проектування. Однак інверсія розбита на проекційні та матриці світового на камеру. Це тому, що для усунення положення камери слід використовувати лише 3x3 частину матриці камери. Це вирівнює камеру до центру skybox. Окрім того, оскільки моя камера не має жодного масштабування чи зсуву, інверсія може бути спрощена до переміщення. Інверсія матриці проекції є дорогою операцією і може бути попередньо розрахована, але оскільки цей код виконується вершинним шейдером, як правило, всього чотири рази на кадр, зазвичай це не проблема.

Шейдер фрагмента просто виконує пошук текстури, використовуючи eyeDirectionвектор:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

Зауважте, що для позбавлення від режиму сумісності вам потрібно замінити textureCubeна just textureі вказати вихідну змінну самостійно.


Я думаю, ви також повинні зазначити, що інверсія матриці є дорогим процесом, тому вона краще відбувається в коді на стороні клієнта.
акалтар

1
Щодо 4-х версій повноекранного квадратику, я не думаю, що нам потрібно сильно турбуватися про витрати на інверсію (тим більше, що GPU, що робить це в 4 рази, все одно, швидше, буде швидше, ніж ЦП це робить один раз).
Максим Мінімус

1
Лише корисна примітка для людей, GLSL ES 1.0 (використовується для GL ES 2.0) не реалізуєinverse()
Стівен Лу

є uWorldToCameraMatrix MVP об'єкта перетворення камери?
Сидар

@Sidar Ні, це просто матриця ModelView, Проекція окрема.
msell
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.