#ifndef DEMO_H_
#define DEMO_H_
#include <glad/glad.h>
class Demo
{
public:
Demo();
virtual ~Demo(); // 必须是虚析构函数!否则 delete 子类会出问题
// 纯虚函数:必须由子类实现
virtual void init() = 0;
virtual void update(float dt) = 0;
virtual void render() = 0; // 只能出现一次
virtual void destroy() = 0;
// 设置渲染参数(可被子类重写)
virtual void setRenderParameter(bool enableCullFace = false,
GLenum cullMode = GL_BACK, // 建议默认剔除背面(GL_BACK),不是 GL_FRONT
GLenum frontFace = GL_CCW);
// 应用当前渲染状态(如面剔除)——这个也可以是普通虚函数或非虚函数
virtual void beginRender();
// // 可选公共方法
// void beginRender();
//private:
// bool enableCullFace; //是否启用面剔除功能(即不渲染背对摄像机的面)
// GLenum cullMode; //指定要剔除的面类型:GL_BACK(背面)、GL_FRONT(前面)或 GL_FRONT_AND_BACK(双面)
// GLenum frontFace; //定义三角形的“正面”方向:GL_CCW(逆时针为正,默认),GL_CW(顺时针为正)
protected:
bool enableCullFace;
GLenum cullMode;
GLenum frontFace;
const char* vertexShaderSource; //顶点着色器的 GLSL 源代码字符串
const char* fragmentShaderSource; //默认片段着色器的 GLSL 源代码字符串
const char* fragmentShader1Source; //可选的第二种片段着色器源码(例如不同光照模型)
const char* fragmentShader2Source; //可选的第三种片段着色器源码(例如边缘检测、颜色变换等)
//public:
// virtual void setRenderParameter(bool enableCullFace = false, GLenum cullMode = GL_FRONT, GLenum frontFace = GL_CCW); //补注释
// virtual void beginRender(); // 开始渲染前调用,用于应用当前设置的渲染状态(如启用/禁用面剔除)
// virtual void render() = 0; // 纯虚函数:子类必须实现具体的渲染逻辑(如绘制几何体、使用着色器等)
};
#endif
#include "Rectangle.h"
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
Rectangle::Rectangle()
{
x = -1.0f;
y = 0.0f;
shader = nullptr;
VAO = 0;
}
Rectangle::~Rectangle()
{
if (shader) delete shader;
glDeleteVertexArrays(1, &VAO);
}
void Rectangle::initData()
{
// 创建着色器(从文件加载)
shader = new Shader("shaders/vertex.vert", "shaders/fragment.frag");
// 创建并配置 VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
unsigned int VBO, EBO;
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 位置属性 (location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
std::cout << "Rectangle initialized with shader and VAO." << std::endl;
}
void Rectangle::renderObject()
{
if (!shader) return;
// 使用着色器
shader->use();
// 动画逻辑:正弦轨迹移动 + Z轴自转
x += 0.005f;
if (x > 1.1f) x = -1.1f;
float angle = x / 2.0f * 6.28f; // 转换为弧度
y = sin(angle);
// 构造 model 矩阵
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(x, y, 0.0f)); // 平移到当前位置
model = glm::rotate(model, angle * 5.0f, glm::vec3(0, 0, 1)); // 自旋(更快一点)
// 获取 uniform 位置并上传
unsigned int modelLoc = glGetUniformLocation(shader->id, "model");
if (modelLoc != -1)
{
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
}
// 绘制
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
#include "Demo3_2.h"
Demo3_2::Demo3_2()
{
//rect.setShaderString(vertexShaderSource, fragmentShader1Source);
//rect.initData();
}
Demo3_2::~Demo3_2()
{
}
void Demo3_2::init()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
rect.initData();
}
void Demo3_2::update(float dt) { }
void Demo3_2::render()
{
glClear(GL_COLOR_BUFFER_BIT);
rect.renderObject();
}
void Demo3_2::destroy() { }
#pragma once
#include "Rectangle.h"
#include "Demo.h"
class Demo3_2:public Demo
{
public:
Demo3_2();
~Demo3_2();
private:
Rectangle rect; //表示一个具体的矩形对象实例
// 注释:该成员变量封装了顶点数据、缓冲区(VBO/VAO)、着色器以及绘制逻辑在 render() 中会被调用来实际绘制矩形
public:
//void render(); //重写基类的纯虚函数,实现本 demo 的具体渲染逻辑
// 注释:在此函数中会调用 beginRender() 设置 OpenGL 状态,然后调用 rect.render() 执行矩形的绘制操作
void init() override;
void update(float dt) override;
void render() override;
void destroy() override;
};
#pragma once
#include <glad/glad.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
class Shader
{
public:
unsigned int id; //着色器程序(Program Object)的唯一标识符(OpenGL ID)
Shader() { id = 0; }
Shader(const char* vs, const char* fs)
{
unsigned int vertex, fragment; //临时变量,分别存储顶点着色器和片段着色器的 OpenGL 对象 ID
// 注释:- vertex: 通过 glCreateShader(GL_VERTEX_SHADER) 创建,- fragment: 通过 glCreateShader(GL_FRAGMENT_SHADER) 创建它们是独立编译的着色器单元,在链接前不会生效
vertex = glCreateShader(GL_VERTEX_SHADER); //创建一个顶点着色器对象,该对象将用于加载、编译顶点着色器源代码
fragment = glCreateShader(GL_FRAGMENT_SHADER); //创建一个片段着色器对象,用于处理像素颜色计算(光栅化后)
id = glCreateProgram(); //创建一个着色器程序对象,所有着色器必须附加到此程序才能链接运行链接成功后,可通过 glUseProgram(id) 启用整个管线
glShaderSource(vertex, 1, &vs,nullptr); //将 C++ 字符串 vs(顶点着色器源码)绑定到 vertex 着色器对象
glCompileShader(vertex); //编译顶点着色器,将之前设置的源码编译成 GPU 可执行的形式,若语法错误则编译失败,需调用 checkCompileErrors 检查
checkCompileErrors(vertex, "VERTEX"); //检查顶点着色器是否编译成功如果失败,打印详细的错误日志到控制台
glShaderSource(fragment, 1, &fs, nullptr); //将 fs 字符串作为源码传入片段着色器对象
glCompileShader(fragment); //编译片段着色器
checkCompileErrors(fragment, "FRAGMENT"); //检查片段着色器编译状态
glAttachShader(id, vertex); //将已编译的顶点着色器附加到程序对象 id 上
glAttachShader(id, fragment); //将已编译的片段着色器附加到程序对象 id 上
glLinkProgram(id); //链接着色器程序把所有附加的着色器合并为一个可执行程序检查接口匹配性(如 vs 输出与 fs 输入)、全局符号等成功后方可使用
glDeleteShader(vertex); //链接完成后,删除临时的顶点着色器对象
glDeleteShader(fragment); // 删除临时的片段着色器对象
}
Shader(const std::string& vertexPath, const std::string& fragmentPath)
{
// 读取着色器源码文件
std::string vertexCode = readFile(vertexPath);
std::string fragmentCode = readFile(fragmentPath);
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
unsigned int vertex, fragment;
vertex = glCreateShader(GL_VERTEX_SHADER);
fragment = glCreateShader(GL_FRAGMENT_SHADER);
id = glCreateProgram();
glShaderSource(vertex, 1, &vShaderCode, nullptr);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
glShaderSource(fragment, 1, &fShaderCode, nullptr);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
glAttachShader(id, vertex);
glAttachShader(id, fragment);
glLinkProgram(id);
checkCompileErrors(id, "PROGRAM");
glDeleteShader(vertex);
glDeleteShader(fragment);
}
void use()
{
glUseProgram(id); //激活当前着色器程序使 OpenGL 渲染管线使用这个 program 进行绘制后续 draw call 都会使用该着色器逻辑
}
private:
std::string readFile(const std::string& path)
{
std::ifstream file;
std::stringstream buffer;
// 尝试打开文件
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
file.open(path);
buffer << file.rdbuf(); // 读取全部内容
file.close();
return buffer.str();
}
catch (const std::exception& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_READ: " << e.what() << std::endl;
return "";
}
}
void checkCompileErrors(GLuint shader, std::string type) //用于检查着色器编译或程序链接是否出错
{
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
};
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Lab3_2.h"
#include "Demo3_2.h"
#include "Demo3_3.h"
#include"Hexagon.h"
#include"ColorTriangle.h"
#include"ColorHexagon.h"
#include "WaveHexagon.h"
using namespace std;
// settings
const unsigned int SCR_WIDTH = 1200;
const unsigned int SCR_HEIGHT = 800;
GLFWwindow* window;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void processInput(GLFWwindow* window);
void render();
void init();
void cleanup();
Lab3_2* lab3_2 = nullptr; //指向 Lab3_2 实例的指针,用于管理第一个实验场景(例如带面剔除功能的三角形渲染),初始化为空,防止野指针
Demo3_2* demo3_2 = nullptr;
Demo3_3* demo3_3 = nullptr;
Hexagon* hexagon = nullptr;
ColorTriangle* colorTriangle = nullptr;
ColorHexagon* colorHexagon = nullptr;
WaveHexagon* waveHexagon = nullptr;
int sceneType = 1; // 当前激活的场景编号:1 -> Lab3_2, 2 -> Demo3_2, 3 -> Demo3_3,可通过键盘按键切换不同场景
bool enableCullFace = false; // 是否启用面剔除(Face Culling)的状态标志,控制是否调用 glEnable(GL_CULL_FACE)
GLenum cullMode = GL_FRONT; //当前面剔除模式:默认剔除正面(GL_FRONT)可选值:GL_BACK(剔除背面)、GL_FRONT_AND_BACK(都剔除)
GLenum frontFace = GL_CCW; //正面判定方式:逆时针方向为正面(Counter-Clockwise),对应 glFrontFace 的参数,影响哪些面被认为是“正面”
int main()
{
//初始化glfw窗口
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);//上下文版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建窗口对象
window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Demo3", NULL, NULL);
if (window == NULL) //窗口创建失败提示
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); // 将 OpenGL 上下文设置为当前线程的主上下文,所有 OpenGL 调用都将作用于该窗口
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //注册帧缓冲大小变化的回调函数.当窗口被缩放或最大化时,会自动调整视口大小
glfwSetKeyCallback(window, key_callback); // 注册键盘按键事件的回调函数,每当有键按下/释放时触发 key_callback初始化glad
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))//glad初始化检查
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes); //查询 OpenGL 支持的最大顶点属性数量
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
init();
// 窗口消息循环
while (!glfwWindowShouldClose(window))
{
processInput(window); // 输入
render(); // 渲染
glfwSwapBuffers(window); //交换前后缓冲区,实现双缓冲机制,防止画面撕裂
glfwPollEvents();
}
cleanup(); //释放内存
glfwTerminate(); //终止 GLFW,清理所有相关资源
return 0;
}
void cleanup()
{
if (lab3_2)
{
delete lab3_2;
}
if (demo3_2)
{
delete demo3_2;
}
if (demo3_3)
{
delete demo3_3;
}
if (hexagon)
{
delete hexagon;
}
if (colorTriangle)
{
delete colorTriangle;
}
if (colorHexagon)
{
delete colorHexagon;
}
if (waveHexagon)
{
delete waveHexagon;
waveHexagon = nullptr;
}
}
void init()
{
lab3_2 = new Lab3_2();
lab3_2->setRenderParameter(); //设置其初始渲染参数(如面剔除状态)
demo3_2 = new Demo3_2();
demo3_2->setRenderParameter();
demo3_3 = new Demo3_3();
demo3_3->setRenderParameter();
hexagon = new Hexagon();
hexagon->initData(); // 初始化六边形,显式初始化六边形的顶点缓冲区和 VAO/VBO。
colorTriangle = new ColorTriangle();
colorHexagon = new ColorHexagon();
colorHexagon->initData();
// 在 init() 或主函数开始处
waveHexagon = new WaveHexagon();
waveHexagon->init();
}
void render()
{
glClearColor(0.2, 0.3, 0.2, 1); //设置清除颜色为深绿色
glClear(GL_COLOR_BUFFER_BIT); //清除颜色缓冲区,准备新一帧绘制
if (sceneType == 1 && lab3_2) //判断当前应激活的场景是否为 Lab3_2 场景,并确保该场景对象已成功创建
{
lab3_2->render();
}
else if (sceneType == 2 && demo3_2)
{
demo3_2->render();
}
else if (sceneType == 3 && demo3_3)
{
demo3_3->render();
}
else if (sceneType == 4 && hexagon)
{
hexagon->renderObject(); // 渲染六边形
}
else if (sceneType == 5 && colorTriangle)
{
colorTriangle->render();
}
else if (sceneType == 6 && colorHexagon)
{
colorHexagon->renderObject();
}
else if (sceneType == 7 && waveHexagon) {
waveHexagon->render();
}
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
// 判断按键是否被释放
if (action == GLFW_RELEASE) //GLFW_RELEASE与GLFW_PRESS的区别
{
if (key == GLFW_KEY_1)//释放的是数字键 1,则将当前场景切换为 Lab3_2(编号为 1)
sceneType = 1;
else if (key == GLFW_KEY_2)
sceneType = 2;
else if (key == GLFW_KEY_3)
sceneType = 3;
else if (key == GLFW_KEY_4)
sceneType = 4;
else if (key == GLFW_KEY_5)
sceneType = 5;
else if (key == GLFW_KEY_6)
sceneType = 6;
else if (key == GLFW_KEY_7)
sceneType = 7;
else if (key == GLFW_KEY_E)
{
enableCullFace = !enableCullFace; //切换面剔除开关状态
if (sceneType == 1 && lab3_2)
{
lab3_2->setRenderParameter(enableCullFace, cullMode, frontFace);
}
}
else if (key == GLFW_KEY_C) //按C键切换绕向
{
if (frontFace == GL_CCW) //如果当前为绕向为逆时针
{
frontFace=GL_CW; //切换为顺时针
}
else
{
frontFace = GL_CCW;
}
if (sceneType == 1 && lab3_2)
{
lab3_2->setRenderParameter(enableCullFace, cullMode, frontFace);
}
}
else if (key == GLFW_KEY_F) //按键F切换裁剪面
{
if (cullMode == GL_FRONT) //如果当前正面为裁剪面
{
cullMode = GL_BACK; //切换成背面
}
else
{
cullMode = GL_FRONT;
}
if (sceneType == 1 && lab3_2)
{
lab3_2->setRenderParameter(enableCullFace, cullMode, frontFace);
}
}
}
}
void processInput(GLFWwindow* window) //处理每帧的输入(主要用于连续按键检测)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// 窗口回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height); //设置 OpenGL 视口与窗口一致,参数:x, y, 宽度, 高度
}
修改demo3框架中的shader类,添加读取外部着色器文件的功能。原Demo3_2.cpp实现了在屏幕上按照正弦轨迹进行移动,从屏幕坐标移动到右边,超出屏幕区域后,再回到屏幕左端。请增加其运动轨迹同时在屏幕上绕着z轴线自转。
最新发布