Skip to content

OpenGL/GLUT实践:绘制旋转的立方体与雪人世界——添加光照与SOIL方式添加纹理

2634字约9分钟

OpenGLCG

2024-09-02

源码见GitHub:A-UESTCer-s-Code

1 运行效果

旋转的立方体实现效果:

image-20240417164843163

雪人世界实现效果:

recording

2 实现过程

2.1 几何转换

2.1.1 窗口刷新

利用透视变换实现窗口刷新:

  • 通过透视投影来设置窗口刷新函数,使用gluPerspective()函数定义透视投影。
void ChangeSize(GLsizei w, GLsizei h)
{
	GLfloat aspectRatio;
	if (h == 0)
		h = 1;

	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	aspectRatio = (GLfloat)w / (GLfloat)h;
	gluPerspective(60.0f, aspectRatio, 1.0, 400.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

2.1.2 绘制雪人场景

2.1.2.1 绘制雪人
  1. 绘制雪人身体部分:
    • 首先设置颜色为白色。
    • 使用glTranslatef()将当前矩阵沿着x、y和z轴移动到指定位置。
    • 绘制一个半径为0.75的实心球体作为雪人的身体。
  2. 绘制雪人头部:
    • 再次使用glTranslatef()将当前矩阵移动到头部位置。
    • 绘制一个半径为0.25的实心球体作为雪人的头部。
  3. 绘制雪人眼睛:
    • 将当前矩阵保存(使用glPushMatrix()),以便后续绘制完成后恢复到初始状态。
    • 设置眼睛颜色为黑色。
    • 分别用glTranslatef()将当前矩阵移动到左眼和右眼的位置。
    • 绘制半径为0.05的实心小球体作为眼睛。
    • 恢复之前保存的矩阵状态(使用glPopMatrix())。
  4. 绘制雪人的鼻子:
    • 设置鼻子颜色为橙红色。
    • 使用glRotatef()将当前矩阵绕着x轴旋转0度(这里没有实际的旋转操作)。
    • 绘制一个底半径为0.08、高度为0.5的圆锥体作为雪人的鼻子。
2.1.2.2 绘制场景
  1. 清除颜色和深度缓冲区:
    • 使用glClear()函数清除颜色缓冲区和深度缓冲区,以便开始渲染新的帧。
  2. 重置变换矩阵:
    • 使用glLoadIdentity()函数重置变换矩阵,以确保每一帧的绘制都是从一个空白状态开始的。
  3. 设置相机(镜头):
    • 使用gluLookAt()函数设置相机的位置和方向。函数的参数为相机位置(x, 1.0f, z),相机目标位置(x+lx, 1.0f, z+lz),以及相机的上方向(0.0f, 1.0f, 0.0f)
  4. 绘制地面:
    • 使用白色绘制地面,通过glColor3f()设置颜色。
    • 使用glBegin()glEnd()包裹的GL_QUADS模式绘制一个矩形地面。
  5. 绘制36个雪人:
    • 使用两层嵌套的for循环,在不同的位置调用drawSnowMan()函数来绘制36个雪人。
    • 内部的glPushMatrix()glPopMatrix()用于保存和恢复当前变换矩阵状态,以确保每个雪人的绘制都是相对独立的。
  6. 交换缓冲区:
    • 使用glutSwapBuffers()交换前后缓冲区,以显示渲染好的图像。

2.1.3 键盘事件

  1. 改变视线方向:
    • 当用户按下左右箭头键时,会改变角度变量angle的值,从而改变视线的方向。
    • 根据新的角度值重新计算视线向量的lxlz值,使用sincos函数将极坐标转换为平面坐标。
  2. 改变镜头位置:
    • 当用户按下上下箭头键时,会分别向前或向后移动镜头。
    • 根据lxlz向量以及给定的粒度(fraction)计算新的镜头位置(x, z),实现沿视线方向的移动。

2.1.4 运行效果

实现窗口刷新演示:

recording

键盘控制前后移动和左右转头:

recording

2.2 颜色

  1. 定义颜色方式:

    • OpenGL通过指定红、绿、蓝(RGB)成分的强度来定义颜色。

    • 使用glColor<x><t>(red, green, blue, alpha)

      函数来设置颜色,其中:

      • <x>表示参数的数量,可以是3(表示RGB颜色)或4(表示RGBA颜色,包括alpha通道);
      • <t>表示参数的数据类型。
  2. 着色模式(shading model):

    • 着色模式定义了图元内部的颜色渲染方式。
    • 默认情况下,OpenGL采用平滑着色模式(GL_SMOOTH)。当图元的顶点指定了不同的颜色时,OpenGL会在顶点之间进行平滑过渡,使得图元内部的颜色呈现渐变效果
    • 另一种着色模式是单调着色(GL_FLAT),在这种模式下,图元内部的颜色取决于最后一个顶点所指定的颜色。对于GL_POLYGON图元,内部颜色取决于第一个顶点的颜色。

GL_SMOOTH 来选择平滑,实现效果如下:

image-20240416220438503

GL_FLAT 单调着色模式,实现效果如下:

image-20240416220631363

2.3 光照

2.3.1 绘制正方体

  1. 全局变量:
    • xrotyrot:用于存储立方体绕x轴和y轴的旋转角度。
    • xspeedyspeed:用于控制立方体绕x轴和y轴的旋转速度。
    • z:用于控制立方体在z轴上的位置。
  2. changeSize函数:
    • 设置OpenGL视口,并根据窗口大小设置透视投影。
  3. InitGL函数:
    • 进行OpenGL的初始化设置,包括设置着色模式、清空颜色缓冲区和深度缓冲区等。
  4. renderScene函数:
    • 清空颜色缓冲区和深度缓冲区。
    • 重置模型视图矩阵,并移动相机位置到z轴为z的位置。
    • 根据xrotyrot的值进行旋转。
    • 绘制一个红色的立方体。
    • 利用双缓冲机制交换前后缓冲区,将绘制的图像显示在屏幕上。
    • 根据xspeedyspeed的值更新旋转角度。
  5. processSpecialKeys函数:
    • 处理特殊键盘按键事件,包括上下左右箭头键和Page Up/Page Down键,分别用于控制立方体在z轴上的移动和绕x轴、y轴的旋转速度。
  6. 主函数:
    • 初始化OpenGL和GLUT,并创建窗口。
    • 注册回调函数,包括绘制函数、窗口大小变化函数和键盘特殊按键事件处理函数。
    • 启用深度测试和双缓冲机制。
    • 进入主循环,等待事件的发生。

实现效果:

recording

2.3.2 添加光源

  1. 启用光源:

    • 在键盘按下“l”键时,调用glEnable(GL_LIGHTING);来启用光照计算。
  2. 设置光照模型:

    • 设置光源参数:在程序头部设置了光源的参数,包括环境光和漫反射光的强度和位置。

      • ambientLight[]环境光的强度,用来模拟场景中各处的间接光照。
      • diffuseLight[]漫反射光的强度,用来模拟光线直接照射到物体表面后的散射。
      • position[]:光源的位置,其中最后一个参数是1.0表示光源为定向光,0.0表示光源为点光源。
    • 设置并启用光照:

      InitGL函数中,调用glLight()函数来设置光源的参数,并启用光源GL_LIGHT0glLightfv函数用于设置光源的各个属性,包括环境光、漫反射光、镜面反射光和光源位置等。

最终效果:

recording

2.4 材质

2.4.1 方法一

使用 glMaterialfv函数手动设置材质属性。

  • 定义一个数组来指定物体表面的材质属性,例如GLfloat gray[] = {0.9f, 0.0f, 0.0f, 1.0f};表示物体表面反射90%的红光。
  • 使用glMaterialfv函数设置材质属性,例如glMaterialfv(GL_FRONT, GL_DIFFUSE, gray);用于设置散射光属性。

实现效果:

image-20240417144747792

2.4.2 方法二

使用颜色追踪(Color Tracking)来设置材质属性。

  • 调用glColorMaterial函数启用颜色追踪,例如glColorMaterial(GL_FRONT, GL_DIFFUSE);表示追踪正面的散射光属性。
  • 启用颜色追踪功能,使用glEnable(GL_COLOR_MATERIAL);
  • 使用glColor函数设置物体的颜色,例如glColor(0.0f, 0.0f, 0.9f, 1.0f);表示设置物体为蓝色。

实现效果:

image-20240417145056781

2.5 纹理

2.5.1 SOIL环境配置

  1. 首先在项目目录下创建libinclude文件夹,分别将SOIL.libSOIL.h放入。

  2. 在VS2022的项目中打开项目属性页,将如下两项加入刚刚创建的两个目录。

    image-20240417163446630
  3. 在链接器的常规中,加入lib目录。

    image-20240417163534482
  4. 在链接器的输入中,加入静态库的完整名称。

    image-20240417163611310

2.5.2 纹理加载

  1. LoadGLTextures函数:

    • 使用循环加载两张图片作为纹理,分别存储在texture[0]texture[1]中。

    • 调用SOIL_load_OGL_texture函数加载图片并将其转换为OpenGL纹理。该函数的参数包括图片路径、加载方式、生成新的纹理ID以及其他标志。

    • 检查纹理加载是否成功,如果失败则返回false。

    • 对每张纹理进行绑定,并设置放大和缩小过滤器为线性过滤器(GL_LINEAR)。

  2. renderScene函数

    绘制立方体的各个面:

    • 每个面都使用glBegin(GL_QUADS)开始绘制,并使用glEnd()结束。
    • 每个面的顶点坐标都使用glVertex3f指定。
    • 每个顶点的纹理坐标都使用glTexCoord2f指定,以便纹理正确贴在立方体上。
    • 每个面的法线(用于光照计算)都使用glNormal3f指定。

2.6 雪人世界光照与材质

要在雪人世界加入光照与材质,我们只需要加入一个InitGL函数,进行光照初始化;并加入普通按键控制,实现按l时, 通过设置glDisable(GL_LIGHTING);glEnable(GL_LIGHTING);,就可以打开/关闭光照。

同时,为了保持在光照下,颜色保持不变,我们只需要加入简单的两行代码使用glColorMaterialglEnable函数,即可实现颜色追踪(Color Tracking)来设置材质属性。

int InitGL(GLvoid)
{

	glColorMaterial(GL_FRONT, GL_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);

	GLfloat ambientLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat diffuseLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat position[] = { 0.0f, 0.0f, 2.0f, 1.0f };

	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT0, GL_POSITION, position);
	glEnable(GL_LIGHT0);

	return true;
}