Implementazione di uno skybox con GLSL versione 330


14

Sto cercando di far funzionare uno skybox con OpenGL 3.3 e GLSL versione 330.

Non sono riuscito a trovare un tutorial OGL skybox completamente moderno in qualsiasi parte del Web, quindi ne ho modernizzato uno più vecchio (usando glVertexAttribPointer()invece di gl_Vertexvertici, ecc.). Funziona principalmente, ma per 2 dettagli importanti:

Gli skybox sono più simili a triangoli di cielo, e le trame sono gravemente deformate e allungate (dovrebbero essere campi stellari, ottengo mentre le linee su uno sfondo nero). Sono sicuro al 99% che ciò è dovuto al fatto che non ho eseguito correttamente il porting dei vecchi tutorial.

Ecco la mia classe 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);
}

Vertex Shader:

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

Frammento Shader:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

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

Ecco un esempio dei glitch. Se qualcuno potesse dare un'occhiata a chi conosce bene GLSL (lo sto ancora imparando), o skybox, apprezzerei qualsiasi aiuto tu possa dare. Inoltre, complimenti, se puoi insegnarmi come utilizzare le funzioni non deprecate nello shader di frammenti, quindi non devo usare il profilo di compatibilità di glsl 330.


EDIT: ho immediatamente riscontrato il problema con le trame di stiramento: stavo usando al Position = Vertex.xyxposto del Position = Vertex.xyzvertex shader. Ops. Ma l'errore triangolare esiste ancora.


1
Sono necessari solo 4 vertici (quad a schermo intero) per eseguire il rendering di uno skybox con una trama cubemap. Hai solo bisogno di uno shader di vertice che calcoli le coordinate di trama corrette in base alla telecamera e alla proiezione.
msell

Potrebbe essere un problema di abbattimento. Hai provato a disabilitare l'abbattimento del backface per provare a vedere se hai la scatola piena?
pwny

@pwny, non ci ho pensato. L'ho provato e non ha funzionato, ma posso vedere come avrebbe potuto buttarlo via. Grazie per il suggerimento
sm81095

@msell, ho sentito parlare di questo approccio, ma non ho trovato un tutorial online per questo, e sto ancora imparando glsl. Se potessi fornire un esempio o un link a un esempio su come eseguire questa operazione, lo apprezzerei molto.
sm81095

Risposte:


29

Sebbene questa risposta non dica cosa c'è di sbagliato nel tuo approccio, presenta un modo più semplice per eseguire il rendering di skybox.

Modo tradizionale (cubo strutturato)

Un modo semplice per creare skybox è quello di rendere un cubo strutturato centrato sulla posizione della telecamera. Ogni faccia del cubo è composta da due triangoli e una trama 2D (o parte di un atlante). A causa delle coordinate della trama ogni faccia richiede i propri vertici. Questo approccio presenta problemi nelle giunture di facce adiacenti, in cui i valori di trama non sono interpolati correttamente.

Cubo con texture cubemap

Come nel modo tradizionale, un cubo strutturato viene reso attorno alla telecamera. Invece di usare sei trame 2D, viene utilizzata una singola trama cubemap. Poiché la telecamera è centrata all'interno del cubo, le coordinate del vertice vengono mappate una a una con i vettori di campionamento della mappa cubica. Pertanto le coordinate di trama non sono necessarie per i dati mesh e i vertici possono essere condivisi tra le facce usando il buffer dell'indice.

Questo approccio risolve anche il problema delle cuciture quando GL_TEXTURE_CUBE_MAP_SEAMLESS è abilitato.

Modo più semplice (migliore)

Quando si esegue il rendering di un cubo e la telecamera si trova al suo interno, l'intera finestra viene riempita. Fino a cinque facce dello skybox possono essere parzialmente visibili in qualsiasi momento. I triangoli delle facce del cubo vengono proiettati e ritagliati nella finestra e i vettori di campionamento della mappa cubica vengono interpolati tra i vertici. Questo lavoro non è necessario.

È possibile riempire un singolo quadratino riempiendo l'intero viewport e calcolare i vettori di campionamento cubemap negli angoli. Poiché i vettori di campionamento della mappa cubica corrispondono alle coordinate del vertice, possono essere calcolati non proiettando le coordinate del viewport nello spazio mondiale. Questo è l'opposto della proiezione delle coordinate del mondo sul viewport e può essere ottenuto invertendo le matrici. Assicurati anche di disabilitare la scrittura z-buffer o di scrivere un valore abbastanza lontano.

Di seguito è riportato lo shader di vertice che compie questo:

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

aPositionsono le coordinate del vertice {-1,-1; 1,-1; 1,1; -1,1}. Lo shader calcola eyeDirectioncon l'inverso della matrice di proiezione vista modello. Tuttavia, l'inversione è suddivisa per le matrici di proiezione e world-to-camera. Questo perché solo la parte 3x3 della matrice della telecamera dovrebbe essere utilizzata per eliminare la posizione della telecamera. Questo allinea la fotocamera al centro dello skybox. Inoltre, poiché la mia fotocamera non ha alcun ridimensionamento o taglio, è possibile semplificare l'inversione della trasposizione. L'inversione della matrice di proiezione è un'operazione costosa e potrebbe essere precalcolata, ma poiché questo codice viene eseguito dallo shader di vertice in genere solo quattro volte per frame, di solito non è un problema.

Lo shader di frammenti esegue semplicemente una ricerca di texture usando il eyeDirectionvettore:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

Nota che per sbarazzarti della modalità di compatibilità devi sostituire textureCubecon just texturee specificare tu stesso la variabile di output.


Penso che dovresti anche menzionare che l'inversione della matrice è un processo costoso, quindi ha luogo meglio nel codice lato client.
Akaltar

1
Per i 4 verts di un quad a schermo intero, non credo che dovremmo preoccuparci molto del costo dell'inversione (specialmente perché la GPU che lo fa 4 volte sarà probabilmente più veloce della CPU che lo fa una volta).
Maximus Minimus,

1
Solo una nota utile per la gente, GLSL ES 1.0 (usato per GL ES 2.0) non implementainverse()
Steven Lu

uWorldToCameraMatrix è l'MVP dell'oggetto di trasformazione della telecamera?
Sidar,

@Sidar No, è solo la matrice ModelView, la proiezione è separata.
msell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.