目录

  • 前言
  • Model 类的小小改动
  • phong 光照模型
    • phong 光照简介
    • 环境光
    • 漫反射
    • 高光
  • 在着色器中实现 phong 光照
  • 完整代码
    • c++
    • 着色器

前言

上一篇回顾:OpenGL学习(七)通过assimp库读取多种格式的模型

在上一篇博客中,我们实现了最简单的网格对象 Mesh,并且从 assimp 库接收我们需要的信息,同时进行绘制。这意味着我们逐渐步入现代。

今天我们要利用 phong 光照模型,实现对物体的光照效果的模拟,让场景更加真实。

注:
本篇博客代码基于上一篇博客:OpenGL学习(七)通过assimp库读取多种格式的模型

Model 类的小小改动

在开始之前,我们要对我们昨天(上一篇博客)新鲜封装的 model 类进行一些改动。在昨天,我们直接传递了一个模型变换矩阵 model 到着色器,因为我们只绘制一个物体,足够了。

希望你不会发现上一篇博客我偷懒了,这本来就应该是在 Model 类定义的时候应该完成的。。。

如果要绘制多个不同的物体,那么问题来了。不同的物体我们需要传递不同的模型变换矩阵,于是我们把这一步骤放到了 Model 类的 draw 函数中。

我们添加三个成员,表示一个模型对象的平移,旋转,缩放:

class Model
{public:// ...glm::vec3 translate=glm::vec3(0,0,0), rotate = glm::vec3(0, 0, 0), scale = glm::vec3(1, 1, 1);// ...

紧接着我们在 draw 函数中,绘制之前,传递本模型的模型变换矩阵即可:

// 传模型矩阵
glm::mat4 unit(    // 单位矩阵glm::vec4(1, 0, 0, 0),glm::vec4(0, 1, 0, 0),glm::vec4(0, 0, 1, 0),glm::vec4(0, 0, 0, 1)
);
glm::mat4 scale = glm::scale(unit, this->scale);
glm::mat4 translate = glm::translate(unit, this->translate);glm::mat4 rotate = unit;    // 旋转
rotate = glm::rotate(rotate, glm::radians(this->rotate.x), glm::vec3(1, 0, 0));
rotate = glm::rotate(rotate, glm::radians(this->rotate.y), glm::vec3(0, 1, 0));
rotate = glm::rotate(rotate, glm::radians(this->rotate.z), glm::vec3(0, 0, 1));// 模型变换矩阵
glm::mat4 model = translate * rotate * scale;
GLuint mlocation = glGetUniformLocation(program, "model");    // 名为model的uniform变量的位置索引
glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model));   // 列优先矩阵

遇到任何问题?回顾之前的博客:OpenGL学习(三)三维绘制与模型变换矩阵

对于多个模型,我们创建一个全局 vector 变量,名叫 models,她存储不同的 Model 对象。

std::vector<Model> models;

我们在初始化的时候,就应该指定模型的平移旋转缩放参数,同时将 Model 对象加入 models :

Model tree1 = Model();
tree1.translate = glm::vec3(0.25, 0, -1);
tree1.scale = glm::vec3(0.00025, 0.00025, 0.00025);
tree1.load("models/tree/tree02.obj");
models.push_back(tree1);

在 display 中,我们直接绘制所有的 Model 对象即可:

for (auto m : models)
{m.draw(program);
}

我们现在可以自由的绘制多个模型:

phong 光照模型

在现实世界中,光线从各种光源出发,经过无数次反弹,最终进入眼镜。这种现象对于计算机来说几乎不可解,因为复杂度高的一。

时下流行的方法是从摄像机方向,逐像素,向场景中投射(一条或者多条)光线,光线沿途不断反弹,每次反弹都搜集信息(比如碰撞点的颜色),直到满足某些条件就终止。常用的方法有蒙卡洛特路径追踪,辐射度方法等。这些方法统称为光线追踪,简称光追。

光线追踪最大的难点就是求交,即当前光线何时碰撞到实体?碰撞的实体颜色是什么?此外,光追需要大量的迭代才能够拟合,这意味着我们要向一个像素发送若干条光线(128,256,甚至更多)才能够达到不错的效果,计算量也是一大难点!

