การนำ skybox ไปใช้กับ GLSL เวอร์ชัน 330


14

ฉันกำลังพยายามให้สกายบ็อกซ์ทำงานกับ OpenGL 3.3 และ GLSL เวอร์ชัน 330

ฉันไม่สามารถค้นหาการสอน OGL skybox ที่ทันสมัยได้อย่างสมบูรณ์ที่ใดก็ได้บนเว็บดังนั้นฉันจึงทำให้รุ่นเก่า (ใช้glVertexAttribPointer()แทนgl_Vertexจุดยอดเป็นต้น) ส่วนใหญ่ใช้งานได้ แต่สำหรับ 2 รายละเอียดหลัก

กล่องสกายนั้นเหมือนรูปสามเหลี่ยมบนท้องฟ้าและพื้นผิวนั้นบิดงอไม่ดีและยืดออก (มันควรจะเป็นทุ่งหญ้าดาวฉันได้รับขณะที่เส้นบนพื้นหลังสีดำ) ฉันแน่ใจ 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);
}

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:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

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

นี่เป็นตัวอย่างของความผิดพลาด หากใครสามารถดูที่รู้จัก GLSL ได้ดี (ฉันยังเรียนอยู่) หรือ skyboxes ฉันจะขอบคุณความช่วยเหลือที่คุณสามารถให้ได้ นอกจากนี้รุ่งโรจน์ถ้าคุณสามารถสอนฉันถึงวิธีการใช้ฟังก์ชั่นที่ไม่ได้รับการปฏิเสธในส่วนของ shader ดังนั้นฉันไม่ต้องใช้โปรไฟล์ความเข้ากันได้ของ glsl 330


แก้ไข: พบปัญหาทันทีกับพื้นผิวยืด: ฉันกำลังใช้Position = Vertex.xyxแทนPosition = Vertex.xyzในจุดสุดยอด อุ่ย แต่ข้อผิดพลาดของรูปสามเหลี่ยมยังคงมีอยู่


1
คุณต้องการเพียง 4 จุดยอด (เต็มหน้าจอรูปสี่เหลี่ยม) เพื่อแสดงสกายบ็อกซ์ที่มีพื้นผิว cubemap คุณเพียงแค่ต้องการจุดสุดยอดซึ่งคำนวณพิกัดพื้นผิวที่ถูกต้องตามกล้องและการฉายภาพ
msell

มันอาจเป็นปัญหาการเลือกสรร คุณได้ลองปิดการใช้งาน backface การคัดสรรเพื่อลองดูว่าคุณได้รับกล่องเต็มหรือไม่?
pwny

@ pwny ฉันไม่ได้คิดอย่างนั้น ฉันลองแล้วมันใช้งานไม่ได้ แต่ฉันสามารถดูว่ามันจะถูกโยนทิ้งไปได้อย่างไร ขอบคุณสำหรับคำแนะนำ
sm81095

@msell ฉันเคยได้ยินวิธีการนี้ แต่ฉันไม่พบบทเรียนออนไลน์สำหรับเรื่องนี้และฉันยังอยู่ในขั้นตอนการเรียนรู้ glsl หากคุณสามารถให้ตัวอย่างหรือลิงค์ไปยังตัวอย่างเกี่ยวกับวิธีการทำเช่นนี้ฉันจะขอบคุณอย่างมาก
sm81095

คำตอบ:


29

ในขณะที่คำตอบนี้ไม่ได้บอกว่ามีอะไรผิดปกติกับวิธีการของคุณมันนำเสนอวิธีที่ง่ายกว่าในการแสดงภาพสกายบ็อกซ์

แบบดั้งเดิม (ลูกบาศก์พื้นผิว)

วิธีที่ตรงไปตรงมาสำหรับการสร้างสกายบ็อกซ์คือการสร้างลูกบาศก์ที่มีพื้นผิวอยู่กึ่งกลางตำแหน่งของกล้อง แต่ละหน้าของลูกบาศก์ประกอบด้วยสองสามเหลี่ยมและพื้นผิว 2D (หรือส่วนหนึ่งของแผนที่) เนื่องจากพิกัดพื้นผิวแต่ละใบหน้าต้องมีจุดยอดของตนเอง วิธีนี้มีปัญหาในตะเข็บของใบหน้าที่อยู่ติดกันซึ่งค่าพื้นผิวไม่ถูกแก้ไขอย่างเหมาะสม

Cube ที่มีพื้นผิว cubemap

