材质不同于灯光,但与灯光一起影响最终的颜色输出;本主题总结材质模型,并实现材质的效果;这个部分的关键还是四元数变换。本主题内容包含:
1. 材质 + 灯光模型;
2. 材质与灯光的效果实现;
材质效果
材质说明
-
材质实际是灯光的综合效果设计:
- 环境光(所有方向的强弱都一样)
- 漫反射光(由光的方向决定强弱)
- 镜面光(有观察者视角决定强弱)
-
材质由六个参数控制:
- 颜色
- 环境光颜色:
,物体环境光颜色:
- 环境光颜色:
,物体漫反射光颜色:
- 环境光颜色:
,物体镜面光颜色:
- 环境光颜色:
- 颜色
材质的颜色,决定材质对颜色的反射强弱与吸收强弱
-
材质的输出模型为:
- 输出颜色 = 环境光因子 * 环境光输出 + 漫反射光因子*漫反射光输出 + 镜面光因子*镜面光输出
-
备注:
-
因为是所有方向都一样,所以是直接输出;下面重点总结下
与
。
-
环境光输出模型
漫反射光输出模型
示意图
慢发射材质原理图
- 几个参数的表示:
-
-s
表示光源方向(注意正负表示的方向,s表示顶点位置到灯光位置,-s表示从灯光到顶点位置) -
r
表示反射光; -
n
表示顶点法向量; -
θ
表示光线与法向量的夹角。
-
-
已知:
-
顶点法向量
- 顶点坐标
;
- 光源位置
;
-
-
需要计算的参数:
-
-s
:表示光源方向(注意正负表示的方向,s表示顶点位置到灯光位置,-s表示从灯光到顶点位置) -
r
或者θ
(这里只计算θ
足矣,不需要计算反射向量):反射方向,光源方向与法向量夹角;
-
-
慢发射光输出计算公式:
-
表示内积。
- 根据余弦函数的性质:光线垂直与顶点,则漫反射强;否则光线弱。
-
-
光线位置
-
顶点位置
-
-
-
补充:
- 内积计算:
- 如果
是单位向量,则
- 内积计算:
镜面光输出模型
示意图
镜面光与材质原理图
-
几个参数的表示:
-
-s
表示光源方向(注意正负表示的方向) -
r
表示反射光; -
n
表示顶点法向量; -
θ
表示光线与法向量的夹角; -
v
表示观察者(照相机)方向;
-
-
已知:
-
顶点法向量
- 顶点坐标
;
- 光源位置
;
- 照相机位置
;
- 镜面光
-
-
需要计算的参数:
-
-s
:光源方向 -
v
:照相机与反射光夹角; -
r
或者θ
(这里必须计算反射光方向):反射方向,反射光与法向量夹角;
-
-
镜面光输出计算公式:
-
,
一般会增加一个指数因子控制镜面光的光泽度(Shininess)。
-
表示张相机方向与反射光方向夹角。
-
-
照相机位置
- 如果从照相机坐标系来确定照相机位置,则照相机位置在新的坐标系中永远是原点。也就是说,照相机方向,实际是顶点向量的方向(或者反方向)
-
-
,
在GLSL中光线反射方向可以使用reflect函数直接计算。
-
-
光线位置
-
顶点位置
-
-
-
-
提示:
- 如果3D对象是圆,可以使用顶点位置代替法向量。
最终输出模型
实现
通用模型
- 不考虑漫反射因子计算
- 不考虑镜面光因子计算
Shader代码
- 在顶点着色器计算,然后传递给片着色器直接输出。
- 顶点着色器
#version 410 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 perspective;
// 定义材质:物体的三个颜色 + 镜面光的光泽度(镜面反光度)
struct materials{
vec3 k_a;
vec3 k_d;
vec3 k_s;
float shiness;
};
uniform materials material;
// 光照颜色
struct lights{
vec3 l_pos; // 光线位置(世界坐标)
vec3 l_a; // 环境光颜色(
vec3 l_d; // 漫反射颜色
vec3 l_s; // 镜面光颜色
};
uniform lights light;
out vec4 color_light; // 最终输出颜色
void main(){
vec3 a_out = material.k_a * light.l_a;
vec3 d_out = material.k_d * light.l_d;
vec3 s_out = material.k_s * light.l_s;
vec3 color_out = a_out + d_out + s_out;
color_light = vec4(color_out, 1.0f);
gl_Position = perspective * view * model * vec4(aPos, 1.0);
}
- 片着色器
#version 410 core
in vec4 color_light;
out vec4 FragColor;
void main(){
FragColor = color_light;
}
数据传递代码
- 参数传递封装代码
void GL_Shader::setUniform_1f(const char *name, GLfloat fvalue){
GL_Shader::open();
GLint location = glGetUniformLocation(programID, name);
glUniform1i(location, fvalue);
GL_Shader::close();
}
void GL_Shader::setUniform_3fv(const char *name, const GLfloat *vec3){
GL_Shader::open();
GLint location = glGetUniformLocation(programID, name);
glUniform3fv(location, 1, vec3);
GL_Shader::close();
}
- 参数传递调用代码
/*材质*/
shader.setUniform_1f("material.shiness", 1.0f);
shader.setUniform_3fv("material.k_a",glm::value_ptr(glm::vec3(0.9f, 0.5f, 0.3f)));
shader.setUniform_3fv("material.k_d",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
shader.setUniform_3fv("material.k_s",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
/*灯光*/
shader.setUniform_3fv("light.l_pos",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
shader.setUniform_3fv("light.l_a",glm::value_ptr(glm::vec3(0.4f, 0.4f, 0.4f)));
shader.setUniform_3fv("light.l_d",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
shader.setUniform_3fv("light.l_s",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
- 其中环境光设置为灰度为0.4的灰色,材质的反光能力为:红色0.9, 绿色0.5, 蓝色0.3 ,效果如下:
- 环境材质输出效果
漫反射模型
关于矩阵变换的几个常识
-
物体的法向量的矩阵变换;
- 物体的法向量会受旋转,缩放影响,但不受位置影响;
- 影响矩阵:
- Model
- View
-
物体的位置受所有变换因素影响;
- 影响矩阵:
- Model
- View
- 影响矩阵:
-
灯光的位置受所有因素影响;
- 灯光位置与物体的变换无关,不受物体变换影响
- 影响矩阵:
- View
- 提示:
- 透视矩阵已经与物体的变换无关,与最后视觉效果有关,所以都不考虑透视矩阵。但最终物体的输出坐标是需要考虑的。
Shader脚本
- 顶点着色器
#version 410 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 perspective;
// 定义材质:物体的三个颜色 + 镜面光的光泽度(镜面反光度)
struct materials{
vec3 k_a;
vec3 k_d;
vec3 k_s;
float shiness;
};
uniform materials material;
// 光照颜色
struct lights{
vec3 l_pos; // 光线位置(世界坐标)
vec3 l_a; // 环境光颜色(
vec3 l_d; // 漫反射颜色
vec3 l_s; // 镜面光颜色
};
uniform lights light;
out vec4 color_light; // 最终输出颜色
void main(){
// 1. 首先处理矩阵
mat4 mv = view * model; // 处理与位置有关的坐标变换
mat3 n_mv = mat3(vec3(mv[0]), vec3(mv[1]), vec3(mv[2])); //处理与位置无关的坐标变换(比如法向量):去掉最后一列的位移
// 2. 变换法向量与顶点坐标,光线坐标
vec3 n = normalize(n_mv * aPos); // 变换后的法向量:我们采用球体的顶点作为初始法向量;
vec3 v_pos = vec3(mv * vec4(aPos,1.0)); // 变换后的顶点
vec3 v_light = vec3(view * vec4(light.l_pos,1.0)); // 变换后灯光的位置(灯光不受物体变换影响,只受照相机影响)
// A.环境光
vec3 a_out = material.k_a * light.l_a;
// B.漫反射光
// B1. 计算灯光方向
vec3 s = normalize(vec3(v_light - v_pos));
// B2. 计算漫反射因子
float diff = max(dot(s, n),0.0); // 计算内积,并确保不为负数
vec3 d_out = material.k_d * light.l_d * diff; // 添加漫反射因子
vec3 s_out = material.k_s * light.l_s;
vec3 color_out = a_out + d_out + s_out;
color_light = vec4(color_out, 1.0f);
gl_Position = perspective * view * model * vec4(aPos, 1.0);
}
- 代码重点在其中矩阵变换的地方,如果变换做的有问题,则效果会很差。
数据传递代码
// 添加变换
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::mat4 view = glm::lookAt(
cameraPos,
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 perspective = glm::perspective(
glm::radians(90.0f),
1.0f,
0.0f,100.0f);
shader.setUniform_4fm("model", glm::value_ptr(model));
shader.setUniform_4fm("view", glm::value_ptr(view));
shader.setUniform_4fm("perspective", glm::value_ptr(perspective));
/*材质*/
shader.setUniform_1f("material.shiness", 100.0f);
shader.setUniform_3fv("material.k_a",glm::value_ptr(glm::vec3(0.9f, 0.5f, 0.3f)));
shader.setUniform_3fv("material.k_d",glm::value_ptr(glm::vec3(0.8f, 0.7f, 0.3f)));
shader.setUniform_3fv("material.k_s",glm::value_ptr(glm::vec3(0.8f, 0.8f, 0.8f)));
/*灯光*/
shader.setUniform_3fv("light.l_pos",glm::value_ptr(glm::vec3(3.0f, 3.0f, -3.0f)));
shader.setUniform_3fv("light.l_a",glm::value_ptr(glm::vec3(0.4f, 0.4f, 0.4f)));
shader.setUniform_3fv("light.l_d",glm::value_ptr(glm::vec3(1.0f, 1.0f, 1.0f)));
shader.setUniform_3fv("light.l_s",glm::value_ptr(glm::vec3(0.0f, 0.0f, 0.0f)));
- 效果如下:
- 漫反射材质输出效果
镜面光模型
Shader脚本
#version 410 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 perspective;
// 定义材质:物体的三个颜色 + 镜面光的光泽度(镜面反光度)
struct materials{
vec3 k_a;
vec3 k_d;
vec3 k_s;
float shiness;
};
uniform materials material;
// 光照颜色
struct lights{
vec3 l_pos; // 光线位置(世界坐标)
vec3 l_a; // 环境光颜色(
vec3 l_d; // 漫反射颜色
vec3 l_s; // 镜面光颜色
};
uniform lights light;
out vec4 color_light; // 最终输出颜色
void main(){
// 1. 首先处理矩阵
mat4 mv = view * model; // 处理与位置有关的坐标变换
mat3 n_mv = mat3(vec3(mv[0]), vec3(mv[1]), vec3(mv[2])); //处理与位置无关的坐标变换(比如法向量):去掉最后一列的位移
// 2. 变换法向量与顶点坐标,光线坐标
vec3 n = normalize(n_mv * aPos); // 变换后的法向量:我们采用球体的顶点作为初始法向量;
vec3 v_pos = vec3(mv * vec4(aPos,1.0)); // 变换后的顶点
vec3 v_light = vec3(view * vec4(light.l_pos, 1.0)); // 变换后灯光的位置(灯光不受物体变换影响,只受照相机影响)
// A.环境光
vec3 a_out = material.k_a * light.l_a;
// B.漫反射光
// B1. 计算灯光方向
vec3 s = normalize(vec3(v_light - v_pos));
// B2. 计算漫反射因子
float diff = max(dot(s, n),0.0); // 计算内积,并确保不为负数
vec3 d_out = material.k_d * light.l_d * diff; // 添加漫反射因子
// C.镜面光
// C1. 照相机视角方向
vec3 v = normalize(-v_pos); // 在照相机坐标,观察者就在原点,所以观察者向量就是原点-物体向量
// C2. 光想反正方向
vec3 r = reflect(s, n);
// C3. 计算镜面光因子
float specular = max(dot(r, v), 0.0);
// C4. 镜面光输出
vec3 s_out = material.k_s * light.l_s * pow(specular, material.shiness);
// 最终的颜色输出
vec3 color_out = a_out + d_out + s_out;
color_light = vec4(color_out, 1.0f);
gl_Position = perspective * view * model * vec4(aPos, 1.0);
}
数据传递脚本
- 只修改了部分数据
// 添加变换
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::mat4 view = glm::lookAt(
cameraPos,
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 perspective = glm::perspective(
glm::radians(90.0f),
1.0f,
0.0f,100.0f);
shader.setUniform_4fm("model", glm::value_ptr(model));
shader.setUniform_4fm("view", glm::value_ptr(view));
shader.setUniform_4fm("perspective", glm::value_ptr(perspective));
/*材质*/
shader.setUniform_1f("material.shiness", 128.0f);
shader.setUniform_3fv("material.k_a",glm::value_ptr(glm::vec3(0.9f, 0.5f, 0.3f)));
shader.setUniform_3fv("material.k_d",glm::value_ptr(glm::vec3(0.8f, 0.7f, 0.3f)));
shader.setUniform_3fv("material.k_s",glm::value_ptr(glm::vec3(0.8f, 0.8f, 0.8f)));
/*灯光*/
shader.setUniform_3fv("light.l_pos",glm::value_ptr(glm::vec3(3.0f, 3.0f, -3.0f)));
shader.setUniform_3fv("light.l_a",glm::value_ptr(glm::vec3(0.4f, 0.4f, 0.4f)));
shader.setUniform_3fv("light.l_d",glm::value_ptr(glm::vec3(1.0f, 1.0f, 1.0f)));
shader.setUniform_3fv("light.l_s",glm::value_ptr(glm::vec3(1.0f, 1.0f, 1.0f)));
- 效果如下:
- 材质效果
- 其中的镜面因子是128。