前置:OpenGL基础20:镜面光照

一、反射

不一定所有的光源都是简单的白光,不仅如此,光线也是可以多次反射的,例如一面镜子,可以从中看到远处的风景,一些金属材质的物体表面也会反射周围物体的光

这主要就是着色器的改动,和漫反射以及镜面反射一样,还有一种贴图叫做反射贴图,当然了,是否是贴图只是着色器写法上的问题,有了之前的经验,搞定这个不是问题

反射的计算方式和之前的镜面反射很像,,着色器如下:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
out vec3 normalIn;
out vec3 fragPosIn;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{gl_Position = projection * view * model * vec4(position, 1.0f);fragPosIn = vec3(model * vec4(position, 1.0f));normalIn = mat3(transpose(inverse(model))) * normal;
}///#version 330 core
in vec3 normalIn;
in vec3 fragPosIn;
out vec4 color;
uniform vec3 cameraPos;
uniform samplerCube skybox;
void main()
{float ratio = 1.00 / 1.52;vec3 I = normalize(fragPosIn - cameraPos);vec3 R = reflect(I, normalize(normalIn));color = texture(skybox, R);
}

可以在场景中放一块六边形镜子,它会反射远处的天空盒:

如果一些模型表面不是全反射,必然需要反射贴图(reflection map),并从中获取反射率

二、折射

折射和反射一样简单,插入水中的筷子会变形,这就是折射

折射遵守斯涅尔定律,看起来就像这样:

观察向量的方向在进入物体表面后有轻微弯曲,弯曲的角度取决于折射指数。每个材质都有自己的折射指数,最常见的:水的折射指数为1.33,玻璃则为1.52、而空气的折射指数为1.00

只需要略微修改片段着色器就可以实现折射:

  • refract(I, normal, ratio): 为入射角, 为法向量,ratio为折射指数
#version 330 core
in vec3 normalIn;
in vec3 fragPosIn;
out vec4 color;
uniform vec3 cameraPos;
uniform samplerCube skybox;
void main()
{float ratio = 1.00 / 1.52;vec3 I = normalize(fragPosIn - cameraPos);vec3 R = refract(I, normalize(normalIn), ratio);color = texture(skybox, R);
}

三、模型贴图

前置:OpenGL基础28:模型

如果尝试使用之前的Mesh类和Model类读取模型,又或者是复制的openGL.cn以及网上的大部分mesh类以及model类,那么你可能会发现一个问题:从网上下载下来的大部分模型资源,它的文件内其实是有材质贴图的,但是读取的时候并读不到任何材质贴图,又或者干脆连贴图都没有

原因有三:

  1. 当初制作打包模型时,模型有绑定对应的贴图资源,但是绑定的相对路径有出入
  2. 没有贴图,是因为值单一,因此直接指定了值(这个暂不考虑/处理)
  3. 当初制作打包模型时,模型并没有绑定贴图资源

对于情况①,改下Model.h,支持自己传入路径就ok

对于情况③,想想Unity3D,是不是在大部分情况下材质都是我们自己指定的?是的,一般来讲导入模型可不是简单的流水线式硬编码(这里只是为了学习),往往都有一个类编辑器之类的东西,让使用者可以自由设置材质,所以有些模型不会绑定贴图资源。当然了这里还有第二种情况,那就是模型有绑定,但是读取需要到另一个编辑文件中,例如.mtl文件,然而网上大部分的Mesh类和Model类都没有考虑这一点

想要解决这个问题,目前写个编辑器(游戏引擎)好像不太现实,可以稍微处理一下Model.h和Mesh.h以支持自己读取贴图材质,代码如下:

