GL02-03:OpenGL材质

材质不同于灯光,但与灯光一起影响最终的颜色输出;本主题总结材质模型,并实现材质的效果;这个部分的关键还是四元数变换。本主题内容包含:
  1. 材质 + 灯光模型{Color}_{out} =A_{out} + D_{out} + S_{out}
  2. 材质与灯光的效果实现;
  

材质效果


材质说明

  • 材质实际是灯光的综合效果设计:

    • 环境光(所有方向的强弱都一样)
    • 漫反射光(由光的方向决定强弱)
    • 镜面光(有观察者视角决定强弱)
  • 材质由六个参数控制:

    • 颜色
      • 环境光颜色:L_a,物体环境光颜色:K_a
      • 环境光颜色:L_d,物体漫反射光颜色:K_d
      • 环境光颜色:L_s,物体镜面光颜色:K_s
  • 材质的颜色,决定材质对颜色的反射强弱与吸收强弱

  • 材质的输出模型为:

    • 输出颜色 = 环境光因子 * 环境光输出 + 漫反射光因子*漫反射光输出 + 镜面光因子*镜面光输出
    • {Color}_{out} =A_{out} + D_{out} + S_{out}
  • 备注:

    • \color{red}{环境光输出模型}因为是所有方向都一样,所以是直接输出;下面重点总结下\color{green}{漫反射光输出模型}\color{blue}{镜面光输出模型}

环境光输出模型

  • A_{out} = L_a \times K_a

漫反射光输出模型

示意图

慢发射材质原理图
  • 几个参数的表示:
    • -s 表示光源方向(注意正负表示的方向,s表示顶点位置到灯光位置,-s表示从灯光到顶点位置)
    • r表示反射光;
    • n表示顶点法向量;
    • θ表示光线与法向量的夹角。
  • 已知:

    • n顶点法向量
    • 顶点坐标pos_{V}
    • 光源位置pos_{L}
  • 需要计算的参数:

    • -s:表示光源方向(注意正负表示的方向,s表示顶点位置到灯光位置,-s表示从灯光到顶点位置)
    • r或者θ(这里只计算θ足矣,不需要计算反射向量):反射方向,光源方向与法向量夹角;
  • 慢发射光输出计算公式:

    • D_{out} = L_d \times K_d ( s \cdot n) = L_d \times K_d\color{red}{cos}(\theta) \qquad \qquad \cdot 表示内积。
      • 根据余弦函数的性质:光线垂直与顶点,则漫反射强;否则光线弱。
      • s =pos_{L} - pos_{V}
        • pos_{L}光线位置
        • pos_{V}顶点位置
  • 补充:

    • 内积计算:a\cdot b = |a| |b| \color{red}{cos} (\theta)
    • 如果a, b是单位向量,则a \cdot b = \color{red}{cos} (\theta)

镜面光输出模型

示意图

镜面光与材质原理图
  • 几个参数的表示:

    • -s 表示光源方向(注意正负表示的方向)
    • r表示反射光;
    • n表示顶点法向量;
    • θ表示光线与法向量的夹角;
    • v表示观察者(照相机)方向;
  • 已知:

    • n顶点法向量
    • 顶点坐标pos_{V}
    • 光源位置pos_{L}
    • 照相机位置pos_{C}
    • 镜面光
  • 需要计算的参数:

    • -s:光源方向
    • v:照相机与反射光夹角;
    • r或者θ(这里必须计算反射光方向):反射方向,反射光与法向量夹角;
  • 镜面光输出计算公式:

    • S_{out} = L_s \times K_s ( r \cdot v)^p = L_d \times K_s \color{red}{cos}(\eta) ^p\qquad \qquad一般会增加一个指数因子控制镜面光的光泽度(Shininess)。
      • \eta表示张相机方向与反射光方向夹角。
      • v= pos_{C} - pos_{V}
        • pos_{C}照相机位置
        • 如果从照相机坐标系来确定照相机位置,则照相机位置在新的坐标系中永远是原点。也就是说,照相机方向,实际是顶点向量的方向(或者反方向)
      • r = -s + 2(s\cdot n)n, \qquad \qquad在GLSL中光线反射方向可以使用reflect函数直接计算。
      • s =pos_{L} - pos_{V}
        • pos_{L}光线位置
        • pos_{V}顶点位置
  • 提示:
    • 如果3D对象是圆,可以使用顶点位置代替法向量。

最终输出模型

  • Color_{out} = L_a \times K_a + L_d \times K_d ( s \cdot n) + L_s \times K_s ( r \cdot v)^p

实现

通用模型

  • 不考虑漫反射因子计算
  • 不考虑镜面光因子计算

Shader代码

  • 在顶点着色器计算,然后传递给片着色器直接输出。
  1. 顶点着色器
#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);
}

  1. 片着色器
#version 410 core
in vec4 color_light; 
out vec4 FragColor;
void main(){
    FragColor = color_light;
}

数据传递代码

  1. 参数传递封装代码
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();
}
  1. 参数传递调用代码
    /*材质*/
    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 ,效果如下:
    • 环境材质输出效果

漫反射模型

关于矩阵变换的几个常识

  1. 物体的法向量的矩阵变换;

    • 物体的法向量会受旋转,缩放影响,但不受位置影响;
    • 影响矩阵:
      • Model
      • View
  2. 物体的位置受所有变换因素影响;

    • 影响矩阵:
      • Model
      • View
  3. 灯光的位置受所有因素影响;

    • 灯光位置与物体的变换无关,不受物体变换影响
    • 影响矩阵:
      • View
  • 提示:
    • 透视矩阵已经与物体的变换无关,与最后视觉效果有关,所以都不考虑透视矩阵。但最终物体的输出坐标是需要考虑的。

Shader脚本

  1. 顶点着色器
#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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容