LearnOpenGL学习笔记—高级OpenGL 03:混合

tech2025-03-19  5

LearnOpenGL学习笔记—高级OpenGL 03:混合

1 混合概念2 渲染完全透明和完全不透明2 混合实现3 渲染半透明纹理4 注意渲染的顺序5 代码

【项目地址:点击这里这里这里】

本节对应官网学习内容:混合

1 混合概念

在OpenGL中,混合(Blending)通常可以实现物体透明度(Transparency)。 透明是指一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度地结合。 一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的颜色还包含了玻璃之后所有物体的颜色。 这也是混合这一名字的出处,我们混合(Blend)(不同物体的)多种颜色为一种颜色。

下图展示了全透明窗户和带有颜色的窗户 透明的物体可以是完全透明的(让所有的颜色穿过),或者是半透明的(它让颜色通过,同时也会显示自身的颜色)。

一个物体的透明度是通过它颜色的aplha值来决定的,alpha颜色值是颜色向量的第四个分量。 在这个教程之前我们都将这个第四个分量设置为1.0,让这个物体的透明度为0.0,也就是不透明 而当alpha值为0.0时物体将会是完全透明的 当alpha值为0.5时,物体的颜色有50%是来自物体自身的颜色,50%来自背后物体的颜色。

我们目前一直使用的纹理有三个颜色分量:红、绿、蓝。 但一些材质会有一个内嵌的alpha通道,对每个纹素(Texel)都包含了一个alpha值。这个alpha值精确地告诉我们纹理各个部分的透明度。 比如说,下面这个窗户纹理 玻璃部分的alpha值为0.25(它在一般情况下是完全的红色,但由于它有75%的透明度,能让很大一部分的背景颜色穿过,让它看起来不那么红了) 边框的alpha值是0.0。 我们最后会把这个玻璃加到场景中,不过现在先试着实现一下完全透明和完全不透明

2 渲染完全透明和完全不透明

有些图片并不需要半透明,只需要根据纹理颜色值,但是它要显示一部分,或者不显示一部分,没有中间情况。

比如说下面的草,如果想不太费劲地创建草这种东西,我们要将一个草的纹理贴在一个2D四边形(Quad)上,然后将这个四边形放到场景中。

然而,草的形状和2D四边形的形状并不完全相同,所以我们只想显示草纹理的某些部分,而忽略剩下的部分。

下面这个草纹理正是这样的,它要么是完全不透明的(alpha值为1.0),要么是完全透明的(alpha值为0.0),没有中间情况。 我们可以看到,只要不是草的部分,这个图片都是透明的。

我们尝试在代码中实现它,我们把这张图放到文件夹中,项目添加现有项,然后修改代码 首先我们需要草的位置数据,以及方块面的数据

float grassVertices[] = { //position //normal //TexCoord -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, }; glm::vec3 grassPosition[] = { glm::vec3(-1.5f, 0.0f, 1.0f), glm::vec3(1.5f, 0.0f, 1.0f), };

建立简单的草的片段着色器

#version 330 core in vec4 vertexColor; in vec2 TexCoord; struct Material { vec3 ambient; sampler2D diffuse; sampler2D specular; sampler2D emission; float shininess; }; uniform Material material; out vec4 FragColor; void main(){ FragColor = texture(material.diffuse, TexCoord); }

回到main.cpp中,宣告草的shader以及草的材质

Shader* grass = new Shader("vertexSource.vert", "grass.frag"); Material * Grass = new Material(grass, LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), glm::vec3(1.0f, 1.0f, 1.0f), 150.0f); Mesh grassMesh(grassVertices);

我们打算在渲染循环里,按照机器人→草→方块与边框的顺序绘制 在我们之前绘制机器人的代码后面加上一个循环

model.Draw(myShader); //画草 for (unsigned int i = 0; i < 2; i++) { //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), grassPosition[i]); modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program grass->use(); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); grassMesh.DrawSliceArray(Grass->shader, Grass->diffuse, Grass->specular, Grass->emission); }

注意这里我们调用了新的Mesh类的方法,之前Mesh类只准备了方块的绘制,这次我们在Mesh类添加片面的绘制方法 在Mesh.cpp中添加下列代码后,也记得回到头文件宣告一下