至于求交等大量计算的工作,图形界的大佬们提出了体积树(BVH)的方式来进行空间划分求交,而老黄等一众显卡开发商则将这些操作 “焊死” 在显卡的集成电路和驱动程序中,就如同 OpenGL 的裁剪,光栅化等操作。于是乎,RTX 系显卡就会有一些单元叫做 “光追单元”,这个和经典显卡的几何单元,光栅化单元异曲同工。。。

我们可以注意到了,这些新式的东西,并不在 OpenGL 流水线的范畴内。这意味着我们只有使用其他的图形 API(比如 Vulkan)才能有机会使用这些新东东。唔,OpenGL 毕竟是上个时代的了(雾)

如果你读过我之前写的这篇博客:从零开始编写minecraft光影包(9)高级水面绘制 反射与屏幕空间反射,你可能会说了,我们不是已经实现了光线追踪嘛?

其实这种叫做屏幕空间光线追踪,它的信息都来自于我们的屏幕空间,换句话说,我们只能记录那些我们看到的东西,这也是传统的 OpenGL 流水线约束造成的。对于屏幕空间外的信息我们一无所知!

那么传统的 OpenGL 流水线有没有光追呢?有!Minecraft 光影 SEUS 的作者 SE 大佬就实现了。至于天才 SE 的 PTGI 是怎么记录屏幕空间外的信息,唔。。。我不清楚,估计是用了 shadow 阴影帧缓冲的颜色附件?毕竟这是唯一我能够想到的获取屏幕空间外信息的方式

回想我们玩游戏的时候,屏幕空间外的物品,仍然会将阴影投影到我们的屏幕上。这样一解释就合理了。

啊啊啊啊扯远了扯远了,我爬我爬 dbq Orz 咚咚咚

phong 光照简介

因为经典的全局光照模型太过复杂,而且对于 OpenGL 流水线来说非常难实现,于是早些年的图形程序员提出了一个简单的模型,叫做 phong 光照模型,该模型能够以极低的代价模拟真实的光照场景,在计算机游戏实时渲染领域是性价比极高的模型。直到现在,很多计算机游戏仍然是沿用这一套模型。

phong 光照模型将物体的光线分为三大类,分别是:

  1. 环境光 ambient
  2. 漫反射光 diffuse
  3. 镜面高光 specular

其中环境光 ambient 是一个固定的数值,漫反射光 diffuse 和光源的角度,物体的法向量有关,而镜面高光则和 specular 视线方向,光源角度有关。

最后的光照总和可以用如下的公式简单的描述:

light=ambient+diffuse+specularlight = ambient + diffuse + specular light=ambient+diffuse+specular

环境光

其中环境光 ambient 是一个固定的数值,物体的每一个像素众生平等。环境光是为了模拟那些经过 n 次反射的光,就如同你半夜起来上厕所,周围不是全黑的,通常是有一丝亮度的,这就是环境光。

环境光的计算十分简单:

ambient=Kaambient = Ka ambient=Ka

其中 Ka 是环境光的系数,由物体的材质决定。

漫反射

而漫反射光则需要考虑光源和物体的位置关系了。根据物理定律,光线直射物体的时候,反射的光最多,而光线平视物体的时候,我们几乎无法接收到反射光。

假设光线方向为 L,L’ 为光线的反方向,法线方向为 N,我们可以得出,当光线垂直摄入时,即 N 和 L 成 180 度,即 N 和 L’ 成 0 度时,最亮!

于是有:

diffuse=cos(theta)∗Kd=dot(−L,N)∗Kddiffuse = cos(theta) * Kd = dot(-L, N) * Kd diffuse=cos(theta)∗Kd=dot(−L,N)∗Kd

其中 -L 是 L’ 即入射光的反方向,而 Kd 则是材质的漫反射系数。

高光

高光的原理是反射光线之后,大部分光线都会位于反射光线附近,这部分会高亮过其他地方:


如何判断高光什么时候入眼呢?我们视线方向,和反射光线的方向越近(θ 越小),就能够看到越多的高光!

注:
这里取的是反射光线的反方向