#ifndef MODEL_H
#define MODEL_H
#include<vector>
#include<string>
#include"Shader.h"
#include"Mesh.h"
#include<opengl/glew.h>
#include<SOIL.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<assimp/Importer.hpp>
#include<assimp/scene.h>
#include<assimp/postprocess.h>
using namespace std;
GLint TextureFromFile(const char* path, string directory, string typeName);class Model
{public:Model(const GLchar* path, const GLchar* texPath = ""){this->loadModel(path, texPath);}void Draw(Shader shader){for (GLuint i = 0; i < this->meshes.size(); i++)this->meshes[i].Draw(shader);}private:ifstream myfile;vector<Mesh> meshes;string directory;vector<Texture> textures_loaded;void loadModel(string path, string texPath){Assimp::Importer importer;const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode){cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;return;}this->directory = path.substr(0, path.find_last_of('/'));if (texPath != "")this->directory = texPath;myfile.open(texPath + "/index.txt");this->processNode(scene->mRootNode, scene);myfile.close();}//依次处理所有的场景节点void processNode(aiNode* node, const aiScene* scene){for (GLuint i = 0; i < node->mNumMeshes; i++){aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];this->meshes.push_back(this->processMesh(mesh, scene));}for (GLuint i = 0; i < node->mNumChildren; i++)this->processNode(node->mChildren[i], scene);}//将所有原始的aimesh对象全部转换成我们自己定义的网格对象Mesh processMesh(aiMesh* mesh, const aiScene* scene){vector<Vertex> vertices;vector<GLuint> indices;vector<Texture> textures;//处理顶点坐标、法线和纹理坐标for (GLuint i = 0; i < mesh->mNumVertices; i++){Vertex vertex;glm::vec3 vector;vector.x = mesh->mVertices[i].x;vector.y = mesh->mVertices[i].y;vector.z = mesh->mVertices[i].z;vertex.Position = vector;vector.x = mesh->mNormals[i].x;vector.y = mesh->mNormals[i].y;vector.z = mesh->mNormals[i].z;vertex.Normal = vector;if (mesh->mTextureCoords[0])            //不一定有纹理坐标{glm::vec2 vec;//暂时只考虑第一组纹理坐标,Assimp允许一个模型的每个顶点有8个不同的纹理坐标,只是可能用不到vec.x = mesh->mTextureCoords[0][i].x;vec.y = mesh->mTextureCoords[0][i].y;vertex.TexCoords = vec;}elsevertex.TexCoords = glm::vec2(0.0f, 0.0f);vertices.push_back(vertex);}//处理顶点索引for (GLuint i = 0; i < mesh->mNumFaces; i++){aiFace face = mesh->mFaces[i];for (GLuint j = 0; j < face.mNumIndices; j++)indices.push_back(face.mIndices[j]);}//处理材质if (mesh->mMaterialIndex >= 0){aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());vector<Texture> reflectionMaps = this->loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_reflection");textures.insert(textures.end(), reflectionMaps.begin(), reflectionMaps.end());if (textures.size() == 0 && myfile.is_open()){string name, type;myfile >> type;myfile >> name;if (type.length() > 1){aiString str("Img/" + name);textures.push_back(loadTexturesFromPath(type, str));}}}return Mesh(vertices, indices, textures);}//遍历所有给定纹理类型的纹理位置,获取纹理的文件位置,然后加载生成纹理vector<Texture> loadMaterialTextures(aiMaterial* mat, int type, string typeName){vector<Texture> textures;for (GLuint i = 0; i < mat->GetTextureCount((aiTextureType)type); i++){aiString str;mat->GetTexture((aiTextureType)type, i, &str);textures.push_back(loadTexturesFromPath(typeName, str));}return textures;}Texture loadTexturesFromPath(string typeName, aiString path){Texture texture;for (GLuint j = 0; j < textures_loaded.size(); j++){if (std::strcmp(textures_loaded[j].path.C_Str(), path.C_Str()) == 0)return textures_loaded[j];}texture.id = TextureFromFile(path.C_Str(), this->directory, typeName);texture.type = typeName;texture.path = path;this->textures_loaded.push_back(texture);return texture;}
};GLint TextureFromFile(const char* path, string directory, string typeName)
{string filename = string(path);filename = directory + '/' + filename;cout << typeName << ":" << filename << endl;GLuint textureID;glGenTextures(1, &textureID);int width, height;unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);glBindTexture(GL_TEXTURE_2D, textureID);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);glGenerateMipmap(GL_TEXTURE_2D);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_MIPMAP_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glBindTexture(GL_TEXTURE_2D, 0);SOIL_free_image_data(image);return textureID;
}
#endif
#ifndef MESH_H
#define MESH_H
#include<vector>
#include<string>
#include<fstream>
#include<sstream>
#include"Shader.h"
#include<opengl/glew.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<assimp/Importer.hpp>
#include<assimp/scene.h>
#include<assimp/postprocess.h>
using namespace std;
struct Vertex
{glm::vec3 Position;         //顶点glm::vec3 Normal;           //法线glm::vec2 TexCoords;        //贴图
};struct Material
{glm::vec4 Ka;               //材质颜色glm::vec4 Kd;               //漫反射glm::vec4 Ks;               //镜面反射
};struct Texture
{GLuint id;string type;                //贴图类型:漫反射贴图还是镜面贴图(后面还有法线贴图、错位贴图等)aiString path;              //贴图路径
};class Mesh
{public:vector<Vertex> vertices;vector<GLuint> indices;             //索引vector<Texture> textures;Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures){this->vertices = vertices;this->indices = indices;this->textures = textures;this->setupMesh();}void Draw(Shader shader){GLuint diffuseNr = 1;GLuint specularNr = 1;GLuint reflectionNr = 1;for (GLuint i = 0; i < this->textures.size(); i++){glActiveTexture(GL_TEXTURE0 + i);stringstream ss;string name = this->textures[i].type;if (name == "texture_diffuse")ss << diffuseNr++;else if (name == "texture_specular")ss << specularNr++;else if (name == "texture_reflection")ss << reflectionNr++;name = name + ss.str();glUniform1i(glGetUniformLocation(shader.Program, name.c_str()), i);//这样的话,着色器中的纹理名就必须有一个对应的规范,例如“texture_diffuse3”代表第三个漫反射贴图//方法不唯一,这是最好理解/最简单的一种规范/写法glBindTexture(GL_TEXTURE_2D, this->textures[i].id);}glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);     //暂时写死反光度,也可配置glBindVertexArray(this->VAO);glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);             //EBO绘制for (GLuint i = 0; i < this->textures.size(); i++){glActiveTexture(GL_TEXTURE0 + i);glBindTexture(GL_TEXTURE_2D, 0);}glBindVertexArray(0);}private:GLuint VAO, VBO, EBO;void setupMesh(){glGenVertexArrays(1, &this->VAO);glGenBuffers(1, &this->VBO);glGenBuffers(1, &this->EBO);glBindVertexArray(this->VAO);glBindBuffer(GL_ARRAY_BUFFER, this->VBO);glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);glEnableVertexAttribArray(0);//别忘了struct中内存是连续的//offsetof():获取结构体属性的偏移量glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));glEnableVertexAttribArray(2);glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));glBindVertexArray(0);}
};
#endif