void Mesh::DrawSliceArray(Shader * shader, int diffuse, int specular, int emission) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuse); shader->SetUniform1i("material.diffuse", 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, specular); shader->SetUniform1i("material.specular", 1); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, emission); shader->SetUniform1i("material.emission", 2); // 绘制网格 glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0,6); glBindVertexArray(0); glActiveTexture(GL_TEXTURE0); }

经过以上步骤,我们就把刚才的草放进了我们的场景中

出现这种情况是因为OpenGL默认是不知道怎么处理alpha值的,更不知道什么时候应该丢弃片段,我们需要自己手动来弄。 有了着色器,还是非常容易实现的,GLSL给了我们discard命令,一旦被调用,它就会保证片段不会被进一步处理,所以就不会进入颜色缓冲。 有了这个指令,我们就能够在片段着色器中检测一个片段的alpha值是否低于某个阈值,如果是的话,则丢弃这个片段,就好像它不存在一样,我们对grass.frag做如下修改

void main(){ vec4 texColor = texture(material.diffuse, TexCoord); if(texColor.a < 0.5) discard; FragColor = texColor; }

我们得到效果

当采样纹理的边缘的时候,OpenGL会对边缘的值和纹理下一个重复的值进行插值(因为我们将它的环绕方式设置为了GL_REPEAT)。 这通常是没问题的,但是由于我们使用了透明值,纹理图像的顶部将会与底部边缘的纯色值进行插值,就会产生一个半透明的有色边框,所以可能会看见环绕着纹理的四边形。要想避免这个,每当使用alpha纹理的时候,请将纹理的环绕方式设置为GL_CLAMP_TO_EDGE(本次是直接调大了判断的alpha范围): glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

2 混合实现

虽然我们可以直接丢弃片段,但它不能让我们渲染半透明的图像。 要想渲染有多个透明度级别的图像,我们需要启用混合(Blending)。和OpenGL大多数的功能一样,我们可以启用GL_BLEND来启用混合:

glEnable(GL_BLEND);

OpenGL中的混合是通过下面这个方程来实现的:

C ‾ r e s u l t = C ‾ s o u r c e ∗ F s o u r c e + C ‾ d e s t i n a t i o n ∗ F d e s t i n a t i o n \overline C result=\overline C source∗Fsource+\overline C destination∗Fdestination Cresult=CsourceFsource+CdestinationFdestination

C ‾ s o u r c e \overline Csource Csource:源颜色向量。这是纹理的颜色向量。 KaTeX parse error: Undefined control sequence: \overlineC at position 1: \̲o̲v̲e̲r̲l̲i̲n̲e̲C̲¯destination:目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。 Fsource:源因子值。指定了alpha值对源颜色的影响。 Fdestination:目标因子值。指定了alpha值对目标颜色的影响。

片段着色器运行完成后,并且所有的测试都通过之后,这个混合方程(Blend Equation)才会应用到片段的颜色输出与当前颜色缓冲中的值(当前片段之前,储存的之前片段的颜色)上。 源颜色和目标颜色将会由OpenGL自动设定,但源因子和目标因子的值可以由我们来决定。我们先来看一个简单的例子: 我们有两个方形,我们希望将这个半透明的绿色方形绘制在红色方形之上。红色的方形将会是目标颜色(所以它应该先在颜色缓冲中),我们将要在这个红色方形之上绘制这个绿色方形。

问题来了:我们将因子值设置为什么? 我们想让绿色方形乘以它的alpha值,所以我们想要将Fsrc设置为源颜色向量的alpha值,也就是0.6。 接下来就应该清楚了,目标方形的贡献应该为剩下的alpha值。 如果绿色方形对最终颜色贡献了60%,那么红色方块应该对最终颜色贡献了40%, 结果就是重叠方形的片段包含了一个60%绿色,40%红色的一种的颜色: 最终的颜色将会被储存到颜色缓冲中,替代之前的颜色。

我们该如何让OpenGL使用这样的因子呢?正好有一个专门的函数,叫做glBlendFunc。

glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源和目标因子。 OpenGL为我们定义了很多个选项,下面是大部分最常用的选项。

注意常数颜色向量 C ‾ c o n s t a n t \overline C constant Cconstant可以通过glBlendColor函数来另外设置。

为了达到之前两个方形的混合结果,我们需要使用源颜色向量的alpha作为源因子,使用1−alpha作为目标因子。 这将会产生以下的glBlendFunc:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

也可以使用glBlendFuncSeparate为RGB和alpha通道分别设置不同的选项:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); 这个函数和我们之前设置的那样设置了RGB分量,但这样只能让最终的alpha分量被源颜色向量的alpha值所影响到。