我们令反射光线为 R,视线方向为 V,于是有高光公式:

specular=cos(theta)∗Ks=dot(−R,V)∗Ksspecular = cos(theta) * Ks = dot(-R, V) * Ks specular=cos(theta)∗Ks=dot(−R,V)∗Ks

其中我们给 cos(θ) 做一个指数,即 cos 的 a 次方,其中 a 是高光衰减系数。所以最终有:

kscos⁡αϕ\boldsymbol{k}_{s}\cos ^{\alpha} \boldsymbol{\phi} ks​cosαϕ

在着色器中实现 phong 光照

我们直接套公式即可:首先在顶点着色器,我们获取法线和坐标并且 pass 到片段着色器。注意法线直接乘以模型矩阵,是不完全正确的。在等轴缩放的时候,我们可以这么用!

顶点着色器:

#version 330 core// 顶点着色器输入
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTexcoord;
layout (location = 2) in vec3 vNormal;out vec3 worldPos;
out vec2 texcoord;
out vec3 normal;uniform mat4 model;         // 模型变换矩阵
uniform mat4 view;          // 模型变换矩阵
uniform mat4 projection;    // 模型变换矩阵void main()
{gl_Position = projection * view * model * vec4(vPosition, 1.0);// 传递到片段着色器texcoord = vTexcoord;   worldPos = (model * vec4(vPosition, 1.0)).xyz;normal = (model * vec4(vNormal, 0.0)).xyz;
}

在片段着色器中,我们编写一个函数,计算光照系数,其中第参数是:像素的世界坐标,摄像机位置,光源位置,像素的法线在世界空间下的方向。然后我们代公式即可。

片段着色器:

#version 330 corein vec3 worldPos;   // 当前片元的世界坐标
in vec2 texcoord;   // 纹理坐标
in vec3 normal;     // 法向量out vec4 fColor;    // 片元输出像素的颜色uniform sampler2D texture;  // 纹理图片uniform vec3 lightPos;  // 光源位置
uniform vec3 cameraPos; // 相机位置float phong(vec3 worldPos, vec3 cameraPos, vec3 lightPos, vec3 normal)
{vec3 N = normalize(normal);vec3 V = normalize(worldPos - cameraPos);vec3 L = normalize(worldPos - lightPos);vec3 R = reflect(L, N);float ambient = 0.3;float diffuse = max(dot(N, -L), 0) * 0.7;float specular = pow(max(dot(-R, V), 0), 50.0) * 1.1;return ambient + diffuse + specular;
}void main()
{fColor.rgb =  texture2D(texture, texcoord).rgb;fColor.rgb *= phong(worldPos, cameraPos, lightPos, normal);
}

别忘了在 c++ 代码中,我们操控光源。我们传送光源位置和相机位置进着色器:

// 传递光源位置
glUniform3fv(glGetUniformLocation(program, "lightPos"), 1, glm::value_ptr(lightPos));// 传递相机位置
glUniform3fv(glGetUniformLocation(program, "cameraPos"), 1, glm::value_ptr(cameraPosition));

同时我们记得增加控制光源移动的接口:

最后我们用一个模型,将他始终绑定在光源的位置,以表示我们光源的位置。唔。。。就选最后一个模型罢:

models.back().translate = lightPos;

重新加载程序:


好耶!

完整代码

c++

