使用GLSL 330版实现Skybox


14

我正在尝试让Skybox使用OpenGL 3.3和GLSL 330版。

我在网络上的任何地方都找不到完整的现代OGL skybox教程,因此我对一个较旧的版本进行了现代化(使用glVertexAttribPointer()而不是gl_Vertex用于顶点等)。它主要在工作,但是有两个主要细节:

天空盒更像天空三角形,纹理严重扭曲和拉伸(它们应该是星场,我在黑色背景上看到线条)。我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);
}

顶点着色器:

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

片段着色器:

#version 330 compatibility

uniform samplerCube SkyTexture;

in vec3 Position;

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

是故障的一个例子。如果有人看得懂GLSL(我仍在学习)或Skybox,那么我将不胜感激。另外,如果您能教我如何在片段着色器中使用不推荐使用的功能,则表示敬意,因此我不必使用glsl 330的兼容性配置文件。


编辑:立即发现拉伸纹理的问题:我正在使用Position = Vertex.xyx而不是Position = Vertex.xyz在顶点着色器中。哎呀 但是三角误差仍然存在。


1
您只需要4个顶点(全屏四边形)即可渲染具有立方体贴图纹理的天空盒。您只需要一个顶点着色器即可根据相机和投影计算正确的纹理坐标。
msell,2013年

这可能是一个淘汰的问题。您是否尝试过禁用背面剔除以尝试查看是否有完整的盒子?
pwny 2013年

@pwny,我没想到。我尝试了一下,但是没有用,但是我可以看到它是如何抛出它的。谢谢你的建议。
sm81095 2013年

@msell,我听说过这种方法,但是我没有为此找到在线教程,而且我还在学习glsl。如果您可以提供一个示例或示例链接以了解如何执行此操作,我将不胜感激。
sm81095

Answers:


29

虽然此答案不能说明您的方法出了什么问题,但它提供了一种更简单的渲染天空盒的方法。

传统方式(纹理立方体)

创建天空框的一种直接方法是渲染以相机位置为中心的纹理立方体。立方体的每个面都包含两个三角形和一个2D纹理(或地图集的一部分)。由于纹理坐标,每个面都需要有自己的顶点。这种方法在相邻面的接缝中存在问题,在这些面中纹理值未正确插值。

多维数据集纹理的多维数据集

像传统方式一样,在相机周围渲染带纹理的立方体。代替使用六个2D纹理,使用单个立方体贴图纹理。由于摄影机位于立方体内部,因此顶点坐标与立方体贴图采样向量一一对应。因此,网格数据不需要纹理坐标,并且可以使用索引缓冲区在面之间共享顶点。

启用GL_TEXTURE_CUBE_MAP_SEAMLESS时,此方法还解决了接缝问题。

更简单(更好)的方式

渲染多维数据集并将相机放置在其中时,整个视口将被填充。随时可以部分看到天空盒的多达五个面。将立方体面的三角形投影并裁剪到视口,并在顶点之间插入立方体贴图采样向量。这项工作是不必要的。

可以填充单个四边形来填充整个视口,并计算拐角处的立方体贴图采样向量。由于立方体贴图采样矢量与顶点坐标匹配,因此可以通过将视口坐标投影到世界空间来计算它们。这与将世界坐标投影到视口相反,可以通过反转矩阵来实现。还要确保禁用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使用模型-视图-投影矩阵的逆矩阵进行计算。但是,反演分为投影矩阵和世界相机矩阵。这是因为仅应使用摄像机矩阵的3x3部分来消除摄像机的位置。这会将相机对准天空盒的中心。另外,由于我的相机没有任何缩放或剪切作用,因此可以将反演简化为换位。投影矩阵的求反是一项昂贵的操作,可以预先计算,但是由于此代码通常由顶点着色器每帧执行四次,因此通常不是问题。

片段着色器仅使用eyeDirectionvector 执行纹理查找:

#version 330
uniform samplerCube uTexture;

smooth in vec3 eyeDirection;

out vec4 fragmentColor;

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

请注意,要摆脱兼容模式,您需要替换textureCube为just texture并自己指定输出变量。


我想您还应该提到,矩阵求逆是一个代价高昂的过程,因此最好在客户端代码中进行。
akaltar

1
对于全屏四边形的4个版本,我认为我们不必担心反转的成本(尤其是GPU进行4倍运算的速度仍可能会比CPU进行一次运算的速度更快)。
Maximus Minimus

1
GLSL ES 1.0(用于GL ES 2.0)对人们只是一个有益的提示,并未实现inverse()
Steven Lu

相机转换对象的MVP是uWorldToCameraMatrix吗?
锡达

@Sidar不,这只是ModelView矩阵,Projection是独立的。
msell
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.