传统渲染采用"提交-绘制"循环模式:每次调用glDrawArrays
或glDrawElements
都会触发完整的渲染管线执行流程。顶点属性数据通过VBO绑定至显存,着色器程序逐顶点处理数据,最终生成图元。
当需要绘制相同物体的多个副本时,传统方案需要:
实例化渲染通过单次API调用完成全部实例绘制,核心改进包括:
使用两种特殊数据结构:
glVertexAttribDivisor
控制属性更新频率GPU着色器通过内置变量gl_InstanceID
区分不同实例,在顶点处理阶段即可访问实例化数据,避免传统方案中频繁的Uniform更新操作。
特性 | 传统渲染 | 实例化渲染 |
---|---|---|
API调用频次 | O(n) | O(1) |
数据更新方式 | 逐帧CPU提交 | 显存预存 |
矩阵计算位置 | CPU端 | GPU着色器 |
内存带宽消耗 | 高 | 极低 |
万级对象绘制性能 | 15-30 FPS | 60+ FPS |
着色器复杂度 | 简单 | 需支持实例ID |
#if defined(_MSC_VER) && (_MSC_VER >= 1600) && !defined(_WIN32_WCE)
#pragma execution_character_set("utf-8")
#endif
// 包含必要的头文件
#include // GLEW库,用于管理OpenGL扩展
#include // GLFW库,用于窗口和输入管理
#include // GLM数学库
#include // GLM矩阵变换函数
#include // 输入输出流
#include // 向量容器
#include // 字符串处理
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // 确保GLM类型的内存对齐
// 窗口尺寸和实例数量常量
const int WIDTH = 1280;
const int HEIGHT = 720;
const int INSTANCE_COUNT = 1000;
// 顶点着色器源代码
const char* vertexShaderSource = R"(
#version 460 core
layout(location = 0) in vec3 aPos; // 顶点位置属性
layout(location = 1) in vec3 aColor; // 顶点颜色属性
layout(location = 2) in mat4 instanceMatrix; // 实例化矩阵属性(占用location 2-5)
uniform mat4 viewProj; // 视图投影矩阵统一变量
out vec3 Color; // 传递给片段着色器的颜色
void main() {
Color = aColor;
// 计算最终位置:视图投影矩阵 * 实例矩阵 * 顶点位置
gl_Position = viewProj * instanceMatrix * vec4(aPos, 1.0);
}
)";
// 片段着色器源代码
const char* fragmentShaderSource = R"(
#version 460 core
in vec3 Color; // 来自顶点着色器的颜色输入
out vec4 FragColor; // 输出颜色
void main() {
FragColor = vec4(Color, 1.0); // 设置不透明度为1
}
)";
// 立方体顶点数据(包含位置和颜色)
const float vertices[] = {
// 位置坐标 (x,y,z) 颜色 (r,g,b)
-0.5f,-0.5f,-0.5f, 1,0,0, // 顶点0
0.5f,-0.5f,-0.5f, 0,1,0, // 顶点1
0.5f, 0.5f,-0.5f, 0,0,1, // 顶点2
-0.5f, 0.5f,-0.5f, 1,1,0, // 顶点3
-0.5f,-0.5f, 0.5f, 1,0,1, // 顶点4
0.5f,-0.5f, 0.5f, 0,1,1, // 顶点5
0.5f, 0.5f, 0.5f, 0.5,0.5,0.5, // 顶点6
-0.5f, 0.5f, 0.5f, 1,1,1 // 顶点7
};
// 立方体索引数据(定义三角形面)
const unsigned int indices[] = {
0,1,2, 2,3,0, // 前面
4,5,6, 6,7,4, // 后面
0,4,7, 7,3,0, // 左面
1,5,6, 6,2,1, // 右面
3,2,6, 6,7,3, // 顶面
0,1,5, 5,4,0 // 底面
};
// 创建着色器程序的函数
GLuint createShaderProgram(const char* vs, const char* fs) {
// 创建并编译顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vs, nullptr);
glCompileShader(vertexShader);
// 检查编译错误
GLint success;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
std::cerr << "顶点着色器编译失败:\n" << infoLog << std::endl;
}
// 创建并编译片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fs, nullptr);
glCompileShader(fragmentShader);
// 检查编译错误
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
std::cerr << "片段着色器编译失败:\n" << infoLog << std::endl;
}
// 创建着色器程序并链接
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
// 检查链接错误
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, 512, nullptr, infoLog);
std::cerr << "程序链接失败:\n" << infoLog << std::endl;
}
// 删除临时着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);