实现Phong光照模型
Phong Shading
Phong Shading在每个像素根据法向量计算光照,而每个像素的法向量由顶点的法向量插值求得。因此,要实现Phong Shading,顶点着色器需要输出顶点的位置和法向量。输出的位置由世界坐标表示,所以要用model矩阵进行变换后输出。为避免缩放后法向量不垂直于平面,要用法线矩阵对法向量进行变换后输出。顶点着色器的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal;
out vec3 FragPos; out vec3 Normal;
uniform mat4 model; uniform mat4 view; uniform mat4 projection;
void main() { FragPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; gl_Position = projection * view * model * vec4(aPos, 1.0); }
|
Phong Shading在片段着色器根据法向量计算光照,要在片段着色器中计算环境光、漫反射和镜面反射三种光照效果。环境光强度仅由全局变量ambientStrength决定。漫反射光的强度由全局变量diffuseStrength以及光线与法向量的夹角决定。夹角的cos值,可以通过法向量的单位向量与入射点到光源方向的单位向量的点乘求得,且cos值不小于0。镜面反射光强度由全局变量specularStrength以及入射点到摄像机方向与全反射光方向的夹角决定,夹角的cos值也是通过单位向量的点乘求得。三种光照强度相加后,与物体颜色和光照颜色相乘,得到显示的颜色。实现的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| in vec3 Normal; in vec3 FragPos;
out vec4 FragColor;
uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos; uniform vec3 viewPos;
uniform float ambientStrength; uniform float diffuseStrength; uniform float specularStrength;
void main() { vec3 ambient = ambientStrength * lightColor; vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diffuseStrength * diff * lightColor;
vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor; FragColor = vec4(result, 1.0); }
|
Gouraud Shading
与Phong Shadings不同,Gouraud Shading在顶点处计算光照,其他像素的颜色由顶点颜色插值得到,所以光照的计算放在顶点着色器中。具体的计算方法与Phong Shading类似。顶点着色器最终输出光照的颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal;
out vec3 LightingColor;
uniform vec3 lightColor; uniform vec3 lightPos; uniform vec3 viewPos;
uniform mat4 model; uniform mat4 view; uniform mat4 projection;
uniform float ambientStrength; uniform float diffuseStrength; uniform float specularStrength;
void main() { gl_Position = projection * view * model * vec4(aPos, 1.0);
vec3 Position = vec3(model * vec4(aPos, 1.0)); vec3 Normal = mat3(transpose(inverse(model))) * aNormal;
vec3 ambient = ambientStrength * lightColor; vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - Position); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diffuseStrength * diff * lightColor;
vec3 viewDir = normalize(viewPos - Position); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor;
LightingColor = ambient + diffuse + specular; }
|
光线颜色输入到片段着色器中,与物体颜色相乘,得到显示的颜色:
1 2 3 4 5 6 7 8 9
| in vec3 LightingColor;
out vec4 FragColor;
uniform vec3 objectColor;
void main() { FragColor = vec4(LightingColor * objectColor, 1.0); }
|
使用GUI,使参数可调节,效果实时更改
Shading切换
创建两个shader对象,使用GUI进行切换
1 2 3
| Shader phongLightingShader("phong.vs", "phong.fs"); Shader gouraudLightingShader("gouraud.vs", "gouraud.fs"); Shader *lightingShader = &phongLightingShader;
|
在渲染循环中切换:
1 2 3 4 5 6 7 8 9 10 11 12
| ImGui::Begin("Shading Type"); ImGui::BeginGroup(); ImGui::RadioButton("Phong", &shadingType, 1); ImGui::RadioButton("Gouraud", &shadingType, 2); if (shadingType == 1) { lightingShader = &phongLightingShader; } else if (shadingType == 2) { lightingShader = &gouraudLightingShader; } ImGui::EndGroup(); ImGui::End();
|
参数可调节
用SliderFloat调节三个参数,然后设置全局变量。
1 2 3 4 5 6 7 8 9
| ImGui::Begin("Light Strength"); ImGui::SliderFloat("Ambient Strength", &ambientStrength, 0, 1); ImGui::SliderFloat("Diffuse Strength", &diffuseStrength, 0, 1); ImGui::SliderFloat("Specular Strength", &specularStrength, 0, 1); ImGui::End();
lightingShader->setFloat("ambientStrength", ambientStrength); lightingShader->setFloat("diffuseStrength", diffuseStrength); lightingShader->setFloat("specularStrength", specularStrength);
|
Bonus:移动光源
用三角函数可以实现光源在x方向的来回移动:
1 2 3 4 5 6 7 8 9
| float time = (float)glfwGetTime(); if (ImGui::MenuItem("move", NULL, &move)) { moveTime = time; } if (move) lightPos.x = sin(time - moveTime + 3.14 / 6) * 2.0f; else lightPos.x = 1.0f; lightingShader->setVec3("lightPos", lightPos);
|
渲染效果
Phong Shading
Gouraud Shading
调参
运动光源