stl、obj、3DS三种3D模型加载,使用OpenGl+GLSL框架并添加双层纹理,并完成逐顶点光照和逐像素光照(GouraudShading+PhongS)

搭建自己的OpenGl+GLSL框架并添加第二张纹理

本文提到的代码已上传,欢迎下载:https://download.csdn.net/download/qq_32563773/12445711

前言

在第一个实验中,我发现使用glut的一些不方便之处,同时意识到实验一中的不足,对**于OBJ文件并没有读取出其中的纹理信息,**因此,在查阅相关的文档后发现,GLFW代培GLAD是目前较为流行的方案,相对于glut来说,glad更新更流行,因此换用了次框架,同时使用了Assimp库。

一个非常流行的模型导入库是Assimp,它是Open Asset Import Library(开放的资产导入库)的缩写。Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),它会将所有的模型数据加载至Assimp的通用数据结构中。当Assimp加载完模型之后,我们就能够从Assimp的数据结构中提取我们所需的所有数据了。由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问我们需要的数据。

当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个场景(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。Assimp数据结构的(简化)模型如下:

在这里插入图片描述
l 和材质和网格(Mesh)一样,所有的场景/模型数据都包含在Scene对象中。Scene对象也包含了场景根节点的引用。

l 场景的Root node(根节点)可能包含子节点,它会有一系列指向场景对象中mMeshes数组中储存的网格数据的索引。Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引。

l 一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置、法向量、纹理坐标、面(Face)和物体的材质。

l 一个网格包含了多个面。Face代表的是物体的渲染图元(三角形、方形、点)。

l 最后,一个网格也包含了一个Material对象,它包含了一些函数能让我们获取物体的材质属性,比如说颜色和纹理贴图(比如漫反射和镜面光贴图)。

所以,我们需要做的第一件事是将一个物体加载到Scene对象中,遍历节点,获取对应的Mesh对象(我们需要递归搜索每个节点的子节点),并处理每个Mesh对象来获取顶点数据、索引以及它的材质属性。最终的结果是一系列的网格数据。

搭建自己的OpenGL+GLSL框架

编写Model和Mesh类

对于本实验,首先引入Model类和Mesh类。(详细代码请在项目的learnopengl文件夹中查看)

如下是Model类的结构以及其各个函数的功能

在这里插入图片描述

如下是Mesh类及其功能。
在这里插入图片描述

在Mesh类的setupMesh函数中,定义了各个标志相对的数据,如下如:

在这里插入图片描述
因此在顶点着色器中,使用layout(location = ? )来确定相关的变量,本人的顶点着色器如下所示,引入其纹理的数据并定义为aTexCoords,且最后传给TexCoords后传入到片段着色器中。

在这里插入图片描述
在片段着色器中,我们可以获取在顶点着色器中传来的TexCoords数据,并且将两个不同的纹理通过mix函数,事得两个纹理混合,因此达到实验中要求的混合两个纹理的要求。
在这里插入图片描述

在mix函数中,例如mix(x,y,a) ,其中a控制混合结果返回为x(1-a) +y*a,为线性混合的值,因此达到实验要求的设置第二张纹理权重的要求。

引入stb_image.h导入图片作为纹理

stb_image.h是Sean Barrett的一个非常流行的单头文件图像加载库,它能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中。将它以stb_image.h的名字加入你的工程,并另创建一个新的C++文件,输入以下代码:

#define STB_IMAGE_IMPLEMENTATION

#include "stb_image.h"

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。

结果展示

在主函数中,我们使用自己的GLSL渲染和Model类完成最后的结果。

最后在渲染循环中完成模型的渲染。
在这里插入图片描述
结果如下所示,使用本人的照片完成对模型的第二章纹理添加,并设置权重为0.8。

第一张纹理(下左图)和第二张纹理(下右图):

在这里插入图片描述
两张纹理得混合:

在这里插入图片描述

本实验的第二个小任务中。如上图,**在添加第二章纹理之后光照特点不容易观察,且第一张纹理为黑色纹理,更容易观察光照特点。因此在模型中使用第一张纹理而不是两张纹理的混合或第二张。**可在代码中的Model类的Draw函数中注释或显示此代码控制第二张纹理的加载。

在这里插入图片描述

逐顶点光照和逐像素光照

不同着色方式得对比

当前,常见的三类多边形着色方法分别是 Flat Shading、Gouraud Shading 和 Phong Shading。其实从效果图就能看出它们之间大致的区别,以下为三类着色的效果图:

在这里插入图片描述

Flat shading 是最简单的着色模型,每个多边形只会呈现一个颜色,这个颜色由面法向量和光照计算得来。在该模型中,每个多边形中只有多边形的面存在法向量,而其各个顶点没有。

Phong shading 是三者之中最复杂的着色方法。它的特点是结合了多边形物体表面反射光的亮度,并以特定位置的表面法线作为像素参考值,以插值方式来估计其他位置像素的色值。在这种情况下,多边形中每个顶点都有一个法向量,通过这些法向量与光照计算,来得到每个点的颜色。在使用有限数量的多边形时,对定点法向量进行插值可以给出近似平滑的曲面效果,但是这样一来的计算量肯定是非常大的。

Gouraud shading 是三者中比较折衷的方法。类似 Phong shading,其多边形的每个顶点都有一个法向量,但它在着色时不是对向量进行插值。实际使用时,通常先计算多边形每个顶点的光照,再通过双线性插值计算三角形区域中其它像素的颜色。

第一种着色模型比较直观非常好理解,相比之下,后两个方法就显得有些难理解。虽然两个方法都需要应用多边形的法向量计算出其公共顶点的法向量,也都需要计算各顶点处的光强,但在逐像素计算上还是有所区别。

Phong shading 逐像素计算的思路可以理解为从顶点着色器传递过来的法向量,只是这一个顶点的法线方向,而到了片段着色器处理的阶段,每个像素所对应的向量由其周围几个顶点进行插值后得到,之后我们再用该像素点对应的法向量与光照进行计算,得到其着色,即上述所述,我们的插值是对法向量进行插值。而 Gouraud shading 的计算逻辑,我们则可以简单的理解成是对多边形的每个顶点计算好着色,然后再使用双线性插值方法对面上的像素进行计算。
在这里插入图片描述
本人将纹理储存为Material结构体中的一个sampler2D。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图。同时也移除了环境光材质颜色向量,因为环境光颜色在几乎所有情况下都等于漫反射颜色,所以我们不需要将它们分开储存。对于光线相关得数据,设置了Light结构体。

对于相关得原理不再赘述,最后,本人已经把法向量从顶点着色器传到了片段着色器。可是,目前片段着色器里的计算都是在世界空间坐标中进行的。但是这不是简单地把它乘以一个模型矩阵就能搞定的。

首先,法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果本人打算把法向量乘以一个模型矩阵,就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,希望对它实施缩放和旋转变换。

其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此便不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响:
在这里插入图片描述
每当我们应用一个不等比缩放时(注意:等比缩放不会破坏法线,因为法线的方向没被改变,仅仅改变了法线的长度,而这很容易通过标准化来修复),法向量就不会再垂直于对应的表面了,这样光照就会被破坏。因此可使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix),它使用了一些线性代数的操作来移除对法向量错误缩放的影响。

