自学内容网 自学内容网

【OpenGL】OpenGL游戏案例(一)

物理碰撞

AABB - AABB 计算

collisionX为true代表两个碰撞盒在X轴有重合,即双方的左边位置都小于等于对方的右边,collisionY同理

x与y轴都重合的情况下才能判断为两个碰撞盒重合

bool Game::CheckCollision(GameObject& one, GameObject& two) // AABB - AABB collision
{
    bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&
        two.Position.x + two.Size.x >= one.Position.x;

    bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&
        two.Position.y + two.Size.y >= one.Position.y;

    return collisionX && collisionY;
}

Circle - AABB 计算

首先计算碰撞盒中心到圆心的向量,然后将其限制在aabb的正负半高范围内。

将上述向量偏移到碰撞盒的中心位置,此时其终点位置在碰撞盒边缘(如果原本终点在碰撞盒内则不变)。

该向量与圆心相减,可得到圆心到距离最近的aabb盒的点的距离以及碰撞方向。

Collision Game::CheckCollision(BallObject& one, GameObject& two) // AABB - Circle collision
{
    // 圆心 
    glm::vec2 center(one.Position + one.Radius);
    
    // 方盒半高和中心
    glm::vec2 aabb_half_extents(two.Size.x / 2.0f, two.Size.y / 2.0f);
    glm::vec2 aabb_center(two.Position.x + aabb_half_extents.x, two.Position.y + aabb_half_extents.y);
    
    // aabb -> circle 向量
    glm::vec2 difference = center - aabb_center;
    // 将值限制在aabb中
    glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
    // 向量偏移到aabb中心上(原本是在原点计算的)
    glm::vec2 closest = aabb_center + clamped;
    // center -> closest 圆心到距离AABB最近距离的点的向量
    difference = closest - center;

    //根据距离计算是否重叠
    if (glm::length(difference) < one.Radius)
        return std::make_tuple(true, VectorDirection(difference), difference);
    else
        return std::make_tuple(false, UP, glm::vec2(0.0f, 0.0f));
}

方向计算

先预定义固定的方向值,再通过点乘的方式,找到最相似的方向并返回(越相似越接近1,反之接近-1)

Direction Game::VectorDirection(glm::vec2 target)
{
    glm::vec2 compass[] = {
        glm::vec2(0.0f, 1.0f),// up
        glm::vec2(1.0f, 0.0f),// right
        glm::vec2(0.0f, -1.0f),// down
        glm::vec2(-1.0f, 0.0f)// left
    };
    float max = 0.0f;
    unsigned int best_match = -1;
    for (unsigned int i = 0; i < 4; i++)
    {
        float dot_product = glm::dot(glm::normalize(target), compass[i]);
        if (dot_product > max)
        {
            max = dot_product;
            best_match = i;
        }
    }
    return (Direction)best_match;
}

粒子

数据结构

粒子

struct Particle {
    glm::vec2 Position, Velocity;
    glm::vec4 Color;
    float     Life;

    Particle() : Position(0.0f), Velocity(0.0f), Color(1.0f), Life(0.0f) { }
};

生成器

class ParticleGenerator
{
public:
    ParticleGenerator(Shader shader, Texture2D texture, unsigned int amount);

    void Update(float dt, GameObject& object, unsigned int newParticles, glm::vec2 offset = glm::vec2(0.0f, 0.0f));

    void Draw();

private:
    // state
    std::vector<Particle> particles;
    unsigned int amount;

    // render state
    Shader shader;
    Texture2D texture;
    unsigned int VAO;

    void init();
    unsigned int firstUnusedParticle();
    void respawnParticle(Particle& particle, GameObject& object, glm::vec2 offset = glm::vec2(0.0f, 0.0f));
};

绘制流程

  • 构造函数初始化shader,texture,amount,然后调用Generator的Init函数,创建VAO,VBO,并根据amount构造对应数量的Particle
  • Update中每次都获得第一个未使用的Particle,并进行生成。使用池化思想,粒子提前构造,生成时获取未使用Particle复用。同时Update中会更新池中的活跃粒子位置和颜色
  • Draw中修改混合规则为叠加(发光效果),使用particle的shader,为活跃的Particle更新uniform变量并绘制

