【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);
这行代码将顶点的
x
和y
值传递给gl_Position
,z
设为0,w
设为1,表示顶点的空间位置。 -
纹理坐标设置:
- 如果
chaos
效果启用:- 计算一个新的纹理坐标,利用时间和
sin
、cos
函数来引入动态变化,表现为混乱效果。
- 计算一个新的纹理坐标,利用时间和
- 如果
confuse
效果启用:- 通过反转纹理坐标的
x
和y
分量,产生翻转效果。
- 通过反转纹理坐标的
- 默认情况下,直接使用传入的纹理坐标。
- 如果
-
震动效果:
- 如果启用了
shake
,则使用cos
函数和时间对gl_Position
的x
和y
分量进行微小的调整,模拟震动效果。
- 如果启用了
片段着色器(.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. 主程序:
-
样本采样:
- 如果启用了
chaos
或shake
,则使用offsets
数组中的偏移量来从纹理中采样9个邻近的像素,存储在sample
数组中。
- 如果启用了
-
效果处理:
- 混乱效果(chaos):
- 对9个邻近的样本进行加权计算,通过
edge_kernel
数组中的值进行卷积操作,将其加到color
上。edge_kernel[i]
是整数,控制每个样本的权重。
- 对9个邻近的样本进行加权计算,通过
- 翻转效果(confuse):
- 将纹理的颜色进行反转,得到与原色相反的颜色。
- 震动效果(shake):
- 使用
blur_kernel
数组对采样的像素进行模糊处理,进行卷积操作,得到震动效果。
- 使用
- 默认效果:
- 如果没有启用任何特殊效果,直接输出纹理颜色。
- 混乱效果(chaos):
原文地址:https://blog.csdn.net/Siro_sama/article/details/145288084
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!