如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
使用 OpenGL 实现简易的星球旋转效果,如图 1。程序执行效果见附带压缩包的 EXE 程序(EXE 文件为 Win 程序,使用 Mac OS 和 Linux 的同学请参照 Win 平台下的执行效果) 。
功能要求
- 使用不同尺寸的线框球体(Wire Sphere)表示大小两个星球
- 使用平移和旋转操作实现小星球自转和绕大星球旋转的功能,键盘事件响应如下: d 和 shift+d: 分别控制小星球正反两个方向的自转; y 和 shift+y: 分别控制小星球绕大星球的正方向和反方向旋转
- 语言不限,开发平台不限。具体效果展示允许略有差异。
- 要求使用 OpenGL 着色器编程方式实现程序。
实现提示
正方向旋转可以通过以下方式求得:d = (d + 10) % 360;反方向则为 d = (d - 10) % 360
Project3
一开始我的开发环境是搭在 Linux 环境下的,但是运行时发生了各种异常,在吴飚、王威等同学讨论之后(感谢他们),重新在 Windows 下使用 VS2019 开发。相关项目目录是/Project3
,打包好的成品是project3.exe
。
运行效果
这里不太会画线框球体,于是用二十个三角形去模拟这个球体。
另外,遇到很迷的问题,恒星没有显示出来…
打开的效果如下。
按了若干下 d 和 y 之后如图,行星转到了上方,并且发生了一定的自转(极点位置变动了)。
原理
二维坐标下绕原点自转
记$\theta$是要旋转的角度,记$dayd$是幅角为$\theta$的单位向量。考虑复数的乘法意义:幅角相加、模长相乘,只需要将原来的点在复数域上与dayd
做一次乘法即可。
$aPos’=(aPos.xdayd.x-aPos.ydayd.y, aPos.ydayd.x+aPos.xdayd.y)$
二维坐标下绕原点公转
公转相对容易,只要算出现在位置和原位置的向量差$yeard$即可。
$aPos’=aPos+yeard$
源代码main.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>
int year = 0, day = 0;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// settings
const unsigned int SCR_WIDTH = 1024;
const unsigned int SCR_HEIGHT = 768;
const char* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"uniform vec2 yeard;\n"
"uniform vec2 dayd;\n"
"void main()\n"
"{\n"
"if(aPos.x*aPos.x+ aPos.y * aPos.y+ aPos.x * aPos.y<0.06)\n"
" gl_Position = vec4(aPos.x*dayd.x-aPos.y*dayd.y+yeard.x, aPos.y*dayd.x+aPos.x*dayd.y+yeard.y, aPos.z, 1.0);\n"
"else\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char* fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//glfw初始化
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//MacOS
//glfw window creation
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "17341163 WuK", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//glad: load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//build and compile 着色器程序
//顶点着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//检查顶点着色器是否编译错误
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
else {
std::cout << "vertexShader complie SUCCESS" << std::endl;
}
//片段着色器
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//检查片段着色器是否编译错误
glGetShaderiv(fragmentShader, GL_LINK_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
else {
std::cout << "fragmentShader complie SUCCESS" << std::endl;
}
//连接到着色器程序
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检查片段着色器是否编译错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
else {
std::cout << "shaderProgram complie SUCCESS" << std::endl;
}
//连接后删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
const double r1 = 0.2,r2=0.3,r3=0.7,pi=acos(-1);
const int num = 9;
float vertices[(2+num)*3*2] = {
0.0f, -0.1f, sqrt(r1*r1-0.01),
0.0f, 0.1f, -sqrt(r1*r1-0.01)
};
unsigned int indices[num * 2 * 3 * 2];
for (int i = 0; i < num; ++i)
{
vertices[i * 3 + 6] = r1*cos(2*pi*i/num);
vertices[i * 3 + 1 + 6] = r1*sin(2 * pi * i / num);
vertices[i * 3 + 2 + 6] = 0;
}
for (int i = 0; i < (2 + num) * 3; ++i)
vertices[i + (2 + num) * 3] = r2 / r1 * vertices[i];
for (int i = 0; i < num; ++i)
{
int j = (i + 1) % num;
indices[2 * i * 3]= indices[2 * i * 3 + 3] = i+2 ;
indices[2 * i * 3 + 1] = indices[2 * i * 3+ 4]= j+2;
indices[2 * i * 3 + 2] = 0 ;
indices[2 * i * 3 + 5] = 1 ;
indices[2 * i * 3+ num * 2 * 3] = indices[2 * i * 3 + 3+ num * 2 * 3] = i + 2+ (2 + num) * 3;
indices[2 * i * 3 + 1+ num * 2 * 3] = indices[2 * i * 3 + 4+ num * 2 * 3] = j + 2+ (2 + num) * 3;
indices[2 * i * 3 + 2+ num * 2 * 3] = 0+ (2 + num) * 3;
indices[2 * i * 3 + 5+ num * 2 * 3] = 1+ (2 + num) * 3;
}
unsigned int VBO;
glGenBuffers(1, &VBO);
unsigned int VAO;
glGenVertexArrays(1, &VAO);
unsigned int EBO;
glGenBuffers(1, &EBO);
//初始化代码(只运行一次 (除非你的物体频繁改变))
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//线框模式wireframe
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 渲染循环
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_INT, 0);
glUseProgram(shaderProgram);//通过glUniform4f函数设置uniform值
glUniform2f(glGetUniformLocation(shaderProgram, "yeard"),r3*cos(2*pi*year/360),r3*sin(2*pi*year/360));
glUniform2f(glGetUniformLocation(shaderProgram, "dayd"), cos(2 * pi * day / 360),sin(2 * pi * day / 360));
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
int dday=0, dyear=0;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
dday = 1;
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS)
dyear = 1;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS||glfwGetKey(window,GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)//是否按下了返回键
dday = 360 - dday, dyear = 360 - dyear;
day = (day + dday) % 360;
year = (year + dyear) % 360;
}
star.c
一开始没有仔细阅读老师要求(「使用着色器编程」),因此先用 OpenGL 的固定管线实现了要求(简单了好多啊)。这一版是在 Linux 下开发的,得到的生成文件是star.out
。后来我在 Windows 下重新编译了这段代码得到了star.exe
。
运行star.exe
结果如下。
/*
gcc star.c -o star.exe -lGL -lGLU -lglut
./star.exe
*/
#include <GL/glut.h>
int year = 0, day = 0;
void display()
{
glClear(GL_COLOR_BUFFER_BIT); //清空颜色缓冲区
glPushMatrix(); //压栈
glColor3f(1, 0.5, 0); //恒星是橙黄色的
glutWireSphere(3, 20, 16); //绘制恒星
glRotatef(year, 0, 1, 0); //沿y轴旋转
glTranslatef(6, 0, 0); //移动画笔,画行星
glRotatef(day, 0, 1, 0); //沿y轴旋转
glColor3f(0, 0.5, 1); //行星是蔚蓝色的
glutWireSphere(1, 15, 12); //绘制行星
glPopMatrix(); //弹栈,恢复绘制坐标
glutSwapBuffers();
glFlush(); //刷新窗口以显示当前绘制图形
}
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h); //设置机口
glMatrixMode(GL_PROJECTION); //指定哪一个矩阵是当前矩阵
glLoadIdentity();
gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 20); //透视投影矩阵(fovy,aspect,zNear,zFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(9, 9, 9, 0, 0, 0, 0, 1, 0); //观察者位置,观察者朝向的位置,观察者头顶位置
}
void keyboard(unsigned char key, int x, int y)
{
if (key == 'd')
day = (day + 10) % 360;
else if (key == 'D') // 大写情况下是逆向的
day = (day + 350) % 360;
else if (key == 'y')
year = (year + 10) % 360;
else if (key == 'Y') // 大写情况下是逆向的
year = (year + 350) % 360;
else
return;
glutPostRedisplay();
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //缓存模式
glutInitWindowSize(600, 600); //显示框的大小
glutInitWindowPosition(100, 100); //确定显示框左上角的位置
glutCreateWindow("17341163_吴坎_CG_HW3");
glClearColor(0, 0, 0, 0); // 初始化
glShadeModel(GL_FLAT); //选择平面明暗模式或光滑明暗模式
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
}