// std c++
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <map>
#include <sstream>
#include <iostream>// glew glut
#include <GL/glew.h>
#include <GL/freeglut.h>// glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>// SOIL
#include <SOIL2/SOIL2.h>// assimp
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>// --------------------- end of include --------------------- //class Mesh
{public:// OpenGL 对象GLuint vao, vbo, ebo;GLuint diffuseTexture;  // 漫反射纹理// 顶点属性std::vector<glm::vec3> vertexPosition;std::vector<glm::vec2> vertexTexcoord;std::vector<glm::vec3> vertexNormal;// glDrawElements 函数的绘制索引std::vector<int> index;Mesh() {}void bindData(){// 创建顶点数组对象glGenVertexArrays(1, &vao); // 分配1个顶点数组对象glBindVertexArray(vao);     // 绑定顶点数组对象// 创建并初始化顶点缓存对象 这里填NULL 先不传数据glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER,vertexPosition.size() * sizeof(glm::vec3) +vertexTexcoord.size() * sizeof(glm::vec2) +vertexNormal.size() * sizeof(glm::vec3),NULL, GL_STATIC_DRAW);// 传位置GLuint offset_position = 0;GLuint size_position = vertexPosition.size() * sizeof(glm::vec3);glBufferSubData(GL_ARRAY_BUFFER, offset_position, size_position, vertexPosition.data());glEnableVertexAttribArray(0);   // 着色器中 (layout = 0) 表示顶点位置glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_position));// 传纹理坐标GLuint offset_texcoord = size_position;GLuint size_texcoord = vertexTexcoord.size() * sizeof(glm::vec2);glBufferSubData(GL_ARRAY_BUFFER, offset_texcoord, size_texcoord, vertexTexcoord.data());glEnableVertexAttribArray(1);   // 着色器中 (layout = 1) 表示纹理坐标glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_texcoord));// 传法线GLuint offset_normal = size_position + size_texcoord;GLuint size_normal = vertexNormal.size() * sizeof(glm::vec3);glBufferSubData(GL_ARRAY_BUFFER, offset_normal, size_normal, vertexNormal.data());glEnableVertexAttribArray(2);   // 着色器中 (layout = 2) 表示法线glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_normal));// 传索引到 eboglGenBuffers(1, &ebo);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size() * sizeof(GLuint), index.data(), GL_STATIC_DRAW);glBindVertexArray(0);}void draw(GLuint program){glBindVertexArray(vao);// 传纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, diffuseTexture);glUniform1i(glGetUniformLocation(program, "texture"), 0);// 绘制glDrawElements(GL_TRIANGLES, this->index.size(), GL_UNSIGNED_INT, 0);glBindVertexArray(0);}
};class Model
{public:std::vector<Mesh> meshes;std::map<std::string, GLuint> textureMap;glm::vec3 translate=glm::vec3(0,0,0), rotate = glm::vec3(0, 0, 0), scale = glm::vec3(1, 1, 1);Model() {}void load(std::string filepath){Assimp::Importer import;const aiScene* scene = import.ReadFile(filepath, aiProcess_Triangulate);// 异常处理if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode){std::cout << "读取模型出现错误: " << import.GetErrorString() << std::endl;exit(-1);}// 模型文件相对路径std::string rootPath = filepath.substr(0, filepath.find_last_of('/'));// 循环生成 meshfor (int i = 0; i < scene->mNumMeshes; i++){// 引用当前meshmeshes.push_back(Mesh());Mesh& mesh = meshes.back();// 获取 assimp 的读取到的 aimesh 对象aiMesh* aimesh = scene->mMeshes[i];// 我们将数据传递给我们自定义的meshfor (int j = 0; j < aimesh->mNumVertices; j++){// 顶点glm::vec3 vvv;vvv.x = aimesh->mVertices[j].x;vvv.y = aimesh->mVertices[j].y;vvv.z = aimesh->mVertices[j].z;mesh.vertexPosition.push_back(vvv);// 法线vvv.x = aimesh->mNormals[j].x;vvv.y = aimesh->mNormals[j].y;vvv.z = aimesh->mNormals[j].z;mesh.vertexNormal.push_back(vvv);// 纹理坐标: 如果存在则加入。assimp 默认可以有多个纹理坐标 我们取第一个(0)即可glm::vec2 vv(0, 0);if (aimesh->mTextureCoords[0]){vv.x = aimesh->mTextureCoords[0][j].x;vv.y = aimesh->mTextureCoords[0][j].y;}mesh.vertexTexcoord.push_back(vv);}// 如果有材质,那么传递材质if (aimesh->mMaterialIndex >= 0){// 获取当前 aimesh 的材质对象aiMaterial* material = scene->mMaterials[aimesh->mMaterialIndex];// 获取 diffuse 贴图文件路径名称 我们只取1张贴图 故填 0 即可aiString aistr;material->GetTexture(aiTextureType_DIFFUSE, 0, &aistr);std::string texpath = aistr.C_Str();texpath = rootPath + '/' + texpath;   // 取相对路径// 如果没生成过纹理,那么生成它if (textureMap.find(texpath) == textureMap.end()){// 生成纹理GLuint tex;glGenTextures(1, &tex);glBindTexture(GL_TEXTURE_2D, tex);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_WRAP_S, GL_MIRRORED_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);int textureWidth, textureHeight;unsigned char* image = SOIL_load_image(texpath.c_str(), &textureWidth, &textureHeight, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);   // 生成纹理delete[] image;textureMap[texpath] = tex;}// 传递纹理mesh.diffuseTexture = textureMap[texpath];}// 传递面片索引for (GLuint j = 0; j < aimesh->mNumFaces; j++){aiFace face = aimesh->mFaces[j];for (GLuint k = 0; k < face.mNumIndices; k++){mesh.index.push_back(face.mIndices[k]);}}mesh.bindData();}}void draw(GLuint program){// 传模型矩阵glm::mat4 unit(    // 单位矩阵glm::vec4(1, 0, 0, 0),glm::vec4(0, 1, 0, 0),glm::vec4(0, 0, 1, 0),glm::vec4(0, 0, 0, 1));glm::mat4 scale = glm::scale(unit, this->scale);glm::mat4 translate = glm::translate(unit, this->translate);glm::mat4 rotate = unit;    // 旋转rotate = glm::rotate(rotate, glm::radians(this->rotate.x), glm::vec3(1, 0, 0)); rotate = glm::rotate(rotate, glm::radians(this->rotate.y), glm::vec3(0, 1, 0));  rotate = glm::rotate(rotate, glm::radians(this->rotate.z), glm::vec3(0, 0, 1));// 模型变换矩阵glm::mat4 model = translate * rotate * scale;GLuint mlocation = glGetUniformLocation(program, "model");    // 名为model的uniform变量的位置索引glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model));   // 列优先矩阵for (int i = 0; i < meshes.size(); i++){meshes[i].draw(program);}}
};// ---------------------------- end of class definition ---------------------------- //std::vector<Model> models;GLuint program; // 着色器程序对象  // 相机参数
glm::vec3 cameraPosition(0, 0, 0);      // 相机位置
glm::vec3 cameraDirection(0, 0, -1);    // 相机视线方向
glm::vec3 cameraUp(0, 1, 0);            // 世界空间下竖直向上向量
float pitch = 0.0f;
float roll = 0.0f;
float yaw = 0.0f;// 光源
glm::vec3 lightPos = glm::vec3(0, 0.5, 0);// 视界体参数
float left = -1, right = 1, bottom = -1, top = 1, zNear = 0.01, zFar = 100.0;int windowWidth = 512;  // 窗口宽
int windowHeight = 512; // 窗口高bool keyboardState[1024];   // 键盘状态数组 keyboardState[x]==true 表示按下x键// --------------- end of global variable definition --------------- //// 读取文件并且返回一个长字符串表示文件内容
std::string readShaderFile(std::string filepath)
{std::string res, line;std::ifstream fin(filepath);if (!fin.is_open()){std::cout << "文件 " << filepath << " 打开失败" << std::endl;exit(-1);}while (std::getline(fin, line)){res += line + '\n';}fin.close();return res;
}// 获取着色器对象
GLuint getShaderProgram(std::string fshader, std::string vshader)
{// 读取shader源文件std::string vSource = readShaderFile(vshader);std::string fSource = readShaderFile(fshader);const char* vpointer = vSource.c_str();const char* fpointer = fSource.c_str();// 容错GLint success;GLchar infoLog[512];// 创建并编译顶点着色器GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, (const GLchar**)(&vpointer), NULL);glCompileShader(vertexShader);glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);   // 错误检测if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "顶点着色器编译错误\n" << infoLog << std::endl;exit(-1);}// 创建并且编译片段着色器GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, (const GLchar**)(&fpointer), NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);   // 错误检测if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "片段着色器编译错误\n" << infoLog << std::endl;exit(-1);}// 链接两个着色器到program对象GLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// 删除着色器对象glDeleteShader(vertexShader);glDeleteShader(fragmentShader);return shaderProgram;
}// 鼠标滚轮函数
void mouseWheel(int wheel, int direction, int x, int y)
{// zFar += 1 * direction * 0.1;glutPostRedisplay();    // 重绘
}// 鼠标运动函数
void mouse(int x, int y)
{// 调整旋转yaw += 35 * (x - float(windowWidth) / 2.0) / windowWidth;yaw = glm::mod(yaw + 180.0f, 360.0f) - 180.0f;    // 取模范围 -180 ~ 180pitch += -35 * (y - float(windowHeight) / 2.0) / windowHeight;pitch = glm::clamp(pitch, -89.0f, 89.0f);glutWarpPointer(windowWidth / 2.0, windowHeight / 2.0);glutPostRedisplay();    // 重绘
}// 键盘回调函数
void keyboardDown(unsigned char key, int x, int y)
{keyboardState[key] = true;
}
void keyboardDownSpecial(int key, int x, int y)
{keyboardState[key] = true;
}
void keyboardUp(unsigned char key, int x, int y)
{keyboardState[key] = false;
}
void keyboardUpSpecial(int key, int x, int y)
{keyboardState[key] = false;
}
// 根据键盘状态判断移动
void move()
{float cameraSpeed = 0.0015f;if (keyboardState['w']) cameraPosition += cameraSpeed * cameraDirection;if (keyboardState['s']) cameraPosition -= cameraSpeed * cameraDirection;if (keyboardState['a']) cameraPosition -= cameraSpeed * glm::normalize(glm::cross(cameraDirection, cameraUp));if (keyboardState['d']) cameraPosition += cameraSpeed * glm::normalize(glm::cross(cameraDirection, cameraUp));if (keyboardState[GLUT_KEY_CTRL_L]) cameraPosition.y -= cameraSpeed;if (keyboardState[' ']) cameraPosition.y += cameraSpeed;if (keyboardState['i']) lightPos.x += cameraSpeed;if (keyboardState['I']) lightPos.x -= cameraSpeed;if (keyboardState['o']) lightPos.y += cameraSpeed;if (keyboardState['O']) lightPos.y -= cameraSpeed;glutPostRedisplay();    // 重绘
}// 初始化
void init()
{// 生成着色器程序对象std::string fshaderPath = "shaders/fshader.fsh";std::string vshaderPath = "shaders/vshader.vsh";program = getShaderProgram(fshaderPath, vshaderPath);glUseProgram(program);  // 使用着色器Model tree1 = Model();tree1.translate = glm::vec3(0.25, 0, -1);tree1.scale = glm::vec3(0.00025, 0.00025, 0.00025);tree1.load("models/tree/tree02.obj");models.push_back(tree1);Model tree2 = Model();tree2.translate = glm::vec3(1, 0, -1);tree2.scale = glm::vec3(0.00015, 0.00015, 0.00015);tree2.load("models/tree/tree02.obj");models.push_back(tree2);Model plane = Model();plane.translate = glm::vec3(0, -0.1, 0);plane.rotate = glm::vec3(0, -90, 0);plane.load("models/plane/plane.obj");models.push_back(plane);Model vlight = Model();vlight.translate = glm::vec3(1, 0, -1);vlight.scale = glm::vec3(0.15, 0.15, 0.15);vlight.load("models/Indoor plant 3/Low-Poly Plant_.obj");models.push_back(vlight);glEnable(GL_DEPTH_TEST);  // 开启深度测试glClearColor(1.0, 1.0, 1.0, 1.0);   // 背景颜色
}// 显示回调函数
void display()
{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);       // 清空窗口颜色缓存move(); // 移动控制 -- 控制相机位置// 计算欧拉角以确定相机朝向cameraDirection.x = cos(glm::radians(pitch)) * sin(glm::radians(yaw));cameraDirection.y = sin(glm::radians(pitch));cameraDirection.z = -cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 相机看向z轴负方向// 传视图矩阵glm::mat4 view = glm::lookAt(cameraPosition, cameraPosition + cameraDirection, cameraUp);GLuint vlocation = glGetUniformLocation(program, "view");glUniformMatrix4fv(vlocation, 1, GL_FALSE, glm::value_ptr(view));// 传投影矩阵glm::mat4 projection = glm::perspective(glm::radians(70.0f), (GLfloat)windowWidth / (GLfloat)windowHeight, zNear, zFar);GLuint plocation = glGetUniformLocation(program, "projection");glUniformMatrix4fv(plocation, 1, GL_FALSE, glm::value_ptr(projection));// 传递光源位置glUniform3fv(glGetUniformLocation(program, "lightPos"), 1, glm::value_ptr(lightPos));// 传递相机位置glUniform3fv(glGetUniformLocation(program, "cameraPos"), 1, glm::value_ptr(cameraPosition));models.back().translate = lightPos;for (auto m : models){m.draw(program);}glutSwapBuffers();                  // 交换缓冲区
}// -------------------------------- main -------------------------------- //int main(int argc, char** argv)
{glutInit(&argc, argv);              // glut初始化glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);glutInitWindowSize(windowWidth, windowHeight);// 窗口大小glutCreateWindow("7 - phong"); // 创建OpenGL上下文#ifdef __APPLE__
#elseglewInit();
#endifinit();// 绑定鼠标移动函数 -- //glutMotionFunc(mouse);  // 左键按下并且移动glutPassiveMotionFunc(mouse);   // 鼠标直接移动//glutMouseWheelFunc(mouseWheel); // 滚轮缩放// 绑定键盘函数glutKeyboardFunc(keyboardDown);glutSpecialFunc(keyboardDownSpecial);glutKeyboardUpFunc(keyboardUp);glutSpecialUpFunc(keyboardUpSpecial);glutDisplayFunc(display);           // 设置显示回调函数 -- 每帧执行glutMainLoop();                     // 进入主循环return 0;
}