获取首个未使用粒子

首先从最后一个使用的粒子开始向后遍历,这样可以大概率找到未使用的粒子,如果没有找到则从头开始遍历。最后也没有找到则返回最后一个使用的粒子的下一个。

unsigned int lastUsedParticle = 0;
unsigned int ParticleGenerator::firstUnusedParticle()
{
    for (unsigned int i = lastUsedParticle; i < this->amount; ++i) {
        if (this->particles[i].Life <= 0.0f) {
            lastUsedParticle = i;
            return i;
        }
    }

    for (unsigned int i = 0; i < lastUsedParticle; ++i) {
        if (this->particles[i].Life <= 0.0f) {
            lastUsedParticle = i;
            return i;
        }
    }

    lastUsedParticle = (lastUsedParticle + 1) % amount;
return lastUsedParticle;
}

后处理

使用帧缓冲进行离屏渲染,并在渲染完毕后应用shader进行后处理

帧缓冲初始化流程

主要设置和初始化帧缓冲对象(FBO)、多重采样帧缓冲对象(MSFBO)、渲染缓冲对象(RBO)以及纹理等,用于图像后处理效果。下面是对每部分的详细解释:

多重采样帧缓冲对象(MSFBO)初始化
glGenFramebuffers(1, &this->MSFBO);
glGenRenderbuffers(1, &this->RBO);
glBindFramebuffer(GL_FRAMEBUFFER, this->MSFBO);
glBindRenderbuffer(GL_RENDERBUFFER, this->RBO);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGB, width, height); 
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, this->RBO);
  • glGenFramebuffers(1, &this->MSFBO):生成一个多重采样帧缓冲对象(MSFBO)。
  • glGenRenderbuffers(1, &this->RBO):生成一个渲染缓冲对象(RBO)。
  • glBindFramebuffer(GL_FRAMEBUFFER, this->MSFBO):绑定刚刚创建的MSFBO。
  • glBindRenderbuffer(GL_RENDERBUFFER, this->RBO):绑定RBO。
  • glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGB, width, height):为RBO分配内存,指定4倍多重采样和RGB格式。
  • glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, this->RBO):将RBO附加到MSFBO的颜色附件点。
帧缓冲对象(FBO)初始化
glGenFramebuffers(1, &this->FBO);
glBindFramebuffer(GL_FRAMEBUFFER, this->FBO);
this->Texture.Generate(width, height, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this->Texture.ID, 0);
  • glGenFramebuffers(1, &this->FBO):生成一个普通的FBO。
  • glBindFramebuffer(GL_FRAMEBUFFER, this->FBO):绑定创建的FBO。
  • this->Texture.Generate(width, height, NULL):创建纹理,用于存储FBO的渲染结果。
  • glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this->Texture.ID, 0):将纹理附加到FBO的颜色附件点。
初始化渲染数据和着色器参数
this->initRenderData();
this->PostProcessingShader.SetInteger("scene", 0, true);
  • this->initRenderData():初始化渲染数据(例如顶点缓冲区对象、顶点数组对象等),以便后续的渲染操作,函数内容如下,主要构造覆盖屏幕的网格。
  • this->PostProcessingShader.SetInteger("scene", 0, true):设置着色器中的“scene”纹理单元为0,通常这是后期处理图像的输入纹理。
