本人初学者,文中定有代码、术语等错误,欢迎指正
文章目录
- 高级光照
- Phong光照的缺点
- Blinn-Phong
- 介绍
- 例子
- GLSL中遇到的BUG
高级光照
Phong光照的缺点
-
造成Phong光照缺点的两个条件
-
当物体的高光反光度(shiness)比较小时
-
什么是高光的反光度
回顾LearnOpenGL-光照-2.基础光照
在片段着色器计算镜面光照分量时
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);// spec = 光源对当前片段的镜面光影响 vec3 specular = specularStrength * spec * lightColor;// specular = 镜面光照分量
第一行代码:float spec=…,其中32就是片段的高光反光度
-
shiness小到什么程度
小于1以下(下面有例子:代值计算shiness比较小时对spec的影响)
-
-
视线与反射光线向量的夹角大于90度
左图小于90度,右图大于90度
-
-
满足上一点的两个条件,会引起问题
-
图示
-
文字描述
如红色箭头所指,在镜面高光区域的边缘出现了一道很明显的断层。
-
-
将两个条件转换为具体值计算如何影响spec的(大概率有误,请指正)
-
测试前需了解
cos:在0-90度是
1至0
,在90度-180度是0至-1
-
测试pow(a,b)值
a = dot(viewDir, reflectDir)
b = shiness
// 用例一 for (float i = 0; i <= 0.1; i += 0.01) { float spec = pow(i, 0.2);// shiness:0.2 < 1 cout << "pow("<< i <<", 0.2):" << spec << endl; } cout << "---" << endl; // 用例二 for (float i = 2; i <= 2.1; i += 0.01) { float spec = pow(i, 0.2);// shiness:0.2 < 1 cout << "pow(" << i << ", 0.2):" << spec << endl; } cout << "---" << endl; // 用例三 float spec = pow(-0.1, 0.2);// shiness:0.2 < 1 cout << "pow(-0.1, 0.2):" << spec << endl; cout << "---" << endl; // 用例四 spec = pow(-2, 2); // shiness:2 > 1 cout << "pow(-2, 2):" << spec << endl;
看输出可知
-
a^b,a=0.01<1,b=0.2<1,pow(a,b)后大于a
-
a^b,a=2.01>1,b=0.2<1,pow(a,b)后小于a
-
a^b,a<0,b=0.2<1,pow(a,b)后非法数字
-
a^b,a<0,b=2>1 ,pow(a,b)后合法数字
-
-
再回到片段着色器中计算
spec
spec = pow(max(dot(viewDir, reflectDir), 0.0), 0.2);// 光源对当前片段的镜面光影响 vec3 specular = specularStrength * spec * lightColor;// 镜面光照分量
-
当视线与反射光线向量的夹角大于90度,比如夹角是:120
-
dot(viewDir, reflectDir) = cos(120) = -0.5
-
max(-0.5, 0.0) = 0 (注意:max(-0.5 , 0 ) 取最大的为0)
-
从而spec = pow(a=0, b=0.2) = 0
-
则specular = 0,根本没有镜面光照分量,即:这个片段没有镜面高光,不会造成此片段的颜色太亮
个人认为:镜面高光区域的边缘出现了一道很明显的断层,此时视线与反射光线向量的夹角
并不大于90度
(大概率我错了,求大佬指正) -
-
但是图中边缘区域确实有镜面高光断层
则只有可能是视线与反射光线向量的夹角小于90度,并且接近90度,从而cos角度为很小的值,比如夹角是:87.708
-
dot(viewDir, reflectDir) = cos(87.708) 约等于0.04
-
max(dot(viewDir, reflectDir), 0.0) = 0.04
-
spec = pow(0.04, 0.2) = 0.525306 (对应上述代值计算的 用例一)
-
specular = specularStrength * 0.525306 * vec3(1)
镜面光照分量 = specularStrength * spec * 光照颜色白色1,而0.525306约是1/2,1是白色,1/2是一半的白色,所以边缘区域的光高亮,出现断层。
-
-
Blinn-Phong
介绍
-
目的
解决上述Phong提到的问题(在镜面高光区域的边缘出现了一道很明显的断层)
-
解决方式
不再计算反射向量与观察向量的点积。
而是用
半程向量
(Halfway Vector)与法线向量的点积。 -
什么是半程向量
光线与视线夹角一半方向上的一个单位向量
-
图示
-
几个要点
- 当半程向量与法线向量越接近时,镜面光分量就越大
- 不论观察者向哪个方向看,半程向量与表面法线之间的夹角都不会超过90度(除非光源在表面以下)
-
如何计算半程向量
只需要将光线的方向向量和观察向量加到一起,并将结果正规化(Normalize)
// 半程向量 vec3 halfwayDir = normalize(lightDir + viewDir);
例子
-
代码
glsl
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; out VS_OUT{// 4.8节讲的接口块 vec3 FragPos; vec3 Normal; vec2 TexCoords; }vs_out; uniform mat4 projection; uniform mat4 view; uniform mat4 model; void main() { // 虽然没乘model矩阵到世界空间,但model是单位矩阵,可以认为已经在世界空间了 vs_out.FragPos = aPos; vs_out.Normal = aNormal; vs_out.TexCoords = aTexCoords; gl_Position = projection * view * vec4(aPos, 1.0); }
#version 330 core out vec4 FragColor; in VS_OUT{ vec3 FragPos; vec3 Normal; vec2 TexCoords; }fs_in; uniform sampler2D floorTexture; uniform vec3 lightPos; uniform vec3 viewPos; uniform bool blinn; void main() { // 采样纹理作为光照颜色 vec3 color = texture(floorTexture, fs_in.TexCoords).rgb; // 环境光 float ambientStrength = 0.05; vec3 ambient = ambientStrength * color; // 漫反射 vec3 lightDir = normalize(lightPos - fs_in.FragPos); vec3 normal = normalize(fs_in.Normal); float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * color; // 环境光照 vec3 viewDir = normalize(viewPos - fs_in.FragPos); // 是观察者方向,不是观察者看向的方向 float spec = 0.0; if(blinn){ // blinn-pong:半程方向向量与法线方向向量的点积 // 半程向量/// vec3 halfwayDir = normalize(lightDir + viewDir); spec = pow(max(dot(normal, halfwayDir), 0.0), 1); } else { // pong:观察者方向向量与反射方向向量的点积 vec3 reflectDir = reflect(-lightDir, normal); spec = pow(max(dot(viewDir, reflectDir), 0.0), 1); } float specularStrength = 0.3; vec3 specular = specularStrength * spec * vec3(1);// 不像漫反射需要乘以纹理颜色,而是乘以1,表示镜面光的颜色为白色 FragColor = vec4(ambient + diffuse + specular, 1.0); }
cpp
float planeVertices[] = { // positions // normals // texcoords 10.0f, -0.5f, 10.0f, 0.0f, 1.0f, 0.0f, 10.0f, 0.0f, ....... }; // first, configure the cube's VAO (and VBO) unsigned int planeVBO, planeVAO; glGenVertexArrays(1, &planeVAO); glBindVertexArray(planeVAO); glGenBuffers(1, &planeVBO); glBindBuffer(GL_ARRAY_BUFFER, planeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW); // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); unsigned int floorTexture = loadTexture("assest/textures/wood.png"); lightingShader.use(); lightingShader.setInt("texture1", 0); // render loop // ----------- while (!glfwWindowShouldClose(window)) { ...... lightingShader.use(); // view/projection transformations glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); lightingShader.setMat4("projection", projection); lightingShader.setMat4("view", view); lightingShader.setMat4("model", glm::mat4(1.0f)); lightingShader.setInt("blinn", blinn);// 控制是否开启blinn-phong lightingShader.setVec3("viewPos", camera.Position); lightingShader.setVec3("lightPos", lightPos); // render the cube glBindVertexArray(planeVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, floorTexture); glDrawArrays(GL_TRIANGLES, 0, 36); ....... void processInput(GLFWwindow* window){ ....... if (glfwGetKey(window, GLFW_KEY_V) == GLFW_PRESS) { blinn = false; } if (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS) { blinn = true; } }
-
效果
blinn-phong
phong
-
原文与Phong对比
GLSL中遇到的BUG
-
Bug如图
明明在if else 外部定义了spec变量,而glsl却报未定义spec变量,百思不得琦姐。
-
解决方法
不知道为什么,空了一行就能运行了
-
原因找到
由于29行的末尾的中文注释有个**\**,从而引起的问题,可能会引起转义成字符什么问题。
去掉这个**\**,即时不空行,也能正确运行了