当然方法有点拙略,但能解决问题,如果实际模型有贴图,但是读不到,需要自己建立一个Index.txt文件,并在其中输入你想要加载的纹理类型和图片路径,一个例子如下:

总共10个mesh节点,其中第3个节点不需要贴图,其它9个节点贴图都指定为漫反射贴图Img_1.jpg

好了,搞定了,有了这个之后你甚至可以随意指定物体任意一个mesh的贴图,实现“拿漫反射贴图当成折射贴图”之类的骚操作,也可以在这基础上优化这份代码

如果物体存在折射/反射贴图,一个片段着色器的例子如下:(来源于openGL.cn)

#version 330 core
in vec3 Normal;
in vec3 Position;
in vec2 TexCoords;
uniform vec3 cameraPos;
uniform sampler2D texture_diffuse1;
uniform sampler2D texture_reflection1;
uniform samplerCube skybox;
out vec4 color;
void main()
{             // Diffusevec4 diffuse_color = texture(texture_diffuse1, TexCoords);// Reflectionvec3 I = normalize(Position - cameraPos);vec3 R = reflect(I, normalize(Normal));float reflect_intensity = texture(texture_reflection1, TexCoords).r;vec4 reflect_color;if(reflect_intensity > 0.1) // Only sample reflections when above a certain tresholdreflect_color = texture(skybox, R) * reflect_intensity;// Combine themcolor = diffuse_color + reflect_color;
}

四、扩展

反射和折射一直都是一个很难的问题,本身属于高级光照的范畴,上面只算最基础的了解了

动态环境映射(Dynamic Environment Mapping):

上面的着色器只能反射天空盒,而不能反射周边的物体,这明显是有问题的,不仅如此,周边的物体有些也可能是在运动的,因此想要想要每时每刻正确反射周边的所有物体(场景)并非是件容易的事

一个解决方案就是动态环境映射:使用帧缓冲为物体的所有6个不同角度创建一个场景的纹理,并在每次渲染时迭代储存为一个立方体贴图,之后再使用这个动态生成的立方体贴图来创建真实的反射和折射表面,这样就能包含所有其他物体了,当然了,这方法一听就非常的耗,因此优化是必不可少的

放大镜、凸透镜、半透反射:

字面意思,实现这些物体往往都有特定的算法,不唯一,表现效果和性能都有差

光线追踪(ray tracing):

目前非常有名的一项技术,解决了物体之间多次折射反射的问题,也考虑到了包括衰减在内的各种复杂物理因素,已经不再属于光栅化范畴

不过,现在的游戏基本都没有应用光线追踪技术,光线都是由你能看到的亮光的物体自身发出的,往往不会去深入考虑每个光源从哪里来,到哪里去,更不会计算这些光源的相互叠加,只是通过及时演算物体阴影和控制光线的强弱来“模拟”人眼看到的真实情况,当然啦,一个非常牛的算法也可以起到和光线追踪差不多的效果,只不过实现难度可能相差无几

PBR光照:

模拟金属材质的反射和折射是比较困难的,往往有一套专门的算法,网上的资料也很多,一个表现如下(来源于网络):

以上所有的扩展的重心都在:数学,而并非openGL本身