OpenGL给了我们更多的灵活性,允许我们改变方程中源和目标部分的运算符。 当前源和目标是相加的,但如果愿意的话,我们也可以让它们相减。 glBlendEquation(GLenum mode)允许我们设置运算符,它提供了三个选项: 通常我们都可以省略调用glBlendEquation,因为GL_FUNC_ADD对大部分的操作来说都是我们希望的混合方程,但是其他也可以。

3 渲染半透明纹理

既然我们已经知道OpenGL是如何处理混合的了,于是接下来就是要进行实战 我们将会在场景中添加几个半透明的窗户,用渲染草的纹理的方法渲染玻璃 可以下载这个玻璃的图片 首先,在初始化时我们启用混合,并设定相应的混合函数:

glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

对应的片段着色器

#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D texture1; void main() { FragColor = texture(texture1, TexCoords); }

草和玻璃都是一个面,所以三角形坐标不改了,我们把玻璃的位置改一下

glm::vec3 glassPosition[] = { glm::vec3(-1.5f, 1.0f, 2.0f), glm::vec3(-1.2f, 0.9f, 1.5f), glm::vec3(0.5f, 1.5f, 1.0f), };

在main.cpp中进行和渲染草时类似的操作,做玻璃的准备

Shader* glass = new Shader("vertexSource.vert", "glass.frag"); Material * Glass = new Material(glass, LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA), LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA), LoadImageToGPU("blending_transparent_window.png", GL_RGB, GL_RGB), glm::vec3(1.0f, 1.0f, 1.0f), 150.0f); //继续用草的方块形状 Mesh glassMesh(grassVertices);

在之前绘制机器人和方块的渲染循环后面,我们加上玻璃的循环

//画玻璃 for (unsigned int i = 0; i < 3; i++) { //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), glassPosition[i]); modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program glass->use(); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); grassMesh.DrawSliceArray(Glass->shader, Glass->diffuse, Glass->specular, Glass->emission); }

在这一次的渲染中,我们就不考虑模板测试的边框了,因为模板测试的边框不适合在玻璃后面显示

我们绘制了结果,从玻璃是可以看到后面,但是很奇怪

最前面窗户的透明部分遮蔽了背后的窗户,但是从后面看又能看见前面的窗户。 为什么呢?

因为深度测试和混合一起使用的话会产生一些麻烦。

当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。 结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。

所以我们不能随意地决定如何渲染窗户,让深度缓冲解决所有的问题了。 这也是混合变得有些麻烦的部分。 要想保证窗户中能够显示它们背后的窗户,我们需要首先绘制背后的这部分窗户。 这也就是说在绘制的时候,我们必须先手动将窗户按照最远到最近来排序,再按照顺序渲染。

注意,对于草这种全透明的物体,我们可以选择丢弃透明的片段而不是混合它们,这样就解决了这些头疼的问题(没有深度问题)。

4 注意渲染的顺序

要想让混合在多个物体上工作,我们需要最先绘制最远的物体,最后绘制最近的物体。

普通不需要混合的物体仍然可以使用深度缓冲正常绘制,所以它们不需要排序。

但我们仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了。

当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:

先绘制所有不透明的物体。 对所有透明的物体排序。 按顺序绘制所有透明的物体。

排序透明物体的一种方法是,从观察者视角获取物体的距离。

这可以通过计算摄像机位置向量和物体的位置向量之间的距离所获得。

接下来我们把距离和它对应的位置向量存储到一个STL库的map数据结构中。map会自动根据键值(Key)对它的值排序,所以只要我们添加了所有的位置,并以它的距离作为键,它们就会自动根据距离值排序了。

include <map> ...... //画玻璃 //利用map排序 std::map<float, glm::vec3 > sorted; int length = sizeof(glassPosition) / sizeof(glassPosition[0]); for (unsigned int i = 0; i < length; i++) { float distance = glm::length(camera.Position - glassPosition[i]); sorted[distance] = glassPosition[i]; } //在渲染的时候,我们将以逆序(从远到近)从map中获取值 //使用了map的一个反向迭代器(Reverse Iterator),反向遍历其中的条目,second会得到value,讲每个窗户四边形位移到对应的窗户位置上。 for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) { //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), it->second); modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program glass->use(); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); grassMesh.DrawSliceArray(Glass->shader, Glass->diffuse, Glass->specular, Glass->emission); }

