Wdrożenie skybox z wersją GLSL 330

14

Usiłuję uzyskać skybox współpracujący z OpenGL 3.3 i GLSL w wersji 330.

Nie mogłem znaleźć całkowicie nowoczesnego samouczka OGL skybox w dowolnym miejscu w sieci, więc zmodernizowałem starszy (używając glVertexAttribPointer()zamiast gl_Vertexwierzchołków itp.). Działa głównie, ale dla dwóch głównych szczegółów:

Skyboksy bardziej przypominają trójkąty nieba, a tekstury są mocno wypaczone i rozciągnięte (powinny to być pola gwiezdne, dostaję linie na czarnym tle). Jestem w 99% pewien, że dzieje się tak, ponieważ stare portale nie zostały całkowicie poprawnie przeniesione.

Oto moja klasa 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);
}

Shader Vertex:

#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);
}

Shader fragmentów:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

void main()
{
    gl_FragColor = textureCube(SkyTexture, Position);
}

Oto przykład usterki. Jeśli ktoś mógłby rzucić okiem na kogoś, kto dobrze zna GLSL (wciąż się go uczę) lub skyboksów, byłbym wdzięczny za wszelką pomoc, którą możesz udzielić. Ponadto, jeśli możesz nauczyć mnie, jak używać nieaktualnych funkcji w module cieniującym fragmenty, więc nie muszę używać profilu zgodności glsl 330.


EDYCJA: Natychmiast znalazłem problem z rozciągającymi się teksturami: używałem Position = Vertex.xyxzamiast Position = Vertex.xyzw module cieniującym wierzchołki. Ups Ale błąd trójkąta nadal istnieje.

sm81095
źródło
1
Potrzebujesz tylko 4 wierzchołków (quad pełnoekranowy), aby wyrenderować skybox z teksturą mapy kubatury. Potrzebujesz tylko modułu cieniującego wierzchołek, który oblicza prawidłowe współrzędne tekstury na podstawie kamery i projekcji.
sprzedać
Może to być problem z ubijaniem. Czy próbowałeś wyłączyć culling backface, aby sprawdzić, czy masz pełne pole?
pwny,
@pwny, nie myślałem o tym. Próbowałem i nie działało, ale widzę, jak to mogło go wyrzucić. Dzieki za sugestie.
sm81095,
@msell, słyszałem o tym podejściu, ale nie znalazłem samouczka online do tego i wciąż jestem w trakcie nauki glsl. Jeśli podasz przykład lub link do przykładu, jak to zrobić, byłbym bardzo wdzięczny.
sm81095,

Odpowiedzi:

29

Chociaż ta odpowiedź nie mówi, co jest złego w twoim podejściu, przedstawia prostszy sposób renderowania skyboxów.

Tradycyjny sposób (sześcian teksturowany)

Prostym sposobem tworzenia skyboksów jest renderowanie teksturowanego sześcianu na środku kamery. Każda powierzchnia sześcianu składa się z dwóch trójkątów i tekstury 2D (lub części atlasu). Ze względu na współrzędne tekstury każda twarz wymaga własnych wierzchołków. Takie podejście ma problemy w szwach sąsiadujących ścian, gdzie wartości tekstury nie są poprawnie interpolowane.

Kostka o fakturze cubemap

Podobnie jak w tradycyjny sposób, wokół kamery renderowany jest teksturowany sześcian. Zamiast korzystać z sześciu tekstur 2D, używana jest pojedyncza tekstura mapy sześciennej. Ponieważ kamera jest wyśrodkowana wewnątrz sześcianu, współrzędne wierzchołków są odwzorowywane jeden na jeden za pomocą wektorów próbkowania mapy kostek. Dlatego współrzędne tekstury nie są potrzebne dla danych siatki, a wierzchołki mogą być dzielone między ścianami za pomocą bufora indeksu.

To podejście rozwiązuje również problem szwów, gdy GL_TEXTURE_CUBE_MAP_SEAMLESS jest włączony.

Prostszy (lepszy) sposób

Podczas renderowania sześcianu, w którym znajduje się kamera, cała rzutnia zostaje wypełniona. W dowolnym momencie można częściowo wyświetlić do pięciu twarzy skyboksa. Trójkąty ścian sześcianu są rzutowane i przycinane do rzutni, a wektory próbkowania mapy kubaturowej są interpolowane między wierzchołkami. Ta praca jest niepotrzebna.

Możliwe jest wypełnienie pojedynczego kwadratu wypełniającego całą rzutnię i obliczenie wektorów próbkowania mapy kostki w rogach. Ponieważ wektory próbkowania mapy sześciennej odpowiadają współrzędnym wierzchołka, można je obliczyć, odsuwając współrzędne rzutni do przestrzeni świata. Jest to przeciwieństwo rzutowania współrzędnych świata na rzutnię i można to osiągnąć poprzez odwrócenie matryc. Upewnij się także, że albo wyłączasz zapisywanie w buforze Z, albo zapisujesz wystarczająco dużą wartość.

Poniżej znajduje się moduł cieniujący wierzchołek, który to osiąga:

#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;
} 

aPositionto współrzędne wierzchołka {-1,-1; 1,-1; 1,1; -1,1}. Moduł cieniujący obliczaeyeDirection z odwrotnością macierzy widok-model-rzut. Inwersja jest jednak podzielona dla matryc projekcyjnych i świat-kamera. Wynika to z faktu, że do wyeliminowania pozycji kamery należy użyć tylko części matrycy kamery 3x3. Spowoduje to wyrównanie aparatu do środka skyboksa. Ponadto, ponieważ mój aparat nie ma żadnego skalowania ani ścinania, inwersję można uprościć do transpozycji. Odwrócenie macierzy projekcji jest kosztowną operacją i może być wstępnie obliczone, ale ponieważ ten kod jest wykonywany przez moduł cieniujący wierzchołki zwykle tylko cztery razy na ramkę, zwykle nie stanowi to problemu.

Moduł cieniujący fragment wykonuje po prostu wyszukiwanie tekstury za pomocą eyeDirectionwektora:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

Pamiętaj, że aby pozbyć się trybu zgodności, musisz zastąpić textureCubego parametrem just texturei samodzielnie określić zmienną wyjściową.

sprzedać
źródło
Myślę, że powinieneś również wspomnieć, że inwersja macierzy jest kosztownym procesem, więc lepiej ma miejsce w kodzie klienta.
akaltar
1
W przypadku 4 wierzchołków kwadratu pełnoekranowego nie sądzę, abyśmy musieli martwić się o koszt inwersji (zwłaszcza, że ​​GPU robienie tego 4 razy nadal będzie prawdopodobnie szybsze niż procesor robiąc to raz).
Maximus Minimus,
1
Po prostu pomocna uwaga dla ludzi, GLSL ES 1.0 (używany dla GL ES 2.0) nie implementujeinverse()
Steven Lu
czy uWorldToCameraMatrix jest MVP obiektu transformacji kamery?
Sidar
@ Sidar Nie, to tylko matryca ModelView, projekcja jest osobna.
sprzedać