【问题标题】:Issues adding instancing to a working sprite system将实例添加到工作精灵系统的问题
【发布时间】:2025-12-10 08:55:02
【问题描述】:

我正在尝试在我的 2d 游戏引擎中实现实例化,以便它可以支持粒子系统而不会损失任何性能。我的类 ISprite 派生自一个有效的 Sprite 类。我刚刚完成并删除了影响单个精灵的所有功能,并用一个实例计划替换它。不幸的是,屏幕上没有任何东西。

以下是相关信息:

顶点着色器

#version 330 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;
layout (location = 2) in vec4 colorSource;
layout (location = 3) in mat4 transform;

out vec2 TexCoords;
out vec4 Color;

uniform mat4 uniformView;
uniform mat4 uniformProjection;

void main()
{
  gl_Position = uniformProjection * uniformView * transform * vec4(position, 1.0f);
  TexCoords = texCoords;
  Color = colorSource;
}

片段着色器

#version 330 core
in vec2 TexCoords;
in vec4 Color;

out vec4 color;

uniform sampler2D Texture;
uniform vec4 uniformColor;

void main()
{
    vec4 texColor = texture(Texture, TexCoords) * Color;
    if(texColor.a < 0.1)
        discard;

    color = texColor;
 }

加载 - 为绘制准备所有精灵,调用一次。

void ISprite::Load(Shader spriteShader)
{
    spriteShader.Use();

  GLfloat vertices[] = {
    //X    Y     Z     
    0.5f, -0.5f, 0.0f,
    -0.5f, 0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    -0.5f, 0.5f, 0.0f
  };

  glGenVertexArrays(1, &vertexArray);
  glGenBuffers(1, &positionBuffer);
  glGenBuffers(1, &texCoordsBuffer);
  glGenBuffers(1, &colorBuffer);
  glGenBuffers(1, &matrixBuffer);

  glBindVertexArray(vertexArray); 

  //The vertex data will never change, so send that data now. 
  glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  //For vertex Position
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (GLvoid*)0);

  //For texture coordinates
  glBindBuffer(GL_ARRAY_BUFFER, texCoordsBuffer);
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), (GLvoid*)0);

  //For Color
  glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
  glEnableVertexAttribArray(2);
  glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);

  //For Transformation Matrix
  glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
  for (int i = 0; i < 4; ++i)
  {
      glEnableVertexAttribArray(3 + i);
      glVertexAttribPointer(3 + i, 4, GL_FLOAT, GL_FALSE, 
          4 * 4 * sizeof(GLfloat), (GLvoid*)(4 * i * sizeof(GLfloat)));
  }

  glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, texCoordsBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
  glBindVertexArray(0);

  glVertexAttribDivisor(positionBuffer, 0);
  glVertexAttribDivisor(texCoordsBuffer, 1);
  glVertexAttribDivisor(colorBuffer, 1);
  glVertexAttribDivisor(matrixBuffer, 1);
  glVertexAttribDivisor(matrixBuffer + 1, 1);
  glVertexAttribDivisor(matrixBuffer + 2, 1);
  glVertexAttribDivisor(matrixBuffer + 3, 1);

  ISprite::shader = &spriteShader;
}

Prepare Draw - 由每个精灵、每一帧调用。将数据发送到静态向量

void ISprite::prepareDraw(void)
{
  //Adds their personal data to vectors shared by class 
  glm::mat4 transform = calculateTransorm();

  for (int i = 0; i < 4; ++i)
  {
      for (int j = 0; j < 4; ++j)
        ISprite::transformMatrices.push_back(transform[i][j]);
  }

  texture.updateAnimation();
  for (int i = 0; i < 12; ++i)
    ISprite::textureCoordinatesAll.push_back(texture.textureCoordinates[i]);

  ISprite::colorValues.push_back(color.x);
  ISprite::colorValues.push_back(color.y);
  ISprite::colorValues.push_back(color.z);
  ISprite::colorValues.push_back(color.w);
}

绘制精灵 - 每帧调用一次,实际绘制精灵

