OpenGL/GLUT实践:绘制旋转的立方体与雪人世界——添加光照与SOIL方式添加纹理(电子科技大学信软图形与动画Ⅱ实验)

本文主要是介绍OpenGL/GLUT实践:绘制旋转的立方体与雪人世界——添加光照与SOIL方式添加纹理(电子科技大学信软图形与动画Ⅱ实验),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

    • 1 运行效果
    • 2 实现过程
      • 2.1 几何转换
        • 2.1.1 窗口刷新
        • 2.1.2 绘制雪人场景
          • 2.1.2.1 绘制雪人
          • 2.1.2.2 绘制场景
        • 2.1.3 键盘事件
        • 2.1.4 运行效果
      • 2.2 颜色
      • 2.3 光照
        • 2.3.1 绘制正方体
        • 2.3.2 添加光源
      • 2.4 材质
        • 2.4.1 方法一
        • 2.4.2 方法二
      • 2.5 纹理
        • 2.5.1 SOIL环境配置
        • 2.5.2 纹理加载
      • 2.6 雪人世界光照与材质

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;
}

这篇关于OpenGL/GLUT实践:绘制旋转的立方体与雪人世界——添加光照与SOIL方式添加纹理(电子科技大学信软图形与动画Ⅱ实验)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131325

相关文章

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

Springboot整合Redis主从实践

《Springboot整合Redis主从实践》:本文主要介绍Springboot整合Redis主从的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnect

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

RedisTemplate默认序列化方式显示中文乱码的解决

《RedisTemplate默认序列化方式显示中文乱码的解决》本文主要介绍了SpringDataRedis默认使用JdkSerializationRedisSerializer导致数据乱码,文中通过示... 目录1. 问题原因2. 解决方案3. 配置类示例4. 配置说明5. 使用示例6. 验证存储结果7.

Nginx Location映射规则总结归纳与最佳实践

《NginxLocation映射规则总结归纳与最佳实践》Nginx的location指令是配置请求路由的核心机制,其匹配规则直接影响请求的处理流程,下面给大家介绍NginxLocation映射规则... 目录一、Location匹配规则与优先级1. 匹配模式2. 优先级顺序3. 匹配示例二、Proxy_pa

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Python程序打包exe,单文件和多文件方式

《Python程序打包exe,单文件和多文件方式》:本文主要介绍Python程序打包exe,单文件和多文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python 脚本打成exe文件安装Pyinstaller准备一个ico图标打包方式一(适用于文件较少的程