OpenGL基础37:反射与折射相关推荐

  1. OpenGL基础40:Uniform缓冲

    前置:OpenGL基础39:GLSL内建变量与接口块 想想之前代码,glUniform()和glGetUniformLocation()的使用数量是不是过于频繁了,对于每个着色器的每一个uniform ...

  2. OpenGL反射和折射

    OpenGL反射和折射 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <stdio.h> #include <math.h&g ...

  3. openGL GLSL GLSL.Refract Reflect Diffraction 反射、折射、衍射Fresnel Effect

    一.Refract & Reflect   Snell定律描述了光线从一个介质传播到另外一个介质时,入射角.折射角以及介质折射率的关系.通过snell定律,可以根据入射光的方向向量求取折射光的 ...

  4. OpenGL基础53:阴影映射(下)

    接上文:OpenGL基础52:阴影映射(上) 五.阴影失真 按照上文的计算的结果,一个很明显的问题是:对于参与计算深度贴图的物体,其表面可以看到这样的栅格状的阴影,这种常见的错误表现也叫做阴影失真(S ...

  5. OpenGL基础44:光照矫正(上)

    对于openGL的API,倒是没有必要花太多时间,重点应该还是在着色器上 一.采样器.glActiveTexture和glBindTexture 在之前测试简单光照时可能出现的两个问题,尽管它们可能不 ...

  6. OpenGL基础35:帧缓冲(下)之简单图像处理

    在之前的章节,所有的物体都是中规中矩的显示的,只考虑了光照对物体的影响,那假设想要显示特殊的效果该怎么操作呢?例如马赛克风.将所有的物体都显示为黑白色,就像上世纪80年代的灰白电视一样,又或者说将整个 ...

  7. OpenGL基础22:贴图

    在 OpenGL基础13:第一个正方体 中给正方体加了箱子的纹理,但是在后面介绍光照的时候又把纹理属性给丢了,现在尝试在有纹理的基础之上增加光照 一.漫反射贴图 先把之前的纹理加回去 顶点着色器和主代 ...

  8. OpenGL基础21:材质

    前置:OpenGL基础20:镜面光照 一.材质 前面环境光.漫反射光照和镜面光照都已经安排上了,但是物体的属性是写死的 在真实的世界里,不同的物体会对光产生不同的反应,例如一颗金属球或一块橡木,它们在 ...

  9. OpenGL基础20:镜面光照

    前置:OpenGL基础19:法向量与漫反射 一.镜面光照 前面物体已经拥有了环境光和漫反射光,现在再加上镜面光照就完美了,镜面光照的效果是:当我们去看光被物体所反射的那个方向的时候,会看到一个高光 和 ...

最新文章

  1. Ubuntu16.04下配置最新Vs Code的C/C++开发环境
  2. yolo v3 fatal : Memory allocation failure
  3. 【caffe解读】 caffe从数学公式到代码实现3-shape相关类
  4. Maven项目在pom文件中引入lib下的第三方jar包并打包进去
  5. java 连接kafka_设置多个kafka连接接收器
  6. java视窗_java-预览窗口(如Windows 7任务栏显示已打开的...
  7. 职场:人生从没有最佳时机!一个离职客服人员的领悟
  8. 在C语言中023是八进制数,C语言总结
  9. 字典按照值或键进行排序
  10. python3图像处理_Python3与OpenCV3.3 图像处理(二)--图像基本操作
  11. 像冠军一样创建报告! Reporting Services的提示和技巧
  12. 重新打包mysql数据库文件_服务器每天早上备份一次 MySQL 数据库并自动打包,同时删除 5 天前的备份文件...
  13. Java添加过期注解
  14. Android实战简易教程-第二十八枪(Uri转String型实例)
  15. html选择时间区间控件,Html5添加用户选择一个日期时间范围的日期选择器插件教程...
  16. 查看Oracle执行计划的几种常用方法-系列1
  17. Linux下deamon(服务)的实现
  18. 如何调试ajax 和php
  19. 【已解决】pdf导出的eps图形在WinEdt中只显示一半
  20. STP生成树协议切割网络环路

热门文章

  1. matlab串口采集频率,matlab之串口数据采集绘图
  2. 数据分配器和数据选择器
  3. 软考和华为认证哪个好?更利于求职?
  4. 一个正在换工作的十年程序猿简历
  5. Markdown表格—合并单元格—设置单元格颜色
  6. 传奇开服需要多少钱?传奇服务端建立不了行会,传奇开区时点创建行会没反应的解决方法
  7. ASN.Net 发布后访问报:<customErrors> 标记的“mode”属性设置为“Off”的错误问题的解决方案
  8. 【Java】IDEA编译Java项目报错 java: 找不到符号 的解决方法
  9. canvas实现H5接物类小游戏
  10. vue封装背景知识小插曲之插槽slot的用法