void PostProcessor::initRenderData()
{
    // configure VAO/VBO
    unsigned int VBO;
    float vertices[] = {
        // pos        // tex
        -1.0f, -1.0f, 0.0f, 0.0f,
         1.0f,  1.0f, 1.0f, 1.0f,
        -1.0f,  1.0f, 0.0f, 1.0f,

        -1.0f, -1.0f, 0.0f, 0.0f,
         1.0f, -1.0f, 1.0f, 0.0f,
         1.0f,  1.0f, 1.0f, 1.0f
    };
    glGenVertexArrays(1, &this->VAO);
    glGenBuffers(1, &VBO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(this->VAO);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}
设置偏移量和卷积核
偏移量
float offset = 1.0f / 300.0f;
float offsets[9][2] = {
    { -offset,  offset  },
    {  0.0f,    offset  },
    {  offset,  offset  },
    { -offset,  0.0f    },
    {  0.0f,    0.0f    },
    {  offset,  0.0f    },
    { -offset, -offset  },
    {  0.0f,   -offset  },
    {  offset, -offset  }
};
glUniform2fv(glGetUniformLocation(this->PostProcessingShader.ID, "offsets"), 9, (float*)offsets);
  • offset:计算出用于后处理的像素偏移量。
  • offsets:存储了9个相邻像素的偏移量(用于卷积操作,如模糊效果)。
  • glUniform2fv:将偏移量数据传递给着色器中的 offsets uniform。
边缘检测卷积核
int edge_kernel[9] = {
    -1, -1, -1,
    -1,  8, -1,
    -1, -1, -1
};
glUniform1iv(glGetUniformLocation(this->PostProcessingShader.ID, "edge_kernel"), 9, edge_kernel);
  • edge_kernel:设置一个简单的边缘检测卷积核(Sobel算子)。
  • glUniform1iv:将卷积核传递给着色器中的 edge_kernel uniform。
模糊卷积核
float blur_kernel[9] = {
    1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f,
    2.0f / 16.0f, 4.0f / 16.0f, 2.0f / 16.0f,
    1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f
};
glUniform1fv(glGetUniformLocation(this->PostProcessingShader.ID, "blur_kernel"), 9, blur_kernel);
  • blur_kernel:设置一个用于模糊处理的卷积核(高斯模糊)。
  • glUniform1fv:将模糊卷积核传递给着色器中的 blur_kernel uniform。
总结

这个构造函数主要用于设置一个后处理器,它通过创建多个OpenGL对象(FBO、MSFBO、RBO、纹理)来实现图像的多重采样渲染、后期处理效果(如模糊、边缘检测等)。它还设置了相关的着色器参数,并为后期处理操作初始化了相关的渲染数据。

使用流程

BeginRender

在渲染循环中,在渲染其他物体前调用BeginRender函数。

绑定了MSFBO,代表接下来绘制的数据都填充入MSFBO,由于只挂载了一个接受Color数据的RBO,因此只接收Color数据。

然后执行了清空颜色和颜色缓冲的命令来准备下一次绘制

void PostProcessor::BeginRender()
{
    glBindFramebuffer(GL_FRAMEBUFFER, this->MSFBO);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}
EndRender

在渲染完其他物体后,调用EndRender函数。

将MSFBO的数据转移到FBO中

void PostProcessor::EndRender()
{
    // 现在将多采样颜色缓冲区解析为中间FBO以存储到纹理
    glBindFramebuffer(GL_READ_FRAMEBUFFER, this->MSFBO);  //读这个
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, this->FBO);    //写到这个
    //传递缓冲区数据
    glBlitFramebuffer(0, 0, this->Width, this->Height, 0, 0, this->Width, this->Height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // binds both READ and WRITE framebuffer to default framebuffer
}
Render

在EndRender后调用该函数进行真正的绘制

根据当前数据修改shader的uniform参数

绑定输出纹理并绘制到屏幕上

void PostProcessor::Render(float time)
{
    // set uniforms/options
    this->PostProcessingShader.Use();
    this->PostProcessingShader.SetFloat("time", time);
    this->PostProcessingShader.SetInteger("confuse", this->Confuse);
    this->PostProcessingShader.SetInteger("chaos", this->Chaos);
    this->PostProcessingShader.SetInteger("shake", this->Shake);
    // render textured quad
    glActiveTexture(GL_TEXTURE0);
    this->Texture.Bind();
    glBindVertexArray(this->VAO);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);
}

Shader

这段GLSL代码包含了顶点着色器(.vs)和片段着色器(.frag),其主要作用是实现一些图像效果,具体包括混乱效果(chaos)、翻转效果(confuse)和震动效果(shake)。下面对代码进行详细解释:

顶点着色器(.vs)
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>

out vec2 TexCoords;

uniform bool chaos;
uniform bool confuse;
uniform bool shake;
uniform float time;