与之对应,可以使用inverse和transpose函数自己生成这个法线矩阵,这两个函数对所有类型矩阵都有效。如下所示:
在这里插入图片描述

两种着色方法结果和分析

本实验使用得光照为一个立方体盒子,并且随时间进行移动,可更好得查看GLSL着色结果并对比,运行结果被保存成gif图片,可以通过作业附件查看。

Gouraud Shading

此方法的计算在顶点着色器中,如下所示,分别计算出环境光照、漫反射光照、镜面光照,第一种光照对物体的视觉影响较小,主要由后两种光照方式决定物体的“外观”。漫反射光照和镜面光照的效果和光线方向与物体表面的法向量方向有关。因此在下边的代码中,使用了多次的法向量变量,同时为了节省计算时间对法向量初始为单位向量(详细代码可在Gouraud_Shading.vs和Gouraud_Shading.fs中查看)。
在这里插入图片描述
在下图中可以看出使用Gouraud Shading着色方法会显示一些类似三角的特征,这是由于本方法对三角顶点进行渲染的方式所决定的。

在这里插入图片描述
放大其中的光照带来的高光区域,可以看到下图:

在这里插入图片描述
明显看出此着色方式的弊端,渲染出的整体结构会略又模糊并带来尖锐的感觉。

Phong Shading

和Gouraud Shadings方法类似,使用GLSL语言完成着色,但是需要在片段着色器中进行计算,以达到逐像素渲染的结果(详细代码可在Phong_Shading.vs和Phong_Shading.fs中查看)。具体的计算方法如下:

在这里插入图片描述
如下所示,使用逐像素的方法明显看出模型的表面较为光滑。

在这里插入图片描述
在下边两图中,我们明显看出对于细节的渲染,本方法得到了更好的结果。

在这里插入图片描述

结论

对于Phong Shading,它根据顶点的法向量插值计算出表面内各点的法向量,再根据光照模型逐像素计算光照值;速度最慢(大概是Gouraud的6~8倍),但效果最好(尤其是在处理高光情况下时);

对于Grouraud Shading,它是根据顶点法向量计算出光照,再插值计算出整个面的光照;由于内插值总小于顶点的最大值,故得到的高光部分只能在顶点出现,在高光面中甚至可以分辨出各个小的面元;因此可以通过充分细分来解决这一问题。速度与效果居中,应用比较广泛。

因此其不同点可总结为:

Gouraud明暗处理只在多边形顶点处采用Phong局部反射模型计算光强,而在多边形内的其他点采用双向线性插值,这样做的优点是高效,但是无法很好的处理镜面高光问题,依赖于其所在多面形的相对位置;