着色器

顶点:

#version 330 core// 顶点着色器输入
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTexcoord;
layout (location = 2) in vec3 vNormal;out vec3 worldPos;
out vec2 texcoord;
out vec3 normal;uniform mat4 model;         // 模型变换矩阵
uniform mat4 view;          // 模型变换矩阵
uniform mat4 projection;    // 模型变换矩阵void main()
{gl_Position = projection * view * model * vec4(vPosition, 1.0);// 传递到片段着色器texcoord = vTexcoord;   worldPos = (model * vec4(vPosition, 1.0)).xyz;normal = (model * vec4(vNormal, 0.0)).xyz;
}

片元:

#version 330 corein vec3 worldPos;   // 当前片元的世界坐标
in vec2 texcoord;   // 纹理坐标
in vec3 normal;     // 法向量out vec4 fColor;    // 片元输出像素的颜色uniform sampler2D texture;  // 纹理图片uniform vec3 lightPos;  // 光源位置
uniform vec3 cameraPos; // 相机位置float phong(vec3 worldPos, vec3 cameraPos, vec3 lightPos, vec3 normal)
{vec3 N = normalize(normal);vec3 V = normalize(worldPos - cameraPos);vec3 L = normalize(worldPos - lightPos);vec3 R = reflect(L, N);float ambient = 0.3;float diffuse = max(dot(N, -L), 0) * 0.7;float specular = pow(max(dot(-R, V), 0), 50.0) * 1.1;return ambient + diffuse + specular;
}void main()
{fColor.rgb =  texture2D(texture, texcoord).rgb;fColor.rgb *= phong(worldPos, cameraPos, lightPos, normal);
}

