OpenGL 蓝书学习(第四版)第九章
OpenGL 蓝书学习(第四版)第九章
纹理贴图高级部分
各向异性过滤
#include <OpenGL/glext.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#include <array>
#include <iosfwd>
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <format>
#include <numbers>
#include <fstream>
#include <vector>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLuint NUM_TEXTURES = 3;
static constexpr std::array<std::string, NUM_TEXTURES> textureFiles = {
"../brick.tga", // 0 砖块
"../floor.tga", // 1 地板
"../ceiling.tga" // 2 天花板
};
// 变量
static GLfloat zPos = -60.0F; // z位置
static int mainWindowId; // 主窗口 ID
static GLuint textureIds[NUM_TEXTURES];
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void changeSize(const int w, int h) {
if (h == 0) h = 1; // Prevent division by zero
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, static_cast<double>(w) / static_cast<double>(h), 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void normalize(GLfloat v[3]) {
const GLfloat len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len > 0.0F) {
v[0] /= len;
v[1] /= len;
v[2] /= len;
}
}
GLfloat toonShade(const GLfloat n[3], const GLfloat m[16], const GLfloat light[3]) {
GLfloat e[3] = {
m[0] * n[0] + m[4] * n[1] + m[8] * n[2],
m[1] * n[0] + m[5] * n[1] + m[9] * n[2],
m[2] * n[0] + m[6] * n[1] + m[10] * n[2],
};
normalize(e);
return light[0] * e[0] + light[1] * e[1] + light[2] * e[2];
}
void renderScene() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef(0.0F, 0.0F, zPos);
for(GLfloat z = 60.0F; z >= 0.0F; z -= 10.0F){
// 地板
glBindTexture(GL_TEXTURE_2D, textureIds[1]); // 绑定第一个纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0F, 0.0F);
glVertex3f(-10.0F, -10.0F, z);
glTexCoord2f(1.0F, 0.0F);
glVertex3f( 10.0F, -10.0F, z);
glTexCoord2f(1.0F, 1.0F);
glVertex3f( 10.0F, -10.0F, z - 10.0F);
glTexCoord2f(0.0F, 1.0F);
glVertex3f(-10.0F, -10.0F, z - 10.0F);
glEnd();
// 天花板
glBindTexture(GL_TEXTURE_2D, textureIds[2]); // 绑定天花板纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0F, 1.0F);
glVertex3f(-10.0F, 10.0F, z - 10.0F);
glTexCoord2f(1.0F, 1.0F);
glVertex3f( 10.0F, 10.0F, z - 10.0F);
glTexCoord2f(1.0F, 0.0F);
glVertex3f( 10.0F, 10.0F, z);
glTexCoord2f(0.0F, 0.0F);
glVertex3f(-10.0F, 10.0F, z);
glEnd();
// 左
glBindTexture(GL_TEXTURE_2D, textureIds[0]); // 绑定左墙纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0F, 0.0F);
glVertex3f(-10.0F, -10.0F, z);
glTexCoord2f(1.0F, 0.0F);
glVertex3f(-10.0F, -10.0F, z - 10.0F);
glTexCoord2f(1.0F, 1.0F);
glVertex3f(-10.0F, 10.0F, z - 10.0F);
glTexCoord2f(0.0F, 1.0F);
glVertex3f(-10.0F, 10.0F, z);
glEnd();
// 右
glBindTexture(GL_TEXTURE_2D, textureIds[0]); // 绑定右墙纹理
glBegin(GL_QUADS);
glTexCoord2f(0.0F, 1.0F);
glVertex3f(10.0F, 10.0F, z);
glTexCoord2f(1.0F, 1.0F);
glVertex3f(10.0F, 10.0F, z - 10.0F);
glTexCoord2f(1.0F, 0.0F);
glVertex3f(10.0F, -10.0F, z - 10.0F);
glTexCoord2f(0.0F, 0.0F);
glVertex3f(10.0F, -10.0F, z);
glEnd();
}
glPopMatrix();
glutSwapBuffers();
}
void setupRc() {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 开启2d纹理
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenTextures(NUM_TEXTURES, textureIds);
for (GLuint i = 0; i < NUM_TEXTURES; ++i) {
glBindTexture(GL_TEXTURE_2D, textureIds[i]);
// 加载纹理数据
std::ifstream file(textureFiles[i], std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("Failed to open " + textureFiles[i]);
}
TargaHeader header;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if(file.fail()) {
throw std::runtime_error("Failed to read Targa header from " + textureFiles[i]);
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(file.fail()) {
throw std::runtime_error("Failed to read Targa image data from " + textureFiles[i]);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, header.width, header.height, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
}
}
void shutdownRc() {
glDeleteTextures(NUM_TEXTURES, textureIds);
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
std::exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
// glutPostRedisplay();
break;
case 'w':
case 'W':
break;
case 's':
case 'S':
break;
case 'z':
case 'Z':
break;
case 'x':
case 'X':
break;
case 'c':
case 'C':
break;
case 'v':
case 'V':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y) {
std::cout << std::format("specialKeys: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case GLUT_KEY_UP:
zPos += 1.0f;
break;
case GLUT_KEY_DOWN:
zPos -= 1.0f;
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
break;
}
// Refresh the Window
glutPostRedisplay();
}
void processMenu(const int value) {
std::cout << std::format("Menu selected: {}", value) << "\n";
for(GLuint i = 0; i < NUM_TEXTURES; ++i) {
glBindTexture(GL_TEXTURE_2D, textureIds[i]);
switch (value) {
case 0:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
break;
case 1:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
break;
case 2:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
break;
case 3:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
break;
case 4:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
break;
case 5:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
break;
}
}
glutSetWindow(mainWindowId);
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
std::cout << "Hello, World!" << std::endl;
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL");
if (!mainWindowId) {
throw std::runtime_error("Failed to create OpenGL window");
}
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys); // 特殊键事件
glutKeyboardFunc(keyboard); // 普通键事件
glutCreateMenu(processMenu);
glutAddMenuEntry("GL_NEAREST",0);
glutAddMenuEntry("GL_LINEAR",1);
glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST",2);
glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
if (glutExtensionSupported("GL_EXT_texture_filter_anisotropic")) {
GLfloat maxAnisotropy = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
std::cout << "Anisotropic filtering supported, max: " << maxAnisotropy << std::endl;
} else {
std::cout << "Anisotropic filtering not supported" << std::endl;
}
glutMainLoop();
return 0;
}
物体线性贴图、视觉线性贴图
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#include <array>
#include <iosfwd>
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <format>
#include <numbers>
#include <fstream>
#include <vector>
#include <filesystem>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLuint NUM_TEXTURES = 2;
static const std::array<std::string, NUM_TEXTURES> textureFiles = {
"../image/stripes.tga", // 0 砖块
"../image/environment.tga", // 1 地板
};
// 变量
static GLfloat xRot = 0.0F; // z位置
static GLfloat yRot = 0.0F; // z位置
static GLfloat zRot = 0.0F; // z位置
static GLfloat zPos = -2.0F; // z位置
static int mainWindowId; // 主窗口 ID
static GLuint textureIds[NUM_TEXTURES];
static GLuint renderMode = 3;
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void changeSize(const int w, int h) {
if (h == 0) h = 1; // Prevent division by zero
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, static_cast<double>(w) / static_cast<double>(h), 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void normalize(GLfloat v[3]) {
const GLfloat len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len > 0.0F) {
v[0] /= len;
v[1] /= len;
v[2] /= len;
}
}
GLfloat toonShade(const GLfloat n[3], const GLfloat m[16], const GLfloat light[3]) {
GLfloat e[3] = {
m[0] * n[0] + m[4] * n[1] + m[8] * n[2],
m[1] * n[0] + m[5] * n[1] + m[9] * n[2],
m[2] * n[0] + m[6] * n[1] + m[10] * n[2],
};
normalize(e);
return light[0] * e[0] + light[1] * e[1] + light[2] * e[2];
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
for (GLint i = 0; i <= stacks; ++i) {
const GLfloat stackAngle1 = i * stackStep;
const GLfloat stackAngle2 = (i + 1) * stackStep;
glBegin(GL_TRIANGLE_STRIP);
for (GLint j = 0; j <= slices; ++j) {
const GLfloat sliceAngle1 = j * sliceStep;
const GLfloat sliceAngle2 = (j + 1) * sliceStep;
const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
const GLfloat ny1 = std::cos(stackAngle1);
const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
const GLfloat x1 = radius * nx1;
const GLfloat y1 = radius * ny1;
const GLfloat z1 = radius * nz1;
const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
const GLfloat ny2 = std::cos(stackAngle2);
const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
const GLfloat x2 = radius * nx2;
const GLfloat y2 = radius * ny2;
const GLfloat z2 = radius * nz2;
glNormal3f(nx1, ny1, nz1);
glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
1.0F - static_cast<GLfloat>(i) / stacks);
glVertex3f(x1, y1, z1);
glNormal3f(nx2, ny2, nz2);
glTexCoord2f(static_cast<GLfloat>(j) / slices,
1.0F - static_cast<GLfloat>(i + 1) / stacks);
glVertex3f(x2, y2, z2);
}
glEnd();
}
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
for (int i = 0; i < numMajor; ++i) {
const GLfloat u0 = i * majorStep;
const GLfloat u1 = u0 + majorStep;
glBegin(GL_TRIANGLE_STRIP);
for (int j = 0; j <= numMinor; ++j) {
const GLfloat v0 = j * minorStep;
// 计算两个顶点的坐标
const GLfloat n0[3] = {
std::cos(v0) * std::cos(u0),
std::cos(v0) * std::sin(u0),
std::sin(v0)
};
const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
const GLfloat z00 = minorRadius * std::sin(v0);
const GLfloat n1[3] = {
std::cos(v0) * std::cos(u1),
std::cos(v0) * std::sin(u1),
std::sin(v0)
};
const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
const GLfloat z01 = minorRadius * std::sin(v0);
glNormal3f(n0[0], n0[1], n0[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor);
glVertex3f(x00, y00, z00);
glNormal3f(n1[0], n1[1], n1[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);
glVertex3f(x01, y01, z01);
}
glEnd();
}
}
void renderScene() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glBindTexture(GL_TEXTURE_2D, textureIds[1]);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDepthMask(GL_FALSE);
glBegin(GL_QUADS);
glTexCoord2f(0.0F, 0.0F);
glVertex2f(0.0F, 0.0F);
glTexCoord2f(1.0F, 0.0F);
glVertex2f(1.0F, 0.0F);
glTexCoord2f(1.0F, 1.0F);
glVertex2f(1.0F, 1.0F);
glTexCoord2f(0.0F, 1.0F);
glVertex2f(0.0F, 1.0F);
glEnd();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glDepthMask(GL_TRUE);
if (renderMode != 3) {
glBindTexture(GL_TEXTURE_2D, textureIds[0]);
}
glPushMatrix();
glTranslatef(0.0F, 0.0F, zPos);
glRotatef(xRot, 1.0F, 0.0F, 0.0F);
glRotatef(yRot, 0.0F, 1.0F, 0.0F);
glRotatef(zRot, 0.0F, 0.0F, 1.0F);
drawTorus(0.35F, 0.15F, 32, 16);
glPopMatrix();
glutSwapBuffers();
}
void setupRc() {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
// 开启2d纹理
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenTextures(NUM_TEXTURES, textureIds);
for (GLuint i = 0; i < NUM_TEXTURES; ++i) {
glBindTexture(GL_TEXTURE_2D, textureIds[i]);
// 加载纹理数据
std::ifstream file(textureFiles[i], std::ios::binary);
std::cout << "current path: " << std::filesystem::current_path() << ", texture file: " << textureFiles[i] << std::endl;
if (!file.is_open()) {
throw std::runtime_error("Failed to open " + textureFiles[i]);
}
TargaHeader header;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if(file.fail()) {
throw std::runtime_error("Failed to read Targa header from " + textureFiles[i]);
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(file.fail()) {
throw std::runtime_error("Failed to read Targa image data from " + textureFiles[i]);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, header.width, header.height, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8,
header.width,
header.height,
0,
GL_BGR,
GL_UNSIGNED_BYTE,
imageData.data()
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
}
void shutdownRc() {
glDeleteTextures(NUM_TEXTURES, textureIds);
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
std::exit(0);
break;
case 'a':
case 'A':
yRot -= 5.0F;
break;
case 'd':
case 'D':
yRot += 5.0F;
break;
case 'w':
case 'W':
xRot -= 5.0F;
break;
case 's':
case 'S':
xRot += 5.0F;
break;
case 'z':
case 'Z':
zRot -= 5.0F;
break;
case 'x':
case 'X':
zRot += 5.0F;
break;
case 'c':
case 'C':
break;
case 'v':
case 'V':
break;
default:
break;
}
glutPostRedisplay();
}
void specialKeys(const int key, const int x, const int y) {
std::cout << std::format("specialKeys: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case GLUT_KEY_UP:
zPos += 1.0f;
break;
case GLUT_KEY_DOWN:
zPos -= 1.0f;
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
break;
}
// Refresh the Window
glutPostRedisplay();
}
void processMenu(const int value) {
std::cout << std::format("processMenu: value={}", value) << "\n";
renderMode = value;
GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f};
switch (value) {
case 1:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane);
glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane);
std::cout << "Object Linear mode selected" << "\n";
break;
case 2:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, zPlane);
glTexGenfv(GL_T, GL_EYE_PLANE, zPlane);
std::cout << "Eye Linear mode selected" << "\n";
break;
case 3:
default:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
std::cout << "Sphere Map mode selected" << "\n";
break;
}
glutSetWindow(mainWindowId);
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
std::cout << "Hello, World!" << std::endl;
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL");
if (!mainWindowId) {
throw std::runtime_error("Failed to create OpenGL window");
}
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys); // 特殊键事件
glutKeyboardFunc(keyboard); // 普通键事件
glutCreateMenu(processMenu);
glutAddMenuEntry("Object Linear",1);
glutAddMenuEntry("Eye Linear",2);
glutAddMenuEntry("Sphere Map",3);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
glutMainLoop();
return 0;
}
立方体贴图
#include <OpenGL/gl.h>
#include <cstddef>
#include <cmath>
#include <fstream>
#include <vector>
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <random>
#include <fstream>
#include <vector>
#include <numbers>
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;
static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
"../image/pos_x.tga",
"../image/neg_x.tga",
"../image/pos_y.tga",
"../image/neg_y.tga",
"../image/pos_z.tga",
"../image/neg_z.tga"
};
static constexpr GLint CUBE[CUBE_FACES_COUNT] = {
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
static constexpr GLint GROUND_TEXTURE = 0;
static constexpr GLint SPHERE_TEXTURE = 1;
static constexpr GLint CUBE_TEXTURE = 2;
static constexpr GLint NUM_TEXTURES = 3;
static constexpr std::array<std::string, NUM_TEXTURES> TEXTURE_FILES = {
"../image/grass.tga",
"../image/orb.tga",
"../image/down.tga"
};
static constexpr GLint NUM_SPHERES = 30;
static constexpr GLfloat fLightPos[] = { -100.0f, 100.0f, 50.0f, 1.0f };
struct GLFrame {
GLfloat vLocation[3]; // 位置 -> move x, y, z
GLfloat vUp[3]; // 上方向 -> y
GLfloat vForward[3]; // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};
static constexpr GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
static constexpr GLfloat fNoLight[] = { 0.0f, 0.0f, 0.0f, 0.0f };
static GLFrame frameCamera;
static GLFrame spheres[NUM_SPHERES];
static GLuint textures[NUM_TEXTURES];
static GLfloat yRot = 0.0f;
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void gltInitFrame(GLFrame *pFrame) {
pFrame->vLocation[0] = 0.0f;
pFrame->vLocation[1] = 0.0f;
pFrame->vLocation[2] = 0.0f;
pFrame->vUp[0] = 0.0f;
pFrame->vUp[1] = 1.0f;
pFrame->vUp[2] = 0.0f;
pFrame->vForward[0] = 0.0f;
pFrame->vForward[1] = 0.0f;
pFrame->vForward[2] = -1.0f; // 为什么是-1?
}
void setupRc(){
// Setup rendering context
constexpr GLfloat fLowLight[] = {0.25f, 0.25f, 0.25f, 1.0f};
glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]);
glCullFace(GL_BACK); // 指定要剔除的面为背面
glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
glEnable(GL_CULL_FACE); // 启用面剔除
glEnable(GL_DEPTH_TEST); // 启用深度测试
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight); // 设置全局环境光
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); // 设置光照模型为独立的镜面反射颜色
glLightfv(GL_LIGHT0, GL_AMBIENT, fLowLight); // 设置光源的环境光为low
glLightfv(GL_LIGHT0, GL_DIFFUSE, fBrightLight); // 设置光源的漫反射光为亮光
glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); // 设置光源的镜面反射光为亮光
glEnable(GL_LIGHTING); // 启用光照
glEnable(GL_LIGHT0); // 启用光源0
glEnable(GL_COLOR_MATERIAL); // 启用颜色材质,使得物体颜色影响光照计算
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // 设置颜色材质的属性为前面,并影响环境光和漫反射光
glMateriali(GL_FRONT, GL_SHININESS, 128); // 设置前面材质的高光指数
// 初始化相机和球体的帧结构体
gltInitFrame(&frameCamera);
std::random_device rd; // 硬件熵源,用于播种
std::mt19937_64 gen(rd()); // Mersenne Twister 引擎
std::uniform_real_distribution<GLfloat> dis(-20.0f, 20.0f);
for(int i = 0; i < NUM_SPHERES; ++i){
gltInitFrame(&spheres[i]);
spheres[i].vLocation[0] = dis(gen); // [-20, 20]
spheres[i].vLocation[1] = 0.0f;
spheres[i].vLocation[2] = dis(gen); // [-20, 20]
}
glEnable(GL_TEXTURE_2D);
glGenTextures(NUM_TEXTURES, textures);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
for(int i = 0; i < NUM_TEXTURES - 1; i ++ ){
std::ifstream file(TEXTURE_FILES[i], std::ios::binary);
if(!file){
throw std::runtime_error("Failed to open texture file: " + std::string(TEXTURE_FILES[i]));
}
TargaHeader targaHeader;
file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
if(!file){
throw std::runtime_error("Failed to read Targa header from file: " + std::string(TEXTURE_FILES[i]));
}
std::cout << "tga file: " << TEXTURE_FILES[i] << ", header: " << targaHeader << std::endl;
const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(!file){
throw std::runtime_error("Failed to read image data from file: " + std::string(TEXTURE_FILES[i]));
}
glBindTexture(GL_TEXTURE_2D, textures[i]);
// glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, textures[NUM_TEXTURES - 1]);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
for(int i = 0; i < CUBE_FACES_COUNT; i++){
std::ifstream file(CUBE_FACES[i], std::ios::binary);
if(!file){
throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
}
TargaHeader targaHeader;
file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
if(!file){
throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
}
std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(!file){
throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
}
glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
}
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
}
void shutdownRc(){
// Cleanup rendering context
glDeleteTextures(NUM_TEXTURES, textures);
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
for (GLint i = 0; i < stacks; ++i) {
const GLfloat stackAngle1 = i * stackStep;
const GLfloat stackAngle2 = (i + 1) * stackStep;
glBegin(GL_TRIANGLE_STRIP);
for (GLint j = 0; j <= slices; ++j) {
const GLfloat sliceAngle1 = j * sliceStep;
const GLfloat sliceAngle2 = (j + 1) * sliceStep;
const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
const GLfloat ny1 = std::cos(stackAngle1);
const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
const GLfloat x1 = radius * nx1;
const GLfloat y1 = radius * ny1;
const GLfloat z1 = radius * nz1;
const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
const GLfloat ny2 = std::cos(stackAngle2);
const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
const GLfloat x2 = radius * nx2;
const GLfloat y2 = radius * ny2;
const GLfloat z2 = radius * nz2;
glNormal3f(nx1, ny1, nz1);
glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
1.0F - static_cast<GLfloat>(i) / stacks);
glVertex3f(x1, y1, z1);
glNormal3f(nx2, ny2, nz2);
glTexCoord2f(static_cast<GLfloat>(j) / slices,
1.0F - static_cast<GLfloat>(i + 1) / stacks);
glVertex3f(x2, y2, z2);
}
glEnd();
}
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
for (int i = 0; i < numMajor; ++i) {
const GLfloat u0 = i * majorStep;
const GLfloat u1 = u0 + majorStep;
glBegin(GL_TRIANGLE_STRIP);
for (int j = 0; j <= numMinor; ++j) {
const GLfloat v0 = j * minorStep;
// 计算两个顶点的坐标
const GLfloat n0[3] = {
std::cos(v0) * std::cos(u0),
std::cos(v0) * std::sin(u0),
std::sin(v0)
};
const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
const GLfloat z00 = minorRadius * std::sin(v0);
const GLfloat n1[3] = {
std::cos(v0) * std::cos(u1),
std::cos(v0) * std::sin(u1),
std::sin(v0)
};
const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
const GLfloat z01 = minorRadius * std::sin(v0);
glNormal3f(n0[0], n0[1], n0[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor);
glVertex3f(x00, y00, z00);
glNormal3f(n1[0], n1[1], n1[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);
glVertex3f(x01, y01, z01);
}
glEnd();
}
}
/**
* 视图矩阵 = 上面的逆:旋转转置 + 平移取负
* 把世界挪到相机眼前
*/
void gltApplyCameraTransform(const GLFrame *pCamera) {
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// 构建视图矩阵 (View matrix) 列主序 (column-major order)
GLfloat m[16]; // 同样的轴,改填「行」,即转置
m[0]=r[0]; m[4]=r[1]; m[8] =r[2]; m[12]=0.0F;
m[1]=pCamera->vUp[0]; m[5]=pCamera->vUp[1]; m[9] =pCamera->vUp[2]; m[13]=0.0F;
m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}
/**
* 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
* 四列依次是: right、up、-forward、location
* 物体→世界
*/
void gltApplyActorTransform(const GLFrame *frame){
// 算出物体的右向量 (Right vector)
GLfloat r[3];
r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];
// 构建物体矩阵 (Model matrix) 列主序 (column-major order)
// 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
// 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
GLfloat m[16];
m[0]=r[0]; m[4]=frame->vUp[0]; m[8] =-frame->vForward[0]; m[12]=frame->vLocation[0];
m[1]=r[1]; m[5]=frame->vUp[1]; m[9] =-frame->vForward[1]; m[13]=frame->vLocation[1];
m[2]=r[2]; m[6]=frame->vUp[2]; m[10]=-frame->vForward[2]; m[14]=frame->vLocation[2];
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
}
void drawGround(){
constexpr GLfloat fExtent = 20.0F;
constexpr GLfloat fStep = 1.0F;
constexpr GLfloat y = -0.4F;
glBindTexture(GL_TEXTURE_2D, textures[GROUND_TEXTURE]); // 草地
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
for (GLfloat i = -fExtent; i <= fExtent; i += fStep) {
glBegin(GL_TRIANGLE_STRIP);
for (GLfloat j = fExtent; j >= -fExtent; j -= fStep) {
glTexCoord2f((i + fExtent) / fExtent * 2.0F, (j - fExtent) / -fExtent * 2.0F);
glNormal3f(0.0F,1.0F,0.0F);
glVertex3f(i, y, j);
glTexCoord2f((i + fExtent + fStep) / fExtent * 2.0F, (j - fExtent) / -fExtent * 2.0F);
glNormal3f(0.0F,1.0F,0.0F);
glVertex3f(i + fStep, y, j);
}
glEnd();
}
}
void drawInhabitants(){
yRot += 0.5F;
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBindTexture(GL_TEXTURE_2D, textures[SPHERE_TEXTURE]); // 假设有一个居民纹理
for(int i = 0; i < NUM_SPHERES; i++){
glPushMatrix();
gltApplyActorTransform(&spheres[i]);
drawSphere(0.3f, 21, 11);
glPopMatrix();
}
glPushMatrix();
glTranslatef(0.0f, 0.1f, -2.5f);
glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight); // 设置前面材质的镜面反射光为亮光
glRotatef(yRot, 0.0f, 1.0f, 0.0f);
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_CUBE_MAP);
glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_TEXTURE]); // 假设有一个立方体贴图纹理
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
// Draw the cube-mapped object here
drawTorus(0.35, 0.15, 61, 37);
// After drawing, disable the texture generation and cube map
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
glDisable(GL_TEXTURE_CUBE_MAP);
glEnable(GL_TEXTURE_2D);
glMaterialfv(GL_FRONT, GL_SPECULAR, fNoLight);
glPopMatrix();
}
void renderScene(){
// Rendering code goes here
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
glLightfv(GL_LIGHT0, GL_POSITION, fLightPos);
glColor3f(1.0f, 1.0f, 1.0f);
drawGround();
drawInhabitants();
glPopMatrix();
glutSwapBuffers();
}
void changeSize(const int w, int h){
if(h == 0) h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
frame->vLocation[0] += frame->vForward[0] * distance;
frame->vLocation[1] += frame->vForward[1] * distance;
frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
* 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
* @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
*
* 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
* v' = v·cosθ + (axis × v)·sinθ
* 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
* 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
*/
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
// axis × forward —— 注意这是 -right(right = forward × up),
// 这个负号决定了旋转方向,别误当成右向量
const GLfloat vAxisCross[3] = {
frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
};
// 角度转弧度
const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
const GLfloat fCos = std::cos(rad);
const GLfloat fSin = std::sin(rad);
// vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];
// 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
// forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
frame->vForward[1] * frame->vForward[1] +
frame->vForward[2] * frame->vForward[2]);
frame->vForward[0] /= len;
frame->vForward[1] /= len;
frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
switch(key){
case GLUT_KEY_UP:
gltMoveFrameForward(&frameCamera, 0.1f);
break;
case GLUT_KEY_DOWN:
gltMoveFrameForward(&frameCamera, -0.1f);
break;
case GLUT_KEY_LEFT:
gltRotateFrameY(&frameCamera, 0.1f);
break;
case GLUT_KEY_RIGHT:
gltRotateFrameY(&frameCamera, -0.1f);
break;
}
glutPostRedisplay();
}
void timerFunc(const int _){
glutPostRedisplay();
glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}
int main(int args, char** argv){
std::cout << "Starting program..." << std::endl;
glutInit(&args, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
const int mainWindowId = glutCreateWindow("OpenGL Window");
if(mainWindowId == 0){
throw std::runtime_error("Failed to create main window");
}
// ======= 事件处理 =======
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys);
glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
// ======= 渲染循环 =======
setupRc();
glutMainLoop();
shutdownRc();
return 0;
}
天空盒
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <random>
#include <fstream>
#include <vector>
#include <numbers>
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;
static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
"../image/pos_x.tga",
"../image/neg_x.tga",
"../image/pos_y.tga",
"../image/neg_y.tga",
"../image/pos_z.tga",
"../image/neg_z.tga"
};
static constexpr GLint CUBE[CUBE_FACES_COUNT] = {
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
struct GLFrame {
GLfloat vLocation[3]; // 位置 -> move x, y, z
GLfloat vUp[3]; // 上方向 -> y
GLfloat vForward[3]; // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};
static GLFrame frameCamera;
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void gltInitFrame(GLFrame *pFrame) {
pFrame->vLocation[0] = 0.0f;
pFrame->vLocation[1] = 0.0f;
pFrame->vLocation[2] = 0.0f;
pFrame->vUp[0] = 0.0f;
pFrame->vUp[1] = 1.0f;
pFrame->vUp[2] = 0.0f;
pFrame->vForward[0] = 0.0f;
pFrame->vForward[1] = 0.0f;
pFrame->vForward[2] = -1.0f; // 为什么是-1?
}
void setupRc(){
gltInitFrame(&frameCamera);
glCullFace(GL_BACK); // 指定要剔除的面为背面
glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
glEnable(GL_CULL_FACE); // 启用面剔除
glEnable(GL_DEPTH_TEST); // 启用深度测试
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
for(int i = 0; i < CUBE_FACES_COUNT; i++){
std::ifstream file(CUBE_FACES[i], std::ios::binary);
if(!file){
throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
}
TargaHeader targaHeader;
file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
if(!file){
throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
}
std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(!file){
throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
}
glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
}
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glEnable(GL_TEXTURE_CUBE_MAP);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
}
void shutdownRc(){
}
void drawSkyBox(){
constexpr GLfloat SIZE = 15.0F; // Skybox size
glBegin(GL_QUADS);
// neg x
glTexCoord3f(-1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glTexCoord3f(-1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glTexCoord3f(-1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glTexCoord3f(-1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
// pos x
glTexCoord3f(1.0F, -1.0F, -1.0F);
glVertex3f(SIZE, -SIZE, -SIZE);
glTexCoord3f(1.0F, -1.0F, 1.0F);
glVertex3f(SIZE, -SIZE, SIZE);
glTexCoord3f(1.0F, 1.0F, 1.0F);
glVertex3f(SIZE, SIZE, SIZE);
glTexCoord3f(1.0F, 1.0F, -1.0F);
glVertex3f(SIZE, SIZE, -SIZE);
// neg z
glTexCoord3f(-1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glTexCoord3f( 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glTexCoord3f( 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glTexCoord3f(-1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
// pos z
glTexCoord3f( 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glTexCoord3f(-1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glTexCoord3f(-1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glTexCoord3f( 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// pos y
glTexCoord3f(-1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glTexCoord3f(-1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glTexCoord3f( 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glTexCoord3f( 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// neg y
glTexCoord3f(-1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glTexCoord3f(-1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glTexCoord3f( 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glTexCoord3f( 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glEnd();
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
for (GLint i = 0; i < stacks; ++i) {
const GLfloat stackAngle1 = i * stackStep;
const GLfloat stackAngle2 = (i + 1) * stackStep;
glBegin(GL_TRIANGLE_STRIP);
for (GLint j = 0; j <= slices; ++j) {
const GLfloat sliceAngle1 = j * sliceStep;
const GLfloat sliceAngle2 = (j + 1) * sliceStep;
const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
const GLfloat ny1 = std::cos(stackAngle1);
const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
const GLfloat x1 = radius * nx1;
const GLfloat y1 = radius * ny1;
const GLfloat z1 = radius * nz1;
const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
const GLfloat ny2 = std::cos(stackAngle2);
const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
const GLfloat x2 = radius * nx2;
const GLfloat y2 = radius * ny2;
const GLfloat z2 = radius * nz2;
// 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
// 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
// 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
glNormal3f(nx2, ny2, nz2);
glTexCoord2f(static_cast<GLfloat>(j) / slices,
1.0F - static_cast<GLfloat>(i + 1) / stacks);
glVertex3f(x2, y2, z2);
glNormal3f(nx1, ny1, nz1);
glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
1.0F - static_cast<GLfloat>(i) / stacks);
glVertex3f(x1, y1, z1);
}
glEnd();
}
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
for (int i = 0; i < numMajor; ++i) {
const GLfloat u0 = i * majorStep;
const GLfloat u1 = u0 + majorStep;
glBegin(GL_TRIANGLE_STRIP);
for (int j = 0; j <= numMinor; ++j) {
const GLfloat v0 = j * minorStep;
// 计算两个顶点的坐标
const GLfloat n0[3] = {
std::cos(v0) * std::cos(u0),
std::cos(v0) * std::sin(u0),
std::sin(v0)
};
const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
const GLfloat z00 = minorRadius * std::sin(v0);
const GLfloat n1[3] = {
std::cos(v0) * std::cos(u1),
std::cos(v0) * std::sin(u1),
std::sin(v0)
};
const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
const GLfloat z01 = minorRadius * std::sin(v0);
glNormal3f(n0[0], n0[1], n0[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor);
glVertex3f(x00, y00, z00);
glNormal3f(n1[0], n1[1], n1[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);
glVertex3f(x01, y01, z01);
}
glEnd();
}
}
/**
* 视图矩阵 = 上面的逆:旋转转置 + 平移取负
* 把世界挪到相机眼前
*/
void gltApplyCameraTransform(const GLFrame *pCamera) {
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// 构建视图矩阵 (View matrix) 列主序 (column-major order)
GLfloat m[16]; // 同样的轴,改填「行」,即转置
m[0]=r[0]; m[4]=r[1]; m[8] =r[2]; m[12]=0.0F;
m[1]=pCamera->vUp[0]; m[5]=pCamera->vUp[1]; m[9] =pCamera->vUp[2]; m[13]=0.0F;
m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}
/**
* 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
* 四列依次是: right、up、-forward、location
* 物体→世界
*/
void gltApplyActorTransform(const GLFrame *frame){
// 算出物体的右向量 (Right vector)
GLfloat r[3];
r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];
// 构建物体矩阵 (Model matrix) 列主序 (column-major order)
// 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
// 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
GLfloat m[16];
m[0]=r[0]; m[4]=frame->vUp[0]; m[8] =-frame->vForward[0]; m[12]=frame->vLocation[0];
m[1]=r[1]; m[5]=frame->vUp[1]; m[9] =-frame->vForward[1]; m[13]=frame->vLocation[1];
m[2]=r[2]; m[6]=frame->vUp[2]; m[10]=-frame->vForward[2]; m[14]=frame->vLocation[2];
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
}
/**
* 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
*
* GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
* 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
* R_world = V⁻¹ · R_eye = Vᵀ · R_eye
* 环境贴图视为无限远,只取旋转部分,不平移。
*
* 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
* 反射方向会被多转一次,和天空盒对不上。
*/
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// V 的三行分别是 right、up、-forward;转置后它们变成三列。
// 列主序数组里直接按「列 = V 原来的行」填入即可。
m[0]=r[0]; m[4]=pCamera->vUp[0]; m[8] =-pCamera->vForward[0]; m[12]=0.0F;
m[1]=r[1]; m[5]=pCamera->vUp[1]; m[9] =-pCamera->vForward[1]; m[13]=0.0F;
m[2]=r[2]; m[6]=pCamera->vUp[2]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
}
void renderScene(){
// Rendering code goes here
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
drawSkyBox();
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glPushMatrix();
glTranslatef(0.0f, 0.0f, -3.0f); // Example translation, adjust as needed
glMatrixMode(GL_TEXTURE); // Switch to texture matrix mode to apply texture transformations if needed
glPushMatrix(); // Save the current texture matrix
// 反射坐标在 eye space,用相机旋转的逆转回 world space,才能和天空盒对齐
GLfloat cameraInverse[16];
gltGetCameraInverseMatrix(&frameCamera, cameraInverse);
glMultMatrixf(cameraInverse);
drawSphere(0.75F, 41, 41); // Assuming you have a function to draw a sphere
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix(); // Restore the texture matrix
glPopMatrix();
glPopMatrix();
glutSwapBuffers();
}
void changeSize(const int w, int h){
if(h == 0) h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
frame->vLocation[0] += frame->vForward[0] * distance;
frame->vLocation[1] += frame->vForward[1] * distance;
frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
* 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
* @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
*
* 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
* v' = v·cosθ + (axis × v)·sinθ
* 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
* 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
*/
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
// axis × forward —— 注意这是 -right(right = forward × up),
// 这个负号决定了旋转方向,别误当成右向量
const GLfloat vAxisCross[3] = {
frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
};
// 角度转弧度
const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
const GLfloat fCos = std::cos(rad);
const GLfloat fSin = std::sin(rad);
// vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];
// 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
// forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
frame->vForward[1] * frame->vForward[1] +
frame->vForward[2] * frame->vForward[2]);
frame->vForward[0] /= len;
frame->vForward[1] /= len;
frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
switch(key){
case GLUT_KEY_UP:
gltMoveFrameForward(&frameCamera, 0.1f);
break;
case GLUT_KEY_DOWN:
gltMoveFrameForward(&frameCamera, -0.1f);
break;
case GLUT_KEY_LEFT:
gltRotateFrameY(&frameCamera, 0.1f);
break;
case GLUT_KEY_RIGHT:
gltRotateFrameY(&frameCamera, -0.1f);
break;
}
glutPostRedisplay();
}
void timerFunc(const int _){
glutPostRedisplay();
glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}
int main(int args, char** argv){
std::cout << "Starting program..." << std::endl;
glutInit(&args, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
const int mainWindowId = glutCreateWindow("OpenGL Window");
if(mainWindowId == 0){
throw std::runtime_error("Failed to create main window");
}
// ======= 事件处理 =======
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys);
glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
// ======= 渲染循环 =======
GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
std::cout << "Max texture units: " << iUnits << std::endl;
setupRc();
glutMainLoop();
shutdownRc();
return 0;
}
多重纹理
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <fstream>
#include <vector>
#include <numbers>
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;
static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
"../image/pos_x.tga",
"../image/neg_x.tga",
"../image/pos_y.tga",
"../image/neg_y.tga",
"../image/pos_z.tga",
"../image/neg_z.tga"
};
static constexpr std::string COLOR_TEXTURE = "../image/tarnish.tga";
static constexpr GLint CUBE[CUBE_FACES_COUNT] = {
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
static constexpr GLint TEXTURE_COUNT = 2;
static constexpr GLint CUBE_MAP = 0;
static constexpr GLint COLOR_MAP = 1;
struct GLFrame {
GLfloat vLocation[3]; // 位置 -> move x, y, z
GLfloat vUp[3]; // 上方向 -> y
GLfloat vForward[3]; // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};
static GLFrame frameCamera;
static GLuint textures[TEXTURE_COUNT];
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void gltInitFrame(GLFrame *pFrame) {
pFrame->vLocation[0] = 0.0f;
pFrame->vLocation[1] = 0.0f;
pFrame->vLocation[2] = 0.0f;
pFrame->vUp[0] = 0.0f;
pFrame->vUp[1] = 1.0f;
pFrame->vUp[2] = 0.0f;
pFrame->vForward[0] = 0.0f;
pFrame->vForward[1] = 0.0f;
pFrame->vForward[2] = -1.0f; // 为什么是-1?
}
void setupRc(){
gltInitFrame(&frameCamera);
glCullFace(GL_BACK); // 指定要剔除的面为背面
glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
glEnable(GL_CULL_FACE); // 启用面剔除
glEnable(GL_DEPTH_TEST); // 启用深度测试
glGenTextures(TEXTURE_COUNT, textures); // 生成纹理对象
glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_MAP]); // 绑定立方体贴图纹理对象
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
for(int i = 0; i < CUBE_FACES_COUNT; i++){
std::ifstream file(CUBE_FACES[i], std::ios::binary);
if(!file){
throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
}
TargaHeader targaHeader;
file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
if(!file){
throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
}
std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(!file){
throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
}
glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
}
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
// 颜色纹理
glBindTexture(GL_TEXTURE_2D, textures[COLOR_MAP]); // 绑定颜色纹理对象
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
std::ifstream file(COLOR_TEXTURE, std::ios::binary);
if(!file){
throw std::runtime_error("Failed to open texture file: " + std::string(COLOR_TEXTURE));
}
TargaHeader targaHeader;
file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
if(!file){
throw std::runtime_error("Failed to read Targa header from file: " + std::string(COLOR_TEXTURE));
}
std::cout << "tga file: " << COLOR_TEXTURE << ", header: " << targaHeader << std::endl;
const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
std::vector<char> imageData(imageSize);
file.read(imageData.data(), imageSize);
if(!file){
throw std::runtime_error("Failed to read image data from file: " + std::string(COLOR_TEXTURE));
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
glGenerateMipmap(GL_TEXTURE_2D);
// 颜色纹理
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textures[COLOR_MAP]);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
// 立方体纹理
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_MAP]);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glEnable(GL_TEXTURE_CUBE_MAP);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
void shutdownRc(){
glDeleteTextures(TEXTURE_COUNT, textures);
}
void drawSkyBox(){
constexpr GLfloat SIZE = 15.0F; // Skybox size
glBegin(GL_QUADS);
// neg x
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
// pos x
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f(SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f(SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f(SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f(SIZE, SIZE, -SIZE);
// neg z
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
// pos z
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// pos y
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// neg y
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glEnd();
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
for (GLint i = 0; i < stacks; ++i) {
const GLfloat stackAngle1 = i * stackStep;
const GLfloat stackAngle2 = (i + 1) * stackStep;
glBegin(GL_TRIANGLE_STRIP);
for (GLint j = 0; j <= slices; ++j) {
const GLfloat sliceAngle1 = j * sliceStep;
const GLfloat sliceAngle2 = (j + 1) * sliceStep;
const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
const GLfloat ny1 = std::cos(stackAngle1);
const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
const GLfloat x1 = radius * nx1;
const GLfloat y1 = radius * ny1;
const GLfloat z1 = radius * nz1;
const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
const GLfloat ny2 = std::cos(stackAngle2);
const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
const GLfloat x2 = radius * nx2;
const GLfloat y2 = radius * ny2;
const GLfloat z2 = radius * nz2;
// 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
// 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
// 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
glNormal3f(nx2, ny2, nz2);
glTexCoord2f(static_cast<GLfloat>(j) / slices,
1.0F - static_cast<GLfloat>(i + 1) / stacks);
glVertex3f(x2, y2, z2);
glNormal3f(nx1, ny1, nz1);
glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
1.0F - static_cast<GLfloat>(i) / stacks);
glVertex3f(x1, y1, z1);
}
glEnd();
}
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
for (int i = 0; i < numMajor; ++i) {
const GLfloat u0 = i * majorStep;
const GLfloat u1 = u0 + majorStep;
glBegin(GL_TRIANGLE_STRIP);
for (int j = 0; j <= numMinor; ++j) {
const GLfloat v0 = j * minorStep;
// 计算两个顶点的坐标
const GLfloat n0[3] = {
std::cos(v0) * std::cos(u0),
std::cos(v0) * std::sin(u0),
std::sin(v0)
};
const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
const GLfloat z00 = minorRadius * std::sin(v0);
const GLfloat n1[3] = {
std::cos(v0) * std::cos(u1),
std::cos(v0) * std::sin(u1),
std::sin(v0)
};
const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
const GLfloat z01 = minorRadius * std::sin(v0);
glNormal3f(n0[0], n0[1], n0[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor);
glVertex3f(x00, y00, z00);
glNormal3f(n1[0], n1[1], n1[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);
glVertex3f(x01, y01, z01);
}
glEnd();
}
}
/**
* 视图矩阵 = 上面的逆:旋转转置 + 平移取负
* 把世界挪到相机眼前
*/
void gltApplyCameraTransform(const GLFrame *pCamera) {
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// 构建视图矩阵 (View matrix) 列主序 (column-major order)
GLfloat m[16]; // 同样的轴,改填「行」,即转置
m[0]=r[0]; m[4]=r[1]; m[8] =r[2]; m[12]=0.0F;
m[1]=pCamera->vUp[0]; m[5]=pCamera->vUp[1]; m[9] =pCamera->vUp[2]; m[13]=0.0F;
m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}
/**
* 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
* 四列依次是: right、up、-forward、location
* 物体→世界
*/
void gltApplyActorTransform(const GLFrame *frame){
// 算出物体的右向量 (Right vector)
GLfloat r[3];
r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];
// 构建物体矩阵 (Model matrix) 列主序 (column-major order)
// 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
// 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
GLfloat m[16];
m[0]=r[0]; m[4]=frame->vUp[0]; m[8] =-frame->vForward[0]; m[12]=frame->vLocation[0];
m[1]=r[1]; m[5]=frame->vUp[1]; m[9] =-frame->vForward[1]; m[13]=frame->vLocation[1];
m[2]=r[2]; m[6]=frame->vUp[2]; m[10]=-frame->vForward[2]; m[14]=frame->vLocation[2];
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
}
/**
* 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
*
* GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
* 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
* R_world = V⁻¹ · R_eye = Vᵀ · R_eye
* 环境贴图视为无限远,只取旋转部分,不平移。
*
* 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
* 反射方向会被多转一次,和天空盒对不上。
*/
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// V 的三行分别是 right、up、-forward;转置后它们变成三列。
// 列主序数组里直接按「列 = V 原来的行」填入即可。
m[0]=r[0]; m[4]=pCamera->vUp[0]; m[8] =-pCamera->vForward[0]; m[12]=0.0F;
m[1]=r[1]; m[5]=pCamera->vUp[1]; m[9] =-pCamera->vForward[1]; m[13]=0.0F;
m[2]=r[2]; m[6]=pCamera->vUp[2]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
}
void renderScene(){
// Rendering code goes here
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
glActiveTexture(GL_TEXTURE0); // Ensure the color texture is active before drawing the skybox
glDisable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE1); // Switch to the cubemap texture unit
glEnable(GL_TEXTURE_CUBE_MAP);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
drawSkyBox();
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glActiveTexture(GL_TEXTURE0); // Make sure the cubemap texture unit is active for subsequent operations
glEnable(GL_TEXTURE_2D); // Re-enable the color texture unit for subsequent operations
glPushMatrix();
glTranslatef(0.0f, 0.0f, -3.0f); // Example translation, adjust as needed
glActiveTexture(GL_TEXTURE1);
glMatrixMode(GL_TEXTURE); // Switch to texture matrix mode to apply texture transformations if needed
glPushMatrix(); // Save the current texture matrix
// 反射坐标在 eye space,用相机旋转的逆转回 world space,才能和天空盒对齐
GLfloat cameraInverse[16];
gltGetCameraInverseMatrix(&frameCamera, cameraInverse);
glMultMatrixf(cameraInverse);
glColor3f(1.0f, 1.0f, 1.0f);
drawSphere(0.75F, 41, 41); // Assuming you have a function to draw a sphere
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix(); // Restore the texture matrix
glPopMatrix();
glPopMatrix();
glutSwapBuffers();
}
void changeSize(const int w, int h){
if(h == 0) h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
frame->vLocation[0] += frame->vForward[0] * distance;
frame->vLocation[1] += frame->vForward[1] * distance;
frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
* 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
* @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
*
* 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
* v' = v·cosθ + (axis × v)·sinθ
* 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
* 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
*/
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
// axis × forward —— 注意这是 -right(right = forward × up),
// 这个负号决定了旋转方向,别误当成右向量
const GLfloat vAxisCross[3] = {
frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
};
// 角度转弧度
const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
const GLfloat fCos = std::cos(rad);
const GLfloat fSin = std::sin(rad);
// vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];
// 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
// forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
frame->vForward[1] * frame->vForward[1] +
frame->vForward[2] * frame->vForward[2]);
frame->vForward[0] /= len;
frame->vForward[1] /= len;
frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
switch(key){
case GLUT_KEY_UP:
gltMoveFrameForward(&frameCamera, 0.1f);
break;
case GLUT_KEY_DOWN:
gltMoveFrameForward(&frameCamera, -0.1f);
break;
case GLUT_KEY_LEFT:
gltRotateFrameY(&frameCamera, 0.1f);
break;
case GLUT_KEY_RIGHT:
gltRotateFrameY(&frameCamera, -0.1f);
break;
}
glutPostRedisplay();
}
void timerFunc(const int _){
glutPostRedisplay();
glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}
int main(int args, char** argv){
std::cout << "Starting program..." << std::endl;
glutInit(&args, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
const int mainWindowId = glutCreateWindow("OpenGL Window");
if(mainWindowId == 0){
throw std::runtime_error("Failed to create main window");
}
// ======= 事件处理 =======
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys);
glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
// ======= 渲染循环 =======
GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
std::cout << "Max texture units: " << iUnits << std::endl;
setupRc();
glutMainLoop();
shutdownRc();
return 0;
}
点块纹理
#include <OpenGL/glext.h>
#include <cmath>
#include <random>
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <fstream>
#include <vector>
#include <numbers>
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;
static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
"../image/pos_x.tga",
"../image/neg_x.tga",
"../image/pos_y.tga",
"../image/neg_y.tga",
"../image/pos_z.tga",
"../image/neg_z.tga"
};
static constexpr std::string COLOR_TEXTURE = "../image/tarnish.tga";
static constexpr GLint CUBE[CUBE_FACES_COUNT] = {
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
static constexpr GLint TEXTURE_COUNT = 2;
struct GLFrame {
GLfloat vLocation[3]; // 位置 -> move x, y, z
GLfloat vUp[3]; // 上方向 -> y
GLfloat vForward[3]; // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};
static constexpr GLint SMALL_STAR = 100;
static constexpr GLint MEDIUM_STAR = 40;
static constexpr GLint LARGE_STAR = 15;
static GLfloat smallStars[SMALL_STAR][2];
static GLfloat mediumStars[MEDIUM_STAR][2];
static GLfloat largeStars[LARGE_STAR][2];
static GLuint textures[TEXTURE_COUNT];
static GLint drawMode = 1;
static int mainWindowId = 0;
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void gltInitFrame(GLFrame *pFrame) {
pFrame->vLocation[0] = 0.0f;
pFrame->vLocation[1] = 0.0f;
pFrame->vLocation[2] = 0.0f;
pFrame->vUp[0] = 0.0f;
pFrame->vUp[1] = 1.0f;
pFrame->vUp[2] = 0.0f;
pFrame->vForward[0] = 0.0f;
pFrame->vForward[1] = 0.0f;
pFrame->vForward[2] = -1.0f; // 为什么是-1?
}
void setupRc(){
// small
std::random_device rd; // 硬件熵源,用于播种
std::mt19937_64 gen(rd()); // Mersenne Twister 引擎
std::uniform_int_distribution<std::int32_t> dis(
std::numeric_limits<std::int32_t>::min(),
std::numeric_limits<std::int32_t>::max()
);
for(int i = 0; i < SMALL_STAR; ++i){
smallStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH); // [-20, 20]
smallStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) + 100); // [-20, 20]
}
for(int i = 0; i < MEDIUM_STAR; ++i){
mediumStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH * 10) / 10.0F; // [-20, 20]
mediumStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) + 100); // [-20, 20]
}
for(int i = 0; i < LARGE_STAR; ++i){
largeStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH * 10) / 10.0F; // [-20, 20]
largeStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) * 10) / 10.0F + 100.0F; // [-20, 20]
}
glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
glColor3f(0.0f, 0.0f, 0.0f);
glGenTextures(TEXTURE_COUNT, textures);
glBindTexture(GL_TEXTURE_2D, textures[0]);
TargaHeader header;
std::ifstream file("../image/star.tga", std::ios::binary);
if(file){
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
}
std::vector<char> imageData(header.width * header.height * (header.bitsPerPixel / 8));
if(file){
file.read(imageData.data(), imageData.size());
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, header.width, header.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, textures[1]);
TargaHeader header2;
std::ifstream file2("../image/moon.tga", std::ios::binary);
if(file2){
file2.read(reinterpret_cast<char*>(&header2), sizeof(TargaHeader));
}
std::vector<char> imageData2(header2.width * header2.height * (header2.bitsPerPixel / 8));
if(file2){
file2.read(imageData2.data(), imageData2.size());
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, header2.width, header2.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData2.data());
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
glTexEnvi(GL_POINT_SPRITE, GL_TEXTURE_ENV_MODE, GL_DECAL);
}
void shutdownRc(){
glDeleteTextures(TEXTURE_COUNT, textures);
}
void drawSkyBox(){
constexpr GLfloat SIZE = 15.0F; // Skybox size
glBegin(GL_QUADS);
// neg x
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
// pos x
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f(SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f(SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f(SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f(SIZE, SIZE, -SIZE);
// neg z
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
// pos z
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// pos y
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, 1.0F);
glVertex3f(-SIZE, SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
glVertex3f(-SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, -1.0F);
glVertex3f( SIZE, SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, 1.0F, 1.0F);
glVertex3f( SIZE, SIZE, SIZE);
// neg y
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
glVertex3f(-SIZE, -SIZE, -SIZE);
glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
glVertex3f(-SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, 1.0F);
glVertex3f( SIZE, -SIZE, SIZE);
glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
glVertex3f( SIZE, -SIZE, -SIZE);
glEnd();
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
for (GLint i = 0; i < stacks; ++i) {
const GLfloat stackAngle1 = i * stackStep;
const GLfloat stackAngle2 = (i + 1) * stackStep;
glBegin(GL_TRIANGLE_STRIP);
for (GLint j = 0; j <= slices; ++j) {
const GLfloat sliceAngle1 = j * sliceStep;
const GLfloat sliceAngle2 = (j + 1) * sliceStep;
const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
const GLfloat ny1 = std::cos(stackAngle1);
const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
const GLfloat x1 = radius * nx1;
const GLfloat y1 = radius * ny1;
const GLfloat z1 = radius * nz1;
const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
const GLfloat ny2 = std::cos(stackAngle2);
const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
const GLfloat x2 = radius * nx2;
const GLfloat y2 = radius * ny2;
const GLfloat z2 = radius * nz2;
// 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
// 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
// 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
glNormal3f(nx2, ny2, nz2);
glTexCoord2f(static_cast<GLfloat>(j) / slices,
1.0F - static_cast<GLfloat>(i + 1) / stacks);
glVertex3f(x2, y2, z2);
glNormal3f(nx1, ny1, nz1);
glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
1.0F - static_cast<GLfloat>(i) / stacks);
glVertex3f(x1, y1, z1);
}
glEnd();
}
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
for (int i = 0; i < numMajor; ++i) {
const GLfloat u0 = i * majorStep;
const GLfloat u1 = u0 + majorStep;
glBegin(GL_TRIANGLE_STRIP);
for (int j = 0; j <= numMinor; ++j) {
const GLfloat v0 = j * minorStep;
// 计算两个顶点的坐标
const GLfloat n0[3] = {
std::cos(v0) * std::cos(u0),
std::cos(v0) * std::sin(u0),
std::sin(v0)
};
const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
const GLfloat z00 = minorRadius * std::sin(v0);
const GLfloat n1[3] = {
std::cos(v0) * std::cos(u1),
std::cos(v0) * std::sin(u1),
std::sin(v0)
};
const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
const GLfloat z01 = minorRadius * std::sin(v0);
glNormal3f(n0[0], n0[1], n0[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor);
glVertex3f(x00, y00, z00);
glNormal3f(n1[0], n1[1], n1[2]);
glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);
glVertex3f(x01, y01, z01);
}
glEnd();
}
}
/**
* 视图矩阵 = 上面的逆:旋转转置 + 平移取负
* 把世界挪到相机眼前
*/
void gltApplyCameraTransform(const GLFrame *pCamera) {
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// 构建视图矩阵 (View matrix) 列主序 (column-major order)
GLfloat m[16]; // 同样的轴,改填「行」,即转置
m[0]=r[0]; m[4]=r[1]; m[8] =r[2]; m[12]=0.0F;
m[1]=pCamera->vUp[0]; m[5]=pCamera->vUp[1]; m[9] =pCamera->vUp[2]; m[13]=0.0F;
m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}
/**
* 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
* 四列依次是: right、up、-forward、location
* 物体→世界
*/
void gltApplyActorTransform(const GLFrame *frame){
// 算出物体的右向量 (Right vector)
GLfloat r[3];
r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];
// 构建物体矩阵 (Model matrix) 列主序 (column-major order)
// 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
// 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
GLfloat m[16];
m[0]=r[0]; m[4]=frame->vUp[0]; m[8] =-frame->vForward[0]; m[12]=frame->vLocation[0];
m[1]=r[1]; m[5]=frame->vUp[1]; m[9] =-frame->vForward[1]; m[13]=frame->vLocation[1];
m[2]=r[2]; m[6]=frame->vUp[2]; m[10]=-frame->vForward[2]; m[14]=frame->vLocation[2];
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
glMultMatrixf(m);
}
/**
* 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
*
* GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
* 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
* R_world = V⁻¹ · R_eye = Vᵀ · R_eye
* 环境贴图视为无限远,只取旋转部分,不平移。
*
* 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
* 反射方向会被多转一次,和天空盒对不上。
*/
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
// 算出相机右向量 (Right vector)
GLfloat r[3];
r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
// V 的三行分别是 right、up、-forward;转置后它们变成三列。
// 列主序数组里直接按「列 = V 原来的行」填入即可。
m[0]=r[0]; m[4]=pCamera->vUp[0]; m[8] =-pCamera->vForward[0]; m[12]=0.0F;
m[1]=r[1]; m[5]=pCamera->vUp[1]; m[9] =-pCamera->vForward[1]; m[13]=0.0F;
m[2]=r[2]; m[6]=pCamera->vUp[2]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
m[3]=0.0F; m[7]=0.0F; m[11]=0.0F; m[15]=1.0F;
}
void renderScene(){
// Rendering code goes here
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0F, 1.0F, 1.0F);
if(drawMode == 3) {
glEnable(GL_POINT_SPRITE);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glEnable(GL_BLEND);
}
glPointSize(7.0f); // Example point size, adjust as needed
glBegin(GL_POINTS);
for (int i = 0; i < SMALL_STAR; ++i) {
glVertex2fv(smallStars[i]);
}
glEnd();
glPointSize(12.0F);
glBegin(GL_POINTS);
for (int i = 0; i < MEDIUM_STAR; ++i) {
glVertex2fv(mediumStars[i]);
}
glEnd();
glPointSize(20.0F);
glBegin(GL_POINTS);
for (int i = 0; i < LARGE_STAR; ++i) {
glVertex2fv(largeStars[i]);
}
glEnd();
glPointSize(120.0F);
if(drawMode == 3) {
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, textures[1]);
}
glBegin(GL_POINTS);
glVertex2f(700.0F, 500.0F);
glEnd();
glDisable(GL_TEXTURE_2D);
glDisable(GL_POINT_SPRITE);
// Draw distant horizon
glLineWidth(3.5F);
glBegin(GL_LINE_STRIP);
glVertex2f(0.0f, 25.0f);
glVertex2f(50.0f, 100.0f);
glVertex2f(100.0f, 25.0f);
glVertex2f(225.0f, 115.0f);
glVertex2f(300.0f, 50.0f);
glVertex2f(375.0f, 100.0f);
glVertex2f(460.0f, 25.0f);
glVertex2f(525.0f, 100.0f);
glVertex2f(600.0f, 20.0f);
glVertex2f(675.0f, 70.0f);
glVertex2f(750.0f, 25.0f);
glVertex2f(800.0f, 90.0f);
glEnd();
glutSwapBuffers();
}
void changeSize(const int w, int h){
if(h == 0) h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
glOrtho(0.0, w, 0.0, h, -1.0, 1); // 使用正交投影替代透视投影
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
frame->vLocation[0] += frame->vForward[0] * distance;
frame->vLocation[1] += frame->vForward[1] * distance;
frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
* 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
* @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
*
* 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
* v' = v·cosθ + (axis × v)·sinθ
* 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
* 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
*/
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
// axis × forward —— 注意这是 -right(right = forward × up),
// 这个负号决定了旋转方向,别误当成右向量
const GLfloat vAxisCross[3] = {
frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
};
// 角度转弧度
const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
const GLfloat fCos = std::cos(rad);
const GLfloat fSin = std::sin(rad);
// vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];
// 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
// forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
frame->vForward[1] * frame->vForward[1] +
frame->vForward[2] * frame->vForward[2]);
frame->vForward[0] /= len;
frame->vForward[1] /= len;
frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
switch(key){
case GLUT_KEY_UP:
// gltMoveFrameForward(&frameCamera, 0.1f);
break;
case GLUT_KEY_DOWN:
// gltMoveFrameForward(&frameCamera, -0.1f);
break;
case GLUT_KEY_LEFT:
// gltRotateFrameY(&frameCamera, 0.1f);
break;
case GLUT_KEY_RIGHT:
// gltRotateFrameY(&frameCamera, -0.1f);
break;
}
glutPostRedisplay();
}
void timerFunc(const int _){
glutPostRedisplay();
glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}
void processMenu(int option){
drawMode = option;
switch(option){
case 1:
// Normal Point
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POINT_SMOOTH);
glDisable(GL_TEXTURE_2D);
glDisable(GL_POINT_SPRITE);
break;
case 2:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_POINT_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glDisable(GL_TEXTURE_2D);
glDisable(GL_POINT_SPRITE);
break;
case 3:
// Point Sprites
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POINT_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
break;
}
glutSetWindow(mainWindowId);
glutPostRedisplay();
}
int main(int args, char** argv){
std::cout << "Starting program..." << std::endl;
glutInit(&args, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
mainWindowId = glutCreateWindow("OpenGL Window");
if(mainWindowId == 0){
throw std::runtime_error("Failed to create main window");
}
// ======= 事件处理 =======
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutSpecialFunc(specialKeys);
glutCreateMenu(processMenu); // 如果有菜单状态处理函数
glutAddMenuEntry("Normal Point", 1);
glutAddMenuEntry("Antialiased Points", 2);
glutAddMenuEntry("Point Sprites", 3);
glutAttachMenu(GLUT_RIGHT_BUTTON); // 将菜单附加到鼠标右键
// ======= 渲染循环 =======
GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
std::cout << "Max texture units: " << iUnits << std::endl;
setupRc();
glutMainLoop();
shutdownRc();
return 0;
}

浙公网安备 33010602011771号