网站落地页和普通网页,有没有做租赁的网站,公司图案图片大全,网站建设制作公司地址ok终于来到了立方体贴图了#xff0c;在这里面我们可以加入好看的天空包围盒#xff0c;这样的画我们的背景就不再是黑色的了#xff01;
首先#xff0c;立方体贴图和前面的sampler2D贴图一样#xff0c;不过是6个2D组成的立方体而已。
那么为什么要把6个组合在一起呢在这里面我们可以加入好看的天空包围盒这样的画我们的背景就不再是黑色的了
首先立方体贴图和前面的sampler2D贴图一样不过是6个2D组成的立方体而已。
那么为什么要把6个组合在一起呢立方体贴图可以通过一个方向向量来进行索引或者说采样。什么意思
我们类比一下之前在一个2D面上我们通过uv纹理坐标来找到对应的纹理值对吧。这里也一样不过是通过一个方向向量来获得。 假设我们有一个立方体、然后又有一个立方体贴图只要立方体中心在原点我就可以通过立方体表面的位置来确定对应的方向向量从而确定对应的立方体纹理坐标。 创建立方体贴图
和前面创建贴图一样。
unsigned int textureID;
glGenTextures(1, textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
不过因为有6个面所以需要贴6次。 int width, height, nrChannels;
unsigned char *data;
for(unsigned int i 0; i textures_faces.size(); i)
{data stbi_load(textures_faces[i].c_str(), width, height, nrChannels, 0);glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
这里我们有一个叫做textures_faces的vector它包含了立方体贴图所需的所有纹理路径并以表中的顺序排列。这将为当前绑定的立方体贴图中的每个面生成一个纹理。
然后还需要设置环绕和过滤方式
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_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);
可以看到这里出现了个R坐标其实就是纹理的第三个维度和位置的z一样。
最后在片段着色器中要使用一个samplerCube就行
天空盒 我们现在下载一个天空盒到我们的项目中。 加载天空盒
和之前把图片加载到gpu一样现在是加载一个cubemap。
unsigned int LoadCubeMapToGPU(std::vectorstd::string faces) {unsigned int textureID;glGenTextures(1, textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);int width, height, nrChannels;for (unsigned int i 0; i faces.size(); i) {unsigned char* data stbi_load(faces[i].c_str(), width, height, nrChannels, 0);if (data) {glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X i,0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);stbi_image_free(data);}else {std::cout Cubemap texture failed to load at path: faces[i] std::endl;stbi_image_free(data);}}glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_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);return textureID;};然后需要存一个skybox的图片vector
float skyboxVertices[] {// positions -1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, -1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, -1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, -1.0f,1.0f, 1.0f, -1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, 1.0f
};std::vectorstd::string faces
{right.jpg,left.jpg,top.jpg,bottom.jpg,front.jpg,back.jpg
};然后需要给这cube给一个VAO VBO unsigned int cubemapTexture LoadCubeMapToGPU(faces);unsigned int skyboxVAO, skyboxVBO;glGenBuffers(1, skyboxVBO);glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), skyboxVertices, GL_STATIC_DRAW);glGenVertexArrays(1, skyboxVAO);glBindVertexArray(skyboxVAO);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
最后写一个skybox的shader就好。
#version 330 corelayout(location 0) in vec3 aPos;out vec3 TexCoords;uniform mat4 projMat;
uniform mat4 viewMat;void main(){gl_Position projMat*viewMat*vec4(aPos,1.0f);TexCoords aPos;
}#version 330 core
out vec4 FragColor;
in vec3 TexCoords;uniform samplerCube skybox;void main(){FragColor texture(skybox,TexCoords);
}可以看到在顶点着色器里面我们把坐标信息作为纹理信息传给了片段着色器这是因为cubemap的纹理坐标就是一个向量所以我们直接把原点在世界中心的立方体的各点坐标当作纹理向量给片段着色器就好。 最后是loop中我们要记住画天空盒我们得先画而且画的时候需要把深度测试关了这样的画之后的物体就是画在背景之前了。
loop中这么写。
//第一阶段
...
//clear screen
...
//skybox
glDepthMask(GL_FALSE);
skyboxShader-use();iewMat camera.GetViewMatrix();
projMat glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyboxShader-ID, viewMat), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(skyboxShader-ID, projMat), 1, GL_FALSE, glm::value_ptr(projMat));glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
...
//木块 机器人 其他 不过结果不对我们希望天空盒是以玩家为中心的这样不论玩家移动了多远天空盒都不会变近让玩家产生周围环境非常大的印象。然而当前的观察矩阵会旋转、缩放和位移来变换天空盒的所有位置所以当玩家移动的时候立方体贴图也会移动我们希望移除观察矩阵中的位移部分让移动不会影响天空盒的位置向量。
在基础光照小节中我们通过取4x4矩阵左上角的3x3矩阵来移除变换矩阵的位移部分。我们可以将观察矩阵转换为3x3矩阵移除位移再将其转换回4x4矩阵来达到类似的效果。
修改viewMat
glm::mat4 view glm::mat4(glm::mat3(camera.GetViewMatrix())); 优化 在刚刚的过程中是先画天空盒在这中间关闭了深度测试。是因为我们想让其他物体画在天空盒上。不过这样不过高效是因为在画天空盒的时候会对每个像素都跑一次片段着色器。
所以我们要最后渲染天空盒。但是怎么做呢提前深度测试可以做到我们先渲染其他东西然后再提前深度测试通过的地方渲染天空盒就行了。
不过要知道的我们的天空盒只有1x1x1其他东西很容易就在天空盒上所以我们需要欺骗深度缓冲或者说让他认为天空盒的深度值有最大要让天空盒最深。
在坐标系统小节中我们说过透视除法是在顶点着色器运行之后执行的将gl_Position的xyz坐标除以w分量。我们又从深度测试小节中知道相除结果的z分量等于顶点的深度值。使用这些信息我们可以将输出位置的z分量等于它的w分量让z分量永远等于1.0这样子的话当透视除法执行之后z分量会变为w / w 1.0。 修改顶点着色器
void main(){vec4 pos projMat*viewMat*vec4(aPos,1.0);gl_Position pos.xyww;TexCoords aPos;
}
修改main中的顺序把天空盒放在其他物体之后但是是再透明物体之前。
我们还要改变一下深度函数将它从默认的GL_LESS改为GL_LEQUAL。
深度缓冲将会填充上天空盒的1.0值所以我们需要保证天空盒在值小于或等于深度缓冲而不是小于时通过深度测试。
结果和之前一样 环境映射
现在我们可以通过这个环境纹理来给物体一些反射折射的属性了
反射 这里灰色向量是我们的观察方向向量、红色是法向方向绿色是反射方向可以通过reflect()来获得之后把这个绿色的向量作为天空盒的纹理方向坐标就可以获得立方体贴图了。
所以重新写一个反射天空盒的箱子着色器。
#version 330 corelayout(location 0) in vec3 aPos;
layout(location 1) in vec3 aNormal;out vec3 Position;
out vec3 Normal;uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;void main(){Normal mat3(transpose(inverse(modelMat)))*aNormal;Position vec3(modelMat* vec4(aPos,1.0f));gl_Position projMat * viewMat * modelMat*vec4(aPos,1.0f);
}#version 330 corein vec3 Position;
in vec3 Normal;out vec4 FragColor;uniform vec3 cameraPos;
uniform samplerCube skybox;void main(){vec3 viewDir normalize(Position-cameraPos);vec3 refDir reflect(viewDir,normalize(Normal));FragColor vec4(texture(skybox,refDir).rgb,1.0f);
}
这里注意看下因为片段着色器需要法向量信息所以需要通过顶点着色器传个normal过去但是这里是需要经过model变换的也不能直接变换。所以需要通过
Normal mat3(transpose(inverse(model))) * aNormal;
这样才能得到正确的法向量。
然后在main中 建立新的shader绑定VAOVBO之后再loop中画一个这样的cube记得要把camera的position传进去。
//反射天空盒cubeskyboxReflectShader-use();modelMat glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 5.0f));viewMat camera.GetViewMatrix();projMat projMat glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader-ID, modelMat), 1, GL_FALSE, glm::value_ptr(modelMat));glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader-ID, viewMat), 1, GL_FALSE, glm::value_ptr(viewMat));glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader-ID, projMat), 1, GL_FALSE, glm::value_ptr(projMat));glUniform3f(glGetUniformLocation(skyboxReflectShader-ID, cameraPos), camera.Position.x, camera.Position.y, camera.Position.z);glBindVertexArray(skyBoxReflectionVAO);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);
记得再画之前绑定一下天空盒的材质。 这是怎么少一个面呢是因为面剔除的原因这里的顶点坐标没有按照逆时针的顺序排列。
修改一下在画的时候把面剔除关了。 OK了 。
如果直接另外画一个机器人模型但是用这里的reflectShader的话 这看起来非常棒但在现实中大部分的模型都不具有完全反射性。我们可以引入反射贴图(Reflection Map)来给模型更多的细节。与漫反射和镜面光贴图一样反射贴图也是可以采样的纹理图像它决定这片段的反射性。通过使用反射贴图我们可以知道模型的哪些部分该以什么强度显示反射。在本节的练习中将由你来为我们之前创建的模型加载器中引入反射贴图显著提升纳米装模型的细节。 折射 折射通过refract函数实现。需要一个法向量一个观察方向两个材质之间的折射率。
现在修改下片段着色器就好。
void main()
{ float ratio 1.00 / 1.52;vec3 I normalize(Position - cameraPos);vec3 R refract(I, normalize(Normal), ratio);FragColor vec4(texture(skybox, R).rgb, 1.0);
} 它不太能显示折射的效果现在看起来只是有点像一个放大镜。对纳米装使用相同的着色器却能够展现出了我们期待的效果一个类玻璃的物体。
你可以想象出有了光照、反射、折射和顶点移动的正确组合你可以创建出非常漂亮的水。注意如果要想获得物理上精确的结果我们还需要在光线离开物体的时候再次折射现在我们使用的只是单面折射(Single-side Refraction)但它对大部分场合都是没问题的。 动态环境贴图
现在我们使用的都是静态图像的组合来作为天空盒看起来很不错但它没有在场景中包括可移动的物体。我们一直都没有注意到这一点因为我们只使用了一个物体。如果我们有一个镜子一样的物体周围还有多个物体镜子中可见的只有天空盒看起来就像它是场景中唯一一个物体一样。
通过使用帧缓冲我们能够为物体的6个不同角度创建出场景的纹理并在每个渲染迭代中将它们储存到一个立方体贴图中。之后我们就可以使用这个动态生成的立方体贴图来创建出更真实的包含其它物体的反射和折射表面了。这就叫做动态环境映射(Dynamic Environment Mapping)因为我们动态创建了物体周围的立方体贴图并将其用作环境贴图。
虽然它看起来很棒但它有一个很大的缺点我们需要为使用环境贴图的物体渲染场景6次这是对程序是非常大的性能开销。现代的程序通常会尽可能使用天空盒并在可能的时候使用预编译的立方体贴图只要它们能产生一点动态环境贴图的效果。虽然动态环境贴图是一个很棒的技术但是要想在不降低性能的情况下让它工作还是需要非常多的技巧的。