从前或者后都能正确显示玻璃,有透明效果

5 代码

本次只给出main.cpp的代码就好,其他的修改在上述文章都有提及,修改不大

#include <iostream> #define GLEW_STATIC #include <GL/glew.h> #include <GLFW/glfw3.h> #include "Shader.h" #include "Camera.h" #include "Material.h" #include "LightDirectional.h" #include "LightPoint.h" #include "LightSpot.h" #include "Mesh.h" #include "Model.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <map> #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> //发光正方体的数据 #pragma region Model Data float vertices[] = { //position //normal //TexCoord -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, }; glm::vec3 cubePositions[] = { glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(2.0f, 5.0f, -15.0f), glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f), glm::vec3(2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f), glm::vec3(1.3f, -2.0f, -2.5f), glm::vec3(1.5f, 2.0f, -2.5f), glm::vec3(1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f) }; float grassVertices[] = { //position //normal //TexCoord -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, }; glm::vec3 grassPosition[] = { glm::vec3(-1.5f, 0.0f, 1.0f), glm::vec3(1.5f, 0.0f, 1.0f), }; glm::vec3 glassPosition[] = { glm::vec3(-1.5f, 1.0f, 2.0f), glm::vec3(-1.2f, 0.9f, 1.5f), glm::vec3(0.5f, 1.5f, 1.0f), }; #pragma endregion #pragma region Camera Declare //建立camera glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); Camera camera(cameraPos, cameraTarget, cameraUp); #pragma endregion #pragma region Light Declare //position angle color LightDirectional lightD ( glm::vec3(135.0f, 0,0), glm::vec3(0.8f, 0.8f, 0.8f)); LightPoint lightP0 (glm::vec3(1.0f, 0,0), glm::vec3(1.0f, 0, 0)); LightPoint lightP1 (glm::vec3(0, 1.0f, 0), glm::vec3(0, 1.0f, 0)); LightPoint lightP2 (glm::vec3(0, 0, 1.0f), glm::vec3(0, 0, 1.0f)); LightPoint lightP3 (glm::vec3(0.0f, 0.0f, -7.0f), glm::vec3(1.0f, 1.0f, 1.0f)); LightSpot lightS (glm::vec3(0, 8.0f,0.0f), glm::vec3(135.0f, 0, 0), glm::vec3(0, 0,1.5f)); #pragma endregion #pragma region Input Declare //移动用 float deltaTime = 0.0f; // 当前帧与上一帧的时间差 float lastFrame = 0.0f; // 上一帧的时间 void processInput(GLFWwindow* window) { //看是不是按下esc键 然后退出 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } //更流畅点的摄像机系统 if (deltaTime != 0) { camera.cameraPosSpeed = 5 * deltaTime; } //camera前后左右根据镜头方向移动 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.PosUpdateForward(); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.PosUpdateBackward(); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.PosUpdateLeft(); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.PosUpdateRight(); if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) camera.PosUpdateUp(); if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) camera.PosUpdateDown(); } float lastX; float lastY; bool firstMouse = true; //鼠标控制镜头方向 void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse == true) { lastX = xpos; lastY = ypos; firstMouse = false; } float deltaX, deltaY; float sensitivity = 0.05f; deltaX = (xpos - lastX)*sensitivity; deltaY = (ypos - lastY)*sensitivity; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(deltaX, deltaY); }; //缩放 float fov = 45.0f; void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { if (fov >= 1.0f && fov <= 45.0f) fov -= yoffset; if (fov <= 1.0f) fov = 1.0f; if (fov >= 45.0f) fov = 45.0f; } #pragma endregion unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format) { unsigned int TexBuffer; glGenTextures(1, &TexBuffer); glBindTexture(GL_TEXTURE_2D, TexBuffer); // 为当前绑定的纹理对象设置环绕、过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // 加载并生成纹理 int width, height, nrChannel; unsigned char *data = stbi_load(filename, &width, &height, &nrChannel, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { printf("Failed to load texture"); } stbi_image_free(data); return TexBuffer; } int main(int argc, char* argv[]) { std::string exePath = argv[0]; #pragma region Open a Window glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //Open GLFW Window GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL); if(window == NULL) { printf("Open window failed."); glfwTerminate(); return - 1; } glfwMakeContextCurrent(window); //关闭鼠标显示 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //回调函数监听鼠标 glfwSetCursorPosCallback(window, mouse_callback); //回调函数监听滚轮 glfwSetScrollCallback(window, scroll_callback); //Init GLEW glewExperimental = true; if (glewInit() != GLEW_OK) { printf("Init GLEW failed."); glfwTerminate(); return -1; } glViewport(0, 0, 800, 600); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_STENCIL_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #pragma endregion #pragma region Init Shader Program Shader* myShader = new Shader("vertexSource.vert", "fragmentSource.frag"); Shader* border = new Shader("vertexSource.vert", "Border.frag"); Shader* grass = new Shader("vertexSource.vert", "grass.frag"); Shader* glass = new Shader("vertexSource.vert", "glass.frag"); #pragma endregion #pragma region Init Material Material * myMaterial = new Material(myShader, LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA), LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA), LoadImageToGPU("matrix.jpg", GL_RGB, GL_RGB), glm::vec3 (1.0f, 1.0f, 1.0f), 150.0f); Material * Grass = new Material(grass, LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), LoadImageToGPU("grass.png", GL_RGBA, GL_RGBA), glm::vec3(1.0f, 1.0f, 1.0f), 150.0f); Material * Glass = new Material(glass, LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA), LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA), LoadImageToGPU("blending_transparent_window.png", GL_RGBA, GL_RGBA), glm::vec3(1.0f, 1.0f, 1.0f), 150.0f); #pragma endregion #pragma region Init and Load Models to VAO,VBO Mesh cube(vertices); Mesh grassMesh(grassVertices); Mesh glassMesh(grassVertices); Model model(exePath.substr(0, exePath.find_last_of('\\')) + "\\model\\nanosuit.obj"); #pragma endregion #pragma region Init and Load Textures //坐标翻转 stbi_set_flip_vertically_on_load(true); #pragma endregion #pragma region Prepare MVP matrices //model glm::mat4 modelMat; //view glm::mat4 viewMat; //projection glm::mat4 projMat; #pragma endregion while (!glfwWindowShouldClose(window)) { //Process Input processInput(window); //Clear Screen glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //方块 0代表模型 for (unsigned int i = 0; i < 11; i++) { if (i != 0) { int k = i - 1; //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), cubePositions[k]); float angle = 20.0f * (k); //float angle = 20.0f * i + 50*glfwGetTime(); modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); } else { modelMat = glm::translate(glm::mat4(1.0f), { 0,-10,-5 }); } //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program myShader->use(); //Set Material -> Uniforms #pragma region Uniform glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); glUniform3f(glGetUniformLocation(myShader->ID, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z); glUniform1f(glGetUniformLocation(myShader->ID, "time"), glfwGetTime()); glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 1.0f, 1.0f); glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 0.1f, 0.1f, 0.1f); glUniform3f(glGetUniformLocation(myShader->ID, "lightD.color"), lightD.color.x, lightD.color.y, lightD.color.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightD.dirToLight"), lightD.direction.x, lightD.direction.y, lightD.direction.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightP0.pos"), lightP0.position.x, lightP0.position.y, lightP0.position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightP0.color"), lightP0.color.x, lightP0.color.y, lightP0.color.z); glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.constant"), lightP0.constant); glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.linear"), lightP0.linear); glUniform1f(glGetUniformLocation(myShader->ID, "lightP0.quadratic"), lightP0.quadratic); glUniform3f(glGetUniformLocation(myShader->ID, "lightP1.pos"), lightP1.position.x, lightP1.position.y, lightP1.position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightP1.color"), lightP1.color.x, lightP1.color.y, lightP1.color.z); glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.constant"), lightP1.constant); glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.linear"), lightP1.linear); glUniform1f(glGetUniformLocation(myShader->ID, "lightP1.quadratic"), lightP1.quadratic); glUniform3f(glGetUniformLocation(myShader->ID, "lightP2.pos"), lightP2.position.x, lightP2.position.y, lightP2.position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightP2.color"), lightP2.color.x, lightP2.color.y, lightP2.color.z); glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.constant"), lightP2.constant); glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.linear"), lightP2.linear); glUniform1f(glGetUniformLocation(myShader->ID, "lightP2.quadratic"), lightP2.quadratic); glUniform3f(glGetUniformLocation(myShader->ID, "lightP3.pos"), lightP3.position.x, lightP3.position.y, lightP3.position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightP3.color"), lightP3.color.x, lightP3.color.y, lightP3.color.z); glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.constant"), lightP3.constant); glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.linear"), lightP3.linear); glUniform1f(glGetUniformLocation(myShader->ID, "lightP3.quadratic"), lightP3.quadratic); glUniform3f(glGetUniformLocation(myShader->ID, "lightS.pos"), lightS.position.x, lightS.position.y, lightS.position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightS.color"), lightS.color.x, lightS.color.y, lightS.color.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightS.dirToLight"), lightS.direction.x, lightS.direction.y, lightS.direction.z); glUniform1f(glGetUniformLocation(myShader->ID, "lightS.constant"), lightS.constant); glUniform1f(glGetUniformLocation(myShader->ID, "lightS.linear"), lightS.linear); glUniform1f(glGetUniformLocation(myShader->ID, "lightS.quadratic"), lightS.quadratic); glUniform1f(glGetUniformLocation(myShader->ID, "lightS.cosPhyInner"), lightS.cosPhyInner); glUniform1f(glGetUniformLocation(myShader->ID, "lightS.cosPhyOuter"), lightS.cosPhyOuter); #pragma endregion myMaterial->shader->SetUniform3f("material.ambient", myMaterial->ambient); myMaterial->shader->SetUniform1f("material.shininess", myMaterial->shininess); if (i == 0) { glStencilMask(0x00); // 记得保证我们在绘制机器人的时候不会更新模板缓冲 model.Draw(myShader); //画草 for (unsigned int i = 0; i < 2; i++) { //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), grassPosition[i]); modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program grass->use(); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(grass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); grassMesh.DrawSliceArray(Grass->shader, Grass->diffuse, Grass->specular, Grass->emission); } } else { //模板测试绘制边框 方块与方块边框 //glStencilFunc(GL_ALWAYS, 1, 0xFF); // 更新模板缓冲函数,所有的片段都要写入模板 //glStencilMask(0xFF); // 启用模板缓冲写入 正常绘制十个正方体,而后记录模板值 cube.DrawArray(myMaterial->shader, myMaterial->diffuse, myMaterial->specular, myMaterial->emission); 现在模板缓冲在箱子被绘制的地方都更新为1了,我们将要绘制放大的箱子,也就是绘制边框 //glStencilFunc(GL_NOTEQUAL, 1, 0xFF); //glStencilMask(0x00); // 禁止模板缓冲的写入 // // // glDisable(GL_DEPTH_TEST); //border->use(); // Set Model matrix //modelMat = glm::translate(glm::mat4(1.0f), cubePositions[i-1]); //float angle = 20.0f * (i-1); //modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); //modelMat = glm::scale(modelMat, glm::vec3(1.2, 1.2, 1.2)); //glUniformMatrix4fv(glGetUniformLocation(border->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); //glUniformMatrix4fv(glGetUniformLocation(border->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); //glUniformMatrix4fv(glGetUniformLocation(border->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); // 因为之前设置了GL_NOTEQUAL,它会保证我们只绘制箱子上模板值不为1的部分 //cube.DrawArray(border,1,1,1); //glStencilMask(0xFF); // //glEnable(GL_DEPTH_TEST); } } //画玻璃 //利用map排序 std::map<float, glm::vec3 > sorted; int length = sizeof(glassPosition) / sizeof(glassPosition[0]); for (unsigned int i = 0; i < length; i++) { float distance = glm::length(camera.Position - glassPosition[i]); sorted[distance] = glassPosition[i]; } //在渲染的时候,我们将以逆序(从远到近)从map中获取值 //使用了map的一个反向迭代器(Reverse Iterator),反向遍历其中的条目,second会得到value,讲每个窗户四边形位移到对应的窗户位置上。 for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) { //Set Model matrix modelMat = glm::translate(glm::mat4(1.0f), it->second); modelMat = glm::rotate(modelMat, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); //Set view matrix viewMat = camera.GetViewMatrix(); //Set projection matrix projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); //Set Material -> Shader Program glass->use(); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat)); glUniformMatrix4fv(glGetUniformLocation(glass->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat)); grassMesh.DrawSliceArray(Glass->shader, Glass->diffuse, Glass->specular, Glass->emission); } //Clean up prepare for next render loop glfwSwapBuffers(window); glfwPollEvents(); //Recording the time float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; } //Exit program glfwTerminate(); return 0; }
最新回复(0)