而Phong明暗处理,通过差值计算每个顶点的法向量(3次差值,在x,y,z三个方向分别进行差值计算),然后计算每个点上的光强值,这样效果好,但计算复杂,需要付出比Gouraud 4-5 倍的时间。通过高光情况下使用Phong shading,镜面反射弱的情况下使用Gouraud shading,可以既保证速度,又保证质量。

本文借鉴多篇网络上文章,详细出处已无法标注,若有侵权欢迎联系。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用MFC和OpenGL来读取并显示STL三维模型需要以下几个步骤: 1. 创建一个新的MFC项目,选择对话框应用程序作为模板。 2. 将OpenGL库文件添加到项目中,并进行相关配置,以便在对话框中使用OpenGL绘图。 3. 在对话框中添加一个静态文本框和一个自定义的OpenGL绘图区域。 4. 创建一个自定义的OpenGL绘图类,用于在绘图区域中显示STL模型。在该类中,定义一个透视投影矩阵、模型矩阵和视图矩阵,通过OpenGL函数来加载和渲染STL文件中的三角形。 5. 在对话框类中,添加一个自定义的OpenGL绘图类的成员变量,并在OnInitDialog()函数中对其进行初始化。 6. 在OnInitDialog()函数中,调用自定义OpenGL绘图类的初始化函数,传入STL模型文件的路径,将模型加载到内存中。 7. 在OnPaint()函数中,调用自定义OpenGL绘图类的绘制函数,将模型渲染到OpenGL绘图区域。 8. 处理对话框中的事件,例如按钮点击事件,调用自定义OpenGL绘图类的相应函数进行操作,如旋转、平移等。 9. 在应用程序类的InitInstance()函数中,创建对话框类的对象,并显示对话框。 通过以上步骤,可以实现在MFC应用程序中读取并显示STL三维模型。 ### 回答2: MFC是Microsoft Foundation Classes的缩写,是一种用于Windows平台的C++应用程序框架OpenGL是一种跨平台的图形API,用于开发图形和计算机视觉应用程序。STL(Standard Template Library)是C++的一部分,提供了一组通用的数据结构和算法。 要在MFC应用程序中读取并显示STL三维模型,可以按照以下步骤进行操作: 1. 首先,创建一个MFC应用程序项目,并在项目中添加OpenGL支持。这可以通过在项目属性中启用OpenGL选项来实现。 2. 在MFC应用程序中创建一个窗口,用于显示3D模型。这可以通过创建一个自定义的CStatic控件,并在其上绘制OpenGL图形来实现。 3. 接下来,编写代码来读取STL文件的数据。STL文件中包含三角形网格的顶点和法线信息。可以使用标准的文件处理函数来读取和解析STL文件的内容,并将其存储在一个适当的数据结构中。 4. 通过OpenGL的API函数,将STL模型数据绘制到窗口中。可以使用OpenGL顶点数组和绘制函数来绘制三角形网格的各个面。 5. 最后,将OpenGL绘制的结果显示在MFC窗口中。可以通过重载绘图消息处理函数,并在其中调用OpenGL的绘制函数来实现。 需要注意的是,由于MFC和OpenGL都是底层的图形库,对于初学者来说,可能需要一定的编程经验和对图形编程的理解。这个过程可能需要一些时间和尝试来完善和调试。 ### 回答3: 在 MFC 中使用 OpenGL 来读取并显示 STL 三维模型可以通过以下步骤实现: 1. 添加准备创建 OpenGL 窗口的代码。在 MFC 的窗体类中,可以使用 `COpenGLControl` 类或 `CView` 类来实现 OpenGL 窗口。 2. 创建一个函数来读取 STL 三维模型文件。你可以使用 STL 文件解析库,例如 `std::ifstream` 类来读取 STL 文件的内容。 3. 在创建的 OpenGL 窗口中,使用 OpenGL 函数来绘制三维模型。你可以使用 `glBegin`、`glEnd` 和 `glVertex3f` 等函数来绘制模型顶点和三角面片。 4. 在 OpenGL 窗口中,将读取到的 STL 三维模型数据传入 OpenGL 函数来进行绘制。你可以使用 `glColor3f` 函数来设置模型的颜色。 5. 在 MFC 的窗体类中,实现绘制 OpenGL 窗口的函数。通过重写 `OnDraw` 或 `OnPaint` 函数,调用 OpenGL 窗口的绘制函数。 6. 在 MFC 的窗体类中,重写 `OnCreate` 函数来初始化 OpenGL 窗口,调用读取和显示 STL 模型的函数。 7. 在 MFC 的窗体类中,为了在窗口中显示 OpenGL 窗口,重写 `OnSize` 函数,并调用 OpenGL 窗口的调整大小函数。 通过上述步骤,你可以在 MFC 中使用 OpenGL 来读取并显示 STL 三维模型。你可以进一步优化代码,例如添加相机控制、光照设置等来增强模型的显示效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值