void ISprite::drawSprites(Texture testTexture)
{
  shader->Use();

  for (std::vector<ISprite*>::iterator it = Isprites.begin(); it != Isprites.end(); ++it)
    (*it)->prepareDraw();

  glBindVertexArray(vertexArray);
  glBindTexture(GL_TEXTURE_2D, testTexture.ID);

  //Bind texture here if you want textures to work. if not, a single texture atlas will be bound
  glBindBuffer(GL_ARRAY_BUFFER, texCoordsBuffer);
  glBufferData(GL_ARRAY_BUFFER, textureCoordinatesAll.size() * sizeof(GLfloat),
  textureCoordinatesAll.data(), GL_STREAM_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
  glBufferData(GL_ARRAY_BUFFER, colorValues.size() * sizeof(GLfloat),
  colorValues.data(), GL_STREAM_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
  glBufferData(GL_ARRAY_BUFFER, transformMatrices.size() * sizeof(GLfloat),
  transformMatrices.data(), GL_STREAM_DRAW);

  glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 6, Isprites.size());

  textureCoordinatesAll.clear();
  colorValues.clear();
  transformMatrices.clear();
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindTexture(GL_TEXTURE_2D, 0);
  glBindVertexArray(0);
}

【问题讨论】:

  • 没有直接解决您的代码问题,但是您是否有理由为每个精灵绘制一个四边形,而不是使用点精灵?除非它不能满足您的需求,否则这似乎更容易、更有效。
  • 嗯,我想把纹理放在四边形上。我真的没有充分的理由按照我的方式做事。所有这些原因都将是“他们在 learnopenGL.com 上就是这样做的”
  • 您可以将纹理应用于点精灵。我在这里的回答概述了如何做到这一点:*.com/questions/27098315/….

标签: c++ opengl instances


【解决方案1】:

没有渲染的原因可能有很多。转换问题、坐标超出范围等。但与实例化相关的一件事在发布的代码中看起来肯定是错误的:

glBindVertexArray(0);

glVertexAttribDivisor(positionBuffer, 0);
glVertexAttribDivisor(texCoordsBuffer, 1);
glVertexAttribDivisor(colorBuffer, 1);
...

glVertexAttribDivisor() 的第一个参数是顶点属性的位置,而不是缓冲区的名称。此外,此调用设置的状态是 VAO 状态的一部分,因此您应该在 VAO 仍处于绑定状态时进行这些调用。

所以调用应该是这样的:

glVertexAttribDivisor(0, 0);
glVertexAttribDivisor(1, 0);
glVertexAttribDivisor(2, 1);
...

glBindVertexArray(0);

glVertexAttribDivisor() 的第一个参数与您也用作glVertexAttribPointer()glEnableVertexAttribArray() 的第一个参数的位置值匹配。

纹理坐标的除数(属性 1)应该很可能是 0,因为您希望每个顶点设置纹理坐标,就像位置一样。对于颜色和其他剩余属性,1 是正确的值,以便在每个实例中应用它们。

另外,正如我在评论中提到的,您可能还想研究使用点精灵。虽然它们没有提供与绘制单个四边形相同的灵活性,但它们通常可以用于精灵。使用点精灵,每个精灵只需要一个顶点,并且纹理坐标是自动生成的。我在这里的回答概述了如何使用点精灵,包括如何将纹理应用于它们:Render large circular points in modern OpenGL

【讨论】:

  • 我对属性除数调用进行了更改并修复了 VAO 的绑定。可惜还是没有图。相同的 calculateTransform() 函数适用于我的非实例化精灵类,所以我不认为是这样。为了尝试检查这些东西是否超出范围,我将相机一直放大。我最终将使用纹理图集,因此每个实例的纹理坐标不会相同。
  • 如果将纹理坐标的除数设置为 1,则每个实例的所有 4 个顶点将具有相同的纹理坐标,这不会给您纹理。
  • 嗯,我对此有点困惑。我在prepareDraw 中推回精灵的所有12 个纹理坐标。不是每个实例都有自己的个人纹理坐标吗?我的理解是它会沿着数组走
  • 除数为 0 时,一个实例中的顶点会获得不同的属性值,然后这些值会针对每个实例重复。使用除数 1,一个实例中的所有顶点都获得相同的属性值,并且该值在每个实例中都会发生变化。
  • 嗯,我确实希望这些值影响不同的顶点,但我不希望每个实例都重复这些值。