void main()
{
    gl_Position = vec4(vertex.xy, 0.0f, 1.0f); 
    vec2 texture = vertex.zw;
    if (chaos)
    {
        float strength = 0.3;
        vec2 pos = vec2(texture.x + sin(time) * strength, texture.y + cos(time) * strength);        
        TexCoords = pos;
    }
    else if (confuse)
    {
        TexCoords = vec2(1.0 - texture.x, 1.0 - texture.y);
    }
    else
    {
        TexCoords = texture;
    }
    if (shake)
    {
        float strength = 0.01;
        gl_Position.x += cos(time * 10) * strength;        
        gl_Position.y += cos(time * 15) * strength;        
    }
}
1. 输入和输出:
  • vertex: 输入的顶点数据,是一个四维向量,包含位置和纹理坐标(<vec2 position, vec2 texCoords>)。
  • TexCoords: 输出的纹理坐标,将传递给片段着色器。
2. uniforms:
  • chaos, confuse, shake: 控制不同效果的布尔变量。
  • time: 一个浮动时间变量,用于控制时间相关的动态效果。
3. 主程序:
  • 位置设置:

    gl_Position = vec4(vertex.xy, 0.0f, 1.0f);
    

    这行代码将顶点的xy值传递给gl_Positionz设为0,w设为1,表示顶点的空间位置。

  • 纹理坐标设置:

    • 如果chaos效果启用:
      • 计算一个新的纹理坐标,利用时间和sincos函数来引入动态变化,表现为混乱效果。
    • 如果confuse效果启用:
      • 通过反转纹理坐标的xy分量,产生翻转效果。
    • 默认情况下,直接使用传入的纹理坐标。
  • 震动效果:

    • 如果启用了shake,则使用cos函数和时间对gl_Positionxy分量进行微小的调整,模拟震动效果。
片段着色器(.frag)
#version 330 core
in  vec2  TexCoords;
out vec4  color;

uniform sampler2D scene;
uniform vec2      offsets[9];
uniform int       edge_kernel[9];
uniform float     blur_kernel[9];

uniform bool chaos;
uniform bool confuse;
uniform bool shake;

void main()
{
    color = vec4(0.0f);
    vec3 sample[9];
    // sample from texture offsets if using convolution matrix
    if(chaos || shake)
        for(int i = 0; i < 9; i++)
            sample[i] = vec3(texture(scene, TexCoords.st + offsets[i]));

    // process effects
    if (chaos)
    {           
        for(int i = 0; i < 9; i++)
            color += vec4(sample[i] * edge_kernel[i], 0.0f);
        color.a = 1.0f;
    }
    else if (confuse)
    {
        color = vec4(1.0 - texture(scene, TexCoords).rgb, 1.0);
    }
    else if (shake)
    {
        for(int i = 0; i < 9; i++)
            color += vec4(sample[i] * blur_kernel[i], 0.0f);
        color.a = 1.0f;
    }
    else
    {
        color =  texture(scene, TexCoords);
    }
}
1. 输入和输出:
  • TexCoords: 从顶点着色器传递来的纹理坐标。
  • color: 输出的颜色值,将最终渲染到屏幕上。
2. uniforms:
  • scene: 输入的纹理(通常是场景图像)。
  • offsets: 用于计算卷积核的偏移量数组,通常用于边缘检测或模糊效果。
  • edge_kernel: 用于混乱效果的边缘检测卷积核。
  • blur_kernel: 用于震动效果的模糊卷积核。
  • chaos, confuse, shake: 控制不同效果的布尔变量。
3. 主程序:
  • 样本采样:

    • 如果启用了chaosshake,则使用offsets数组中的偏移量来从纹理中采样9个邻近的像素,存储在sample数组中。
  • 效果处理:

    • 混乱效果(chaos)
      • 对9个邻近的样本进行加权计算,通过edge_kernel数组中的值进行卷积操作,将其加到color上。edge_kernel[i]是整数,控制每个样本的权重。
    • 翻转效果(confuse)
      • 将纹理的颜色进行反转,得到与原色相反的颜色。
    • 震动效果(shake)
      • 使用blur_kernel数组对采样的像素进行模糊处理,进行卷积操作,得到震动效果。
    • 默认效果:
      • 如果没有启用任何特殊效果,直接输出纹理颜色。

原文地址:https://blog.csdn.net/Siro_sama/article/details/145288084

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!