Triển khai skybox với GLSL phiên bản 330


14

Tôi đang cố gắng để một skybox hoạt động với OpenGL 3.3 và GLSL phiên bản 330.

Tôi không thể tìm thấy một hướng dẫn skybox OGL hoàn toàn hiện đại ở bất cứ đâu trên web, vì vậy tôi đã hiện đại hóa một cái cũ hơn (sử dụng glVertexAttribPointer()thay vì gl_Vertexcho các đỉnh, v.v.). Nó chủ yếu hoạt động, nhưng với 2 chi tiết chính:

Các skybox giống như các hình tam giác trên bầu trời và các kết cấu bị cong vênh và kéo dài (chúng được cho là các trường sao, tôi nhận được trong khi các dòng trên nền đen). Tôi chắc chắn 99% rằng điều này là do tôi đã không chuyển các hướng dẫn cũ hoàn toàn chính xác.

Đây là lớp skybox của tôi:

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

Mảnh vỡ mảnh:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

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

Đây là một ví dụ về các trục trặc. Nếu bất cứ ai cũng có thể xem ai biết rõ về GLSL (tôi vẫn đang học nó) hoặc skybox, tôi sẽ đánh giá cao bất kỳ trợ giúp nào bạn có thể cung cấp. Ngoài ra, kudos nếu bạn có thể dạy tôi cách sử dụng các hàm không bị phản đối trong shader mảnh để tôi không phải sử dụng cấu hình tương thích của glsl 330.


EDIT: Ngay lập tức tìm thấy vấn đề với kết cấu kéo dài: Tôi đã sử dụng Position = Vertex.xyxthay vìPosition = Vertex.xyz trong shader đỉnh. Giáo sư. Nhưng lỗi tam giác vẫn tồn tại.


1
Bạn chỉ cần 4 đỉnh (quad toàn màn hình) để hiển thị skybox với kết cấu hình khối. Bạn chỉ cần một shader đỉnh tính toán tọa độ kết cấu chính xác dựa trên máy ảnh và phép chiếu.
msell

Nó có thể là một vấn đề tiêu hủy. Bạn đã thử vô hiệu hóa loại bỏ backface để thử và xem nếu bạn nhận được hộp đầy đủ?
pwny

@pwny, tôi không nghĩ về điều đó. Tôi đã thử nó, và nó không hoạt động, nhưng tôi có thể thấy làm thế nào mà nó có thể ném nó đi. Cám ơn vì sự gợi ý.
sm81095

@msell, tôi đã nghe nói về phương pháp này, nhưng tôi không tìm thấy một hướng dẫn trực tuyến nào cho việc này và tôi vẫn đang trong quá trình học glsl. Nếu bạn có thể cung cấp một ví dụ hoặc một liên kết đến một ví dụ về cách làm điều này, tôi sẽ đánh giá rất cao điều đó.
sm81095

Câu trả lời:


29

Mặc dù câu trả lời này không cho biết điều gì sai với cách tiếp cận của bạn, nhưng nó trình bày một cách đơn giản hơn để kết xuất skybox.

Cách truyền thống (kết cấu khối)

Một cách đơn giản để tạo skybox là kết xuất một khối kết cấu ở giữa vị trí camera. Mỗi mặt của khối lập phương bao gồm hai hình tam giác và kết cấu 2D (hoặc một phần của tập bản đồ). Do tọa độ kết cấu mỗi mặt đòi hỏi các đỉnh riêng. Cách tiếp cận này có vấn đề trong các đường nối của các mặt liền kề, trong đó các giá trị kết cấu không được nội suy đúng.

Cube với kết cấu khối

Giống như theo cách truyền thống, một khối kết cấu được hiển thị xung quanh máy ảnh. Thay vì sử dụng sáu kết cấu 2D, một kết cấu sơ đồ khối được sử dụng. Vì máy ảnh được đặt chính giữa bên trong khối lập phương, nên tọa độ đỉnh ánh xạ từ một đến một với các vectơ lấy mẫu hình khối. Do đó, tọa độ kết cấu không cần thiết cho dữ liệu lưới và các đỉnh có thể được chia sẻ giữa các mặt bằng cách sử dụng bộ đệm chỉ mục.

Cách tiếp cận này cũng khắc phục sự cố đường nối khi GL_TEXTURE_CUBE_MAP_SEAMLESS được bật.

Cách đơn giản hơn (tốt hơn)

Khi kết xuất một khối và camera nằm bên trong nó, toàn bộ khung nhìn sẽ được lấp đầy. Tối đa năm mặt của skybox có thể được nhìn thấy một phần bất cứ lúc nào. Các hình tam giác của các mặt khối được chiếu và cắt theo các vectơ lấy mẫu của khung nhìn và hình khối được nội suy giữa các đỉnh. Công việc này là không cần thiết.

Có thể điền vào một hình tứ giác lấp đầy toàn bộ khung nhìn và tính toán các vectơ lấy mẫu hình khối ở các góc. Vì các vectơ lấy mẫu hình khối phù hợp với tọa độ đỉnh, nên chúng có thể được tính bằng cách bỏ các tọa độ khung nhìn vào không gian thế giới. Điều này trái ngược với các tọa độ thế giới chiếu tới khung nhìn và có thể đạt được bằng cách đảo ngược các ma trận. Ngoài ra, hãy đảm bảo bạn vô hiệu hóa ghi bộ đệm z hoặc ghi một giá trị đủ xa.

Dưới đây là shader đỉnh thực hiện điều này:

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

aPositionlà tọa độ đỉnh {-1,-1; 1,-1; 1,1; -1,1}. Shader tính toáneyeDirection với nghịch đảo của ma trận chiếu mô hình-khung nhìn. Tuy nhiên, sự đảo ngược được phân chia cho ma trận chiếu và ma trận trên máy ảnh. Điều này là do chỉ nên sử dụng phần 3x3 của ma trận camera để loại bỏ vị trí của camera. Điều này căn chỉnh máy ảnh vào trung tâm của skybox. Ngoài ra, vì máy ảnh của tôi không có bất kỳ tỷ lệ hoặc cắt nào, việc đảo ngược có thể được đơn giản hóa thành chuyển vị. Sự đảo ngược của ma trận chiếu là một hoạt động tốn kém và có thể được tính toán trước, nhưng vì mã này được thực thi bởi trình đổ bóng đỉnh thường chỉ bốn lần trên mỗi khung, nên thường không phải là vấn đề.

Trình đổ bóng mảnh chỉ đơn giản là thực hiện tra cứu kết cấu bằng eyeDirectionvectơ:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

Lưu ý rằng để thoát khỏi chế độ tương thích, bạn cần thay thế textureCubechỉ texturevà tự chỉ định biến đầu ra.


Tôi nghĩ bạn cũng nên đề cập rằng, đảo ngược ma trận là một quá trình tốn kém, vì vậy nó sẽ diễn ra tốt hơn trong mã phía máy khách.
akaltar

1
Đối với 4 câu của một quad toàn màn hình, tôi không nghĩ rằng chúng ta cần lo lắng nhiều về chi phí đảo ngược (đặc biệt là khi GPU làm điều đó 4 lần vẫn có thể sẽ nhanh hơn CPU làm điều đó một lần).
Maximus Minimus

1
Chỉ là một lưu ý hữu ích cho mọi người, GLSL ES 1.0 (được sử dụng cho GL ES 2.0) không triển khaiinverse()
Steven Lu

uWorldToCameraMatrix là MVP của đối tượng biến đổi máy ảnh?
Sidar

@Sidar Không, đó chỉ là ma trận ModelView, Phép chiếu là riêng biệt.
msell
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.