เช่นเดียวกับในแบบดั้งเดิมลูกบาศก์พื้นผิวจะถูกแสดงรอบกล้อง แทนที่จะใช้พื้นผิว 2D หกแบบจะใช้พื้นผิว cubemap เดียว เนื่องจากกล้องอยู่กึ่งกลางภายในลูกบาศก์จุดสุดยอดพิกัดจะแมปหนึ่งต่อหนึ่งกับเวกเตอร์การสุ่มตัวอย่าง cubemap ดังนั้นไม่จำเป็นต้องใช้พิกัดพื้นผิวสำหรับข้อมูลตาข่ายและสามารถแชร์จุดยอดระหว่างใบหน้าโดยใช้บัฟเฟอร์ดัชนี

วิธีการนี้จะแก้ไขปัญหาตะเข็บเมื่อเปิดใช้งาน GL_TEXTURE_CUBE_MAP_SEAMLESS

วิธีที่เรียบง่าย (ดีกว่า)

เมื่อเรนเดอร์ลูกบาศก์และกล้องอยู่ข้างในวิวพอร์ตทั้งหมดจะได้รับการเติมเต็ม สามารถมองเห็นท้องฟ้าได้สูงสุดห้าใบหน้าในบางช่วงเวลา สามเหลี่ยมใบหน้าลูกบาศก์ถูกคาดการณ์และตัดกับเวกเตอร์การสุ่มตัวอย่าง viewport และ cubemap นั้นถูกสอดแทรกระหว่างจุดยอด งานนี้ไม่จำเป็น

มีความเป็นไปได้ที่จะเติมเต็มหนึ่งในสี่ของวิวพอร์ตทั้งหมดและคำนวณเวกเตอร์การสุ่มตัวอย่าง cubemap ที่มุม เนื่องจากเวกเตอร์การสุ่มตัวอย่าง cubemap ตรงกับพิกัดจุดสุดยอดพวกเขาสามารถคำนวณได้โดยการยกเลิกการคาดคะเนพิกัดวิวพอร์ตไปยังอวกาศโลก นี่คือสิ่งที่ตรงกันข้ามกับการฉายพิกัดโลกไปยังวิวพอร์ตและสามารถทำได้โดยการแปลงเมทริกซ์ ตรวจสอบให้แน่ใจด้วยว่าคุณปิดใช้งานการเขียนบัฟเฟอร์ 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ด้วยค่าผกผันของเมทริกซ์ model-view-projection อย่างไรก็ตามการผกผันถูกแบ่งออกสำหรับการฉายภาพและเมทริกซ์จากกล้องสู่โลก นี่เป็นเพราะควรใช้ 3x3 ส่วนของเมทริกซ์กล้องเท่านั้นเพื่อกำจัดตำแหน่งของกล้อง นี่เป็นการจัดตำแหน่งกล้องให้อยู่กึ่งกลางสกายบ็อกซ์ นอกจากนี้ในขณะที่กล้องของฉันไม่มีการปรับขนาดหรือการตัดการผกผันสามารถทำให้การขนย้ายง่ายขึ้น การผกผันของเมทริกซ์การฉายเป็นการดำเนินการที่มีค่าใช้จ่ายสูงและสามารถคำนวณได้ล่วงหน้า แต่เนื่องจากรหัสนี้ถูกเรียกใช้โดยจุดสุดยอดโดยทั่วไปเพียงสี่ครั้งต่อเฟรมจึงมักไม่เป็นปัญหา

แฟรกเมนต์ shader ทำการค้นหาพื้นผิวโดยใช้eyeDirectionเวกเตอร์:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

โปรดทราบว่าในการกำจัดโหมดความเข้ากันได้คุณจะต้องแทนที่textureCubeด้วย just textureและระบุตัวแปรเอาต์พุตด้วยตัวคุณเอง


ฉันคิดว่าคุณควรพูดถึงเช่นกันว่าการผกผันของเมทริกซ์เป็นกระบวนการที่มีค่าใช้จ่ายสูงดังนั้นจึงเกิดขึ้นได้ดีขึ้นในรหัสฝั่งไคลเอ็นต์
akaltar

1
สำหรับ 4 verts ของ fullscreen quad ฉันไม่คิดว่าเราต้องกังวลมากเกี่ยวกับค่าใช้จ่ายในการผกผัน
Maximus Minimus

1
เป็นเพียงข้อความที่เป็นประโยชน์สำหรับคนทั่วไป GLSL ES 1.0 (ใช้สำหรับ GL ES 2.0) ไม่ได้ติดตั้งinverse()
Steven Lu

uWorldToCameraMatrix คือ MVP ของการแปลงวัตถุกล้อง?
Sidar

@ Sidar ไม่มันเป็นเพียงเมทริกซ์ ModelView การฉายแยกต่างหาก
msell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.