Menerapkan skybox dengan GLSL versi 330

14

Saya mencoba membuat skybox bekerja dengan OpenGL 3.3 dan GLSL versi 330.

Saya tidak dapat menemukan tutorial OGL skybox yang sepenuhnya modern di mana pun di web, jadi saya memodernisasi tutorial yang lebih lama (menggunakan glVertexAttribPointer()alih-alih gl_Vertexuntuk simpul, dll.). Sebagian besar berfungsi, tetapi untuk 2 detail utama:

Skybox lebih seperti segitiga langit, dan teksturnya sangat melengkung dan membentang (mereka seharusnya menjadi bidang bintang, saya dapatkan sementara garis-garis pada latar belakang hitam). Saya 99% yakin bahwa ini karena saya tidak mem-port-kan tutorial lama dengan benar.

Ini kelas skybox saya:

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

Shader fragmen:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

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

Ini adalah contoh dari gangguan. Jika ada yang bisa melihat siapa yang mengenal GLSL dengan baik (saya masih mempelajarinya), atau skybox, saya sangat menghargai bantuan yang bisa Anda berikan. Juga, pujian jika Anda bisa mengajari saya cara menggunakan fungsi yang tidak usang dalam shader fragmen jadi saya tidak harus menggunakan profil kompatibilitas glsl 330.


EDIT: Segera menemukan masalah dengan tekstur peregangan: Saya menggunakan Position = Vertex.xyxbukan Position = Vertex.xyzdi vertex shader. Ups. Namun kesalahan segitiga masih ada.

sm81095
sumber
1
Anda hanya perlu 4 simpul (layar penuh quad) untuk membuat skybox dengan tekstur cubemap. Anda hanya perlu vertex shader yang menghitung koordinat tekstur yang benar berdasarkan kamera dan proyeksi.
msell
Mungkin masalah pemusnahan. Sudahkah Anda mencoba menonaktifkan backface culling untuk mencoba dan melihat apakah Anda mendapatkan kotak lengkap?
pwny
@pwny, saya tidak memikirkan itu. Saya mencobanya, dan itu tidak berhasil, tetapi saya bisa melihat bagaimana itu bisa membuatnya terlempar. Terima kasih untuk sarannya.
sm81095
@msell, saya telah mendengar pendekatan ini, tetapi saya tidak menemukan tutorial online untuk ini, dan saya masih dalam proses belajar glsl. Jika Anda bisa memberikan contoh atau tautan ke contoh tentang cara melakukan ini, saya akan sangat menghargai itu.
sm81095

Jawaban:

29

Meskipun jawaban ini tidak memberi tahu apa yang salah dengan pendekatan Anda, jawaban ini menyajikan cara yang lebih sederhana untuk membuat skybox.

Cara tradisional (kubus bertekstur)

Cara mudah untuk membuat skybox adalah dengan membuat kubus bertekstur yang berpusat ke posisi kamera. Setiap permukaan kubus terdiri dari dua segitiga dan tekstur 2D (atau bagian dari atlas). Karena koordinat tekstur setiap wajah memerlukan simpul sendiri. Pendekatan ini memiliki masalah pada lapisan wajah yang berdekatan, di mana nilai tekstur tidak diinterpolasi dengan benar.

Kubus dengan tekstur cubemap

Seperti cara tradisional, kubus bertekstur ditampilkan di sekitar kamera. Alih-alih menggunakan enam tekstur 2D, tekstur cubemap tunggal digunakan. Karena kamera dipusatkan di dalam kubus, titik koordinat koordinat satu ke satu dengan vektor pengambilan sampel cubemap. Jadi koordinat tekstur tidak diperlukan untuk data mesh dan simpul dapat dibagi antara wajah dengan menggunakan buffer indeks.

Pendekatan ini juga memperbaiki masalah jahitan ketika GL_TEXTURE_CUBE_MAP_SEAMLESS diaktifkan.

Cara yang lebih sederhana (lebih baik)

Saat merender kubus dan kamera ada di dalamnya, seluruh viewport terisi. Hingga lima wajah skybox dapat terlihat sebagian kapan saja. Segitiga dari permukaan kubus diproyeksikan dan dipotong ke viewport dan vektor sampel kubemap diinterpolasi di antara simpul. Pekerjaan ini tidak perlu.

Dimungkinkan untuk mengisi satu quad yang mengisi seluruh viewport dan menghitung vektor pengambilan sampel cubemap di sudut-sudut. Karena vektor pengambilan sampel cubemap cocok dengan koordinat titik, mereka dapat dihitung dengan tidak memproyeksikan koordinat viewport ke ruang dunia. Ini adalah kebalikan dari memproyeksikan koordinat dunia ke viewport dan dapat dicapai dengan membalikkan matriks. Pastikan juga Anda menonaktifkan z-buffer write atau menulis nilai yang cukup jauh.

Di bawah ini adalah vertex shader yang melakukan ini:

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

aPositionadalah koordinat titik {-1,-1; 1,-1; 1,1; -1,1}. Shader menghitung eyeDirectiondengan kebalikan dari model-view-projection matrix. Namun inversi dibagi untuk proyeksi dan matriks dunia-ke-kamera. Ini karena hanya bagian 3x3 dari matriks kamera yang harus digunakan untuk menghilangkan posisi kamera. Ini menyelaraskan kamera ke pusat skybox. Selain karena kamera saya tidak memiliki penskalaan atau geser, inversinya dapat disederhanakan menjadi transposisi. Inversi dari matriks proyeksi adalah operasi yang mahal dan dapat dihitung ulang, tetapi karena kode ini dieksekusi oleh vertex shader biasanya hanya empat kali per frame, biasanya bukan masalah.

Shader fragmen hanya melakukan pencarian tekstur menggunakan eyeDirectionvektor:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

Perhatikan bahwa untuk menyingkirkan mode kompatibilitas, Anda perlu mengganti textureCubedengan adil texturedan tentukan sendiri variabel keluarannya.

msell
sumber
Saya pikir Anda juga harus menyebutkan, bahwa inversi matriks adalah proses yang mahal, sehingga lebih baik terjadi dalam kode sisi klien.
akaltar
1
Untuk 4 ver quad fullscreen saya tidak berpikir kita perlu khawatir banyak tentang biaya inversi (terutama karena GPU melakukannya 4 kali mungkin masih akan lebih cepat daripada CPU melakukannya sekali).
Maximus Minimus
1
Hanya catatan yang bermanfaat bagi orang-orang, GLSL ES 1.0 (digunakan untuk GL ES 2.0) tidak menerapkaninverse()
Steven Lu
Apakah uWorldToCameraMatrix adalah MVP dari objek transformasi kamera?
Sidar
@ Sid Tidak, hanya matriks ModelView, Proyeksi terpisah.
msell