c++ - OpenGL:Lambert 着色导入的 OBJ 会导致伪影和奇怪的剔除
问题描述
我决定发布此内容,因为我现在认为问题不仅仅是源于着色器程序,而且很可能是 OBJ 导入和网格初始化过程。我想写一个快速的 Lambert 着色器,最终让屏幕上出现的东西。最终结果充满了有趣的工件和可见性问题:
顶点着色器
#version 330
// MeshVertex
in layout(location=0) vec3 a_Position;
in layout(location=1) vec3 a_Normal;
in layout(location=2) vec2 a_UV;
in layout(location=3) vec3 a_Tangent;
in layout(location=4) vec3 a_BiTangent;
uniform mat4 View;
uniform mat4 Projection;
uniform mat4 Model;
out VS_out
{
vec3 fragNormal;
vec3 fragPos;
} vs_out;
void main()
{
mat3 normalMatrix = mat3(transpose(inverse(Model)));
vec4 position = vec4(a_Position, 1.f);
vs_out.fragPos = (Model * position).xyz;
vs_out.fragNormal = normalMatrix * a_Normal;
gl_Position = Projection * View * Model * position;
}
我最初以为我错误地将顶点法线传递给片段着色器。我已经看到一些示例将顶点位置乘以 ModelView 矩阵。这对我来说听起来不直观,我的灯光位于世界空间中,所以我需要顶点的世界空间坐标,因此只需乘以模型矩阵。如果在这个思考过程中没有危险信号,这里是片段着色器:
#version 330
struct LightSource
{
vec3 position;
vec3 intensity;
};
uniform LightSource light;
in VS_out
{
vec3 fragNormal;
vec3 fragPos;
} fs_in;
struct Material
{
vec4 color;
vec3 ambient;
};
uniform Material material;
void main()
{
// just playing around with some values for now, dont worry, removing this still does not fix the issue
vec3 ambient = normalize(vec3(69, 111, 124));
vec3 norm = normalize(fs_in.fragNormal);
vec3 pos = fs_in.fragPos;
vec3 lightDir = normalize(light.position - pos);
float lambert = max(dot(norm, lightDir), 0.0);
vec3 illumination = (lambert * light.intensity) + ambient;
gl_FragColor = vec4(illumination * material.color.xyz, 1.f);
}
现在主要的怀疑是如何解释 OBJ。我为此使用tinyOBJ导入器。我主要复制了他们在 GitHub 页面上的示例代码,并使用该数据初始化了我的本机顶点类型。
OBJ 导入代码
bool Model::Load(const void* rawBinary, size_t bytes)
{
tinyobj::ObjReader reader;
if(reader.ParseFromString((const char*)rawBinary, ""))
{
// Fetch meshes
std::vector<MeshVertex> vertices;
std::vector<Triangle> triangles;
const tinyobj::attrib_t& attrib = reader.GetAttrib();
const std::vector<tinyobj::shape_t>& shapes = reader.GetShapes();
m_Meshes.resize(shapes.size());
m_Materials.resize(shapes.size());
// Loop over shapes; in our case, each shape corresponds to a mesh object
for(size_t s = 0; s < shapes.size(); s++)
{
// Loop over faces(polygon)
size_t index_offset = 0;
for(size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
{
// Num of face vertices for face f
int fv = shapes[s].mesh.num_face_vertices[f];
ASSERT(fv == 3, "Only supporting triangles for now");
Triangle tri;
// Loop over vertices in the face.
for(size_t v = 0; v < fv; v++) {
// access to vertex
tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
tinyobj::real_t vx = 0.f;
tinyobj::real_t vy = 0.f;
tinyobj::real_t vz = 0.f;
tinyobj::real_t nx = 0.f;
tinyobj::real_t ny = 0.f;
tinyobj::real_t nz = 0.f;
tinyobj::real_t tx = 0.f;
tinyobj::real_t ty = 0.f;
vx = attrib.vertices[3 * idx.vertex_index + 0];
vy = attrib.vertices[3 * idx.vertex_index + 1];
vz = attrib.vertices[3 * idx.vertex_index + 2];
if(attrib.normals.size())
{
nx = attrib.normals[3 * idx.normal_index + 0];
ny = attrib.normals[3 * idx.normal_index + 1];
nz = attrib.normals[3 * idx.normal_index + 2];
}
if(attrib.texcoords.size())
{
tx = attrib.texcoords[2 * idx.texcoord_index + 0];
ty = attrib.texcoords[2 * idx.texcoord_index + 1];
}
// Populate our native vertex type
MeshVertex meshVertex;
meshVertex.Position = glm::vec3(vx, vy, vz);
meshVertex.Normal = glm::vec3(nx, ny, nz);
meshVertex.UV = glm::vec2(tx, ty);
meshVertex.BiTangent = glm::vec3(0.f);
meshVertex.Tangent = glm::vec3(0.f);
vertices.push_back(meshVertex);
tri.Idx[v] = index_offset + v;
}
triangles.emplace_back(tri);
index_offset += fv;
// per-face material
//shapes[s].mesh.material_ids[f];
}
// Adding meshes should occur here!
m_Meshes[s] = std::make_unique<StaticMesh>(vertices, triangles);
// m_Materials[s] = ....
}
}
return true;
}
以我理解 OBJ 的方式,OpenGL 索引的概念并不等同于 OBJ 中的 Face 元素。这是因为每个面元素在位置、法线和 texcoord 数组中都有不同的索引。因此,我只是将 face 元素索引的顶点属性复制到我的原生 MeshVertex 结构中——这代表了我的网格的一个顶点;相应的面部元素 ID 就是我的索引缓冲区对象的相应索引。在我的例子中,我使用了一个三角形结构,但它实际上是一回事。
感兴趣的三角形结构:
struct Triangle
{
uint32_t Idx[3];
Triangle(uint32_t v1, uint32_t v2, uint32_t v3)
{
Idx[0] = v1;
Idx[1] = v2;
Idx[2] = v3;
}
Triangle(const Triangle& Other)
{
Idx[0] = Other.Idx[0];
Idx[1] = Other.Idx[1];
Idx[2] = Other.Idx[2];
}
Triangle()
{
}
};
除此之外,我不知道是什么导致了这个问题,我愿意听到新的想法;也许有经验的人明白这些文物意味着什么。如果您想更深入地了解,我也可以发布网格初始化代码。
编辑: 所以我尝试导入 FBX 格式,我遇到了一个非常相似的问题。我现在正在考虑我的 OpenGL 代码中的愚蠢错误来初始化网格。
这会根据任意顶点数据和三角形来初始化 OpenGL 缓冲区以进行索引
void Mesh::InitBuffers(const void* vertexData, size_t size, const std::vector<Triangle>& triangles)
{
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
// Interleaved Vertex Buffer
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, size, vertexData, GL_STATIC_DRAW);
// Index Buffer
glGenBuffers(1, &m_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Triangle) * triangles.size(), triangles.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
然后我使用 BufferLayout 结构设置顶点缓冲区的布局,该结构指定我们想要的属性。
void Mesh::SetBufferLayout(const BufferLayout& layout)
{
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
uint32_t stride = layout.GetStride();
int i = 0;
for(const BufferElement& element : layout)
{
glEnableVertexAttribArray(i);
glVertexAttribPointer(i++, element.GetElementCount(), GLType(element.Type), element.Normalized, stride, (void*)(element.Offset));
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
所以在我们的例子中,BufferLayout 对应于我填充的 MeshVertex,包含 Position(float3)、Normal(float3)、UV(float2)、Tangent(float3)、BiTangent(float3)。我可以通过调试确认步幅和偏移量以及来自 BufferElement 的其他值正是我所期望的;所以我关心我正在做的 OpenGL 调用的性质。
解决方案
推荐阅读
- sql - 根据 Amazon Athena DB 中的子查询结果设置标志的最佳方法是什么?
- python - Winerror 183 当文件已存在时无法创建文件
- php - 使用 htaccess 重写 url 的问题
- c# - 进程启动时如何运行代码?C#
- multithreading - 在 SPSC 框架中,condition_variable.notify_one() 信号不一致
- batch-file - 如何忽略批处理文件中的“找不到文件”字符串?
- oracle-apex - 动态刷新项目 oracle apex
- python-3.x - TypeError:只能将列表(不是“元组”)连接到列表
- python - 如何将带有“.py”扩展名的文件名更改为带有文件夹和子文件夹的“.txt”
- python - 同一图中的条形图和计数图