OpenGL学习(八)phong光照模型相关推荐

  1. OpenGL学习-球体和phong光照模型

    OpenGL学习-球体和phong光照模型 开发环境搭建参考:https://lexiaoyuan.blog.csdn.net/article/details/120059213 通用配置步骤 Vis ...

  2. 【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】

    ✅ 重点参考了 LearnOpenGL CN 的内容,但大部分知识内容,小编已作改写,以方便读者理解. 文章目录 零. 成果预览图 一. 光照原理与投光物的配置 1.1 光照原理 1.2 投光物 二. ...

  3. OpenGL中phong光照模型详解

    版权 cesuolidec4 https://blog.csdn.net/xiewenzhao123/article/details/54600191 引言 现实世界的光照是极其复杂的,而且会受到诸多 ...

  4. OpenGL学习笔记(八):进一步理解VAO、VBO和SHADER,并使用VAO、VBO和SHADER绘制一个三角形

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7888 ...

  5. OpenGL学习(九)阴影映射(shadowMapping)

    目录 写在前面 阴影映射原理简介 封装 Camera 类 帧缓冲 阴影映射 准备工作 创建帧缓冲与深度纹理附件 从光源方向进行渲染 正常地渲染场景 如何查找bug(⚠重要) 多纹理传送 查看深度纹理数 ...

  6. 基础光照-Phong 光照模型

    1. Phong 光照模型 1.1 环境光照 Ambient Lighting 1.1.1 实现代码 1.2 漫反射光照 Diffuse Lighting 1.2.1 法向量 及实现代码 1.2.2 ...

  7. 【我的OpenGL学习进阶之旅】【持续更新】关于学习OpenGL的一些资料

    目录 一.相关书籍 OpenGL 方面 C方面 NDK 线性代数 二.相关博客 2.0 一些比较官方的链接 2.1 OpenGL着色器语言相关 2.2 [[yfan]](https://segment ...

  8. OPENGL学习脚印

    OPENGL学习脚印 声明:本文转载自wangdingqiao的博客专栏–OPENGL学习脚印,仅仅对OPENGL学习脚印专栏的文章进行了整理,方便初学者按照顺序学习.同时也在这里感谢原创博主提供的学 ...

  9. 计算机图形学 1 —— 颜色模型图像基本知识,Phong光照模型

    色彩视觉 什么是色彩? 色彩是对不同波长的光的能量的感知: 不同波长的电磁波(electromagnetic waves) 对应不同的色彩: 对于人眼能感知的光(可见光),其波长范围为 380nm到7 ...

  10. 【OpenGL学习笔记⑥】——3D变换【旋转的正方体 实现地月系统 旋转+平移+缩放】

    ✈️ 文章目录 零. 成果预览图 一.3D立方体的顶点数组 二.纹理旋转 三.纹理缩放 四.画n个3D图形 五.轨道的数学公式 六.深度缓冲(Z 缓冲) 七.完整代码 八.参考附录: 神器的正方体 ☁ ...

最新文章

  1. 数据结构 python的书推荐-java数据结构书一般推荐看什么好?
  2. 周五话营销 | 健身房花式卖卡,诠释点击营销流
  3. 划分VLAN,根据每个VLAN通过DHCP分配IP地址
  4. 使用ANTLR在5分钟内用Java解析任何语言:例如Python
  5. ios html字符串 label,iOS UIlabel怎么加载html字符串 富文本的用法
  6. php psd图层重命名,ps批量修改图层名字的脚本(附批量替换方法)
  7. 黑马程序员————java线程之间的通信
  8. 负载均衡—实现配置篇(Nginx)
  9. 百度地图InfoWindow弹窗圆角
  10. javaEE mvc样例具体解释
  11. linux之sed使用
  12. JVM调优--04---dump文件分析 、arthas在线排查工具
  13. web-polygraph源码框架和session处理流程笔记
  14. 数据库原理与应用~第三版课后习题答案(何玉洁 编著)
  15. android快牙原理,快牙是什么 快牙使用原理及使用教程
  16. (十三)jQuery addClass()和removeClass()方法
  17. 深圳平安银行软件测试面试,中国平安银行面试笔试题目 软件测试方向
  18. 【HTML】HTML网页设计-----游戏网页设计
  19. SimpleWord 简词开发记录笔记
  20. 论文查找ICCV ECCV CVPR

热门文章

  1. AD封装库大全免费分享
  2. sql服务器数据库主文件,数据库文件和文件组
  3. Linux命令之MD5校验md5sum
  4. 计算机科学概论_第1章_数据储存
  5. java高级工程师认证考试_ACAA 认证Java开发工程师
  6. 采用文件白名单方式构建主机安全环境
  7. Flowable官方指定中国社区成立了
  8. JQuery实战手风琴-遁地龙卷风
  9. jsp mysql demo_ntko: ntko办公插件使用案例demo使用java+jsp+mysql实现
  10. 大数据Hadoop基本概念介绍