OpenGL学习笔记(四)-光照-材质-光照贴图

参考网址:LearnOpenGL 中文版
哔哩哔哩教程

第二章 光照

2.1 颜色

现实生活中人眼看到某一物体的颜色,是它所反射的颜色。如将白光照在红色的玩具上,玩具会吸收白光中除了红色以外的所有子颜色,不被吸收的红色光被反射到我们的眼中,让这个玩具看起来是红色的。
在这里插入图片描述
该颜色反射的定律运用在OpenGL时,给定光源颜色,把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色。

glm::vec3 ambientColor(0.0f, 1.0f, 0.0f);
glm::vec3 objColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = ambientColor * objColor; // = (0.0f, 0.5f, 0.0f);

将环境光和物体光设为uniform变量

uniform vec3 objColor;
uniform vec3 ambientColor;
																	
void main() 										
{										
		FragColor=vec4(objColor*ambientColor,1.0)* mix(texture(TextureA, TexCoord),texture(TextureB, TexCoord),0.2);//从外部输入的纹理图像ourTexture中,使用纹理坐标TexCoord进行采样			
}

在这里插入图片描述

2.2 基础光照

2.2.1 冯氏光照模型

冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。
在这里插入图片描述
2.2.2 环境光照

即使在黑暗的情况下,世界上也仍然有一些光亮。为模拟这种情况,使用一个很小的环境光照常量,添加到物体的最终颜色中。即便场景中没有直接的光源,也能看起来存在有一些发散的光。
环境光照分量ambient:光的颜色lightColor乘以一个常量环境因子ambientStrength

float ambientStrength=0.5;
vec3 ambient=ambientStrength*lightColor;

2.2.3 漫反射光照

漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。如果光线垂直于物体表面,这束光对物体的影响会最大化(更亮)。为了测量光线和片段的角度,使用片段法向量与光线方向向量的点乘计算。两个单位向量的点乘结果为一个标量,夹角越小,点乘结果越倾向于1,光对片段颜色的影响就应该越大。
在这里插入图片描述
1、片段法向量:把法线数据手工添加到顶点数据中,并更新顶点属性指针吗,就可以在顶点着色器中输入片段法向量。

  • 如何将法向量变换到世界坐标系:法向量只是一个方向向量,没有w分量,位移不会影响到法向量。因此,将法向量乘以模型矩阵中的旋转部分,只实施缩放和旋转变换,变换到世界坐标系中。
  • 当模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面,通过法线矩阵可移除对法向量错误缩放的影响。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

Normal= mat3(transpose(inverse(modelMat))) * aNormal;	
...

2、光线方向:通过光源的位置向量和片段的位置向量计算。

  • 光源的位置是一个静态变量,在片段着色器中把它声明为uniform vec3 lightPos

  • 片段位置位于世界坐标系中,可以通过把顶点位置属性乘以模型矩阵来把它变换到世界空间坐标。Frapos=vec3(modelMat*vec4(aPos, 1.0))

  • 光的方向向量是光源位置向量与片段位置向量之间的向量差。

    vec3 lightDir=normalize(lightPos-Frapos);
    vec3 norm=normalize(Normal);
    

3、漫反射光照:对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫发射影响。结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小。如果两个向量之间的角度大于90度,点乘的结果就会变成负数,将这些结果设为0。

float diff=max(dot(lightDir,norm),0.0);	
vec3 diffuse=diff*lightColor;

2.2.4 镜面光照

镜面光照依据光的方向向量、物体的法向量和观察方向决定。镜面光照是基于光的反射特性。如果从反射向量方向上观察表面所反射的光,镜面光照都会达到最大化。
在这里插入图片描述

1、 计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。当我们去看光被物体所反射的那个方向的时候,会看到一个高光。

2、 把摄像机位置坐标viewPos传给片段着色器,使用摄像机位置viewPos和片段的位置Frapos来计算观察向量viewDir(视线方向)。利用reflect函数计算沿着法线轴的反射向量reflectDirreflect函数要求第一个向量是从光源指向片段位置的向量,对lightDir向量进行了取反。

uniform vec3 viewPos;

vec3 viewDir=normalize(viewPos-Frapos);
vec3 reflectDir=reflect(-lightDir,norm);

3、定义一个镜面强度变量specularStrength,给镜面高光一个中等亮度颜色。

4、计算视线方向与反射方向的点乘,然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小(这与幂函数的特性有关)。
在这里插入图片描述

float spec=pow(max(dot(viewDir,reflectDir),0.0),32);
vec3 specular=	specularStrength*spec*lightColor;

5、物体总的光照为三个分量之和

FragColor=vec4((ambient+diffuse+specular)*objColor,1.0);

在这里插入图片描述

2.3 材质

2.3.1 设置材质

1、在现实世界里,每个物体会对光产生不同的反应。如果想要模拟多种类型的物体,必须为每个物体定义一个材质属性。

  • 在上一节中,已知影响物体视觉输出的有:(1)光的颜色,光的强度;(2)环境光照,漫反射光照,镜面光照;(3)物体材质颜色。
  • 在本节中,用四个分量定义物体材质颜色:环境光反射率、漫反射率、镜面反射率和光泽度。通过为每个分量指定一个颜色,就能够精确控制材质颜色。
  • 材质可以理解为对光的反射特性。例如树叶看起来是绿色的,是因为树叶反射了绿色的光。剥离掉树叶这种物质,提取出树叶对光“处理”的特性,这就叫树叶材质
    在这里插入图片描述

2、在片段着色器中,创建一个结构体(Struct)来储存物体的材质属性。

  • ambient定义了在环境光照下这个物体反射的颜色,通常这是和物体颜色相同的颜色;
  • diffuse定义了在漫反射光照下物体的颜色,通常也要设置为我们需要的物体颜色;
  • specular定义了镜面光照对物体的颜色影响;
  • shininess影响镜面高光的散射/半径。
#version 330 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 

uniform Material material;
  • 上一节中,物体的视觉输出
void main()
{
    // 环境光
    vec3 ambient = ambientStrength * lightColor* objectColor;
    // 漫反射 
    vec3 diffuse = lightColor*(diff *objectColor) ;
    // 镜面光
    vec3 specular = specularStrength* lightColor * (spec* objectColor) ;  
    //物体
    vec3 result = ambient + diffuse + specular ;
    FragColor = vec4(result, 1.0);
} 
  • 将物体材质颜色改用三个分量表示,则片段着色器中物体视觉输出为
void main()
{    
    // 环境光
    vec3 ambient = ambientStrength * lightColor * material.ambient;
    // 漫反射 
    vec3 diffuse = lightColor * (diff * material.diffuse);
    // 镜面光
    vec3 specular = specularStrength*lightColor * (spec * material.specular);  
	//物体
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}

3、在渲染主函数中,定义一个材质类,创建材质对象

Material(Shader *_shader, glm::vec3 _ambient, glm::vec3 _diffuse, glm::vec3 _specular, float _shiness);

利用材质类对象,对着色器中的材质变量进行赋值

//设置材质的反射率
myMaterial->shader->setVec3f("material.ambient", myMaterial->ambient);
myMaterial->shader->setVec3f("material.diffuse", myMaterial->diffuse);
myMaterial->shader->setVec3f("material.specular", myMaterial->specular);
myMaterial->shader->setFloat("material.shiness", myMaterial->shiness);

2.3.2 设置光照

光源对环境光、漫反射和镜面光分量也具有着不同的强度。上一节,通过使用一个强度值改变环境光和镜面光强度。本节中,需要为每个光照分量都指定一个强度向量。

1、创建光源结构体。环境光照通常会设置为一个比较低的强度,不希望环境光颜色太过显眼。光源的漫反射分量通常设置为光所具有的颜色,通常是一个比较明亮的白色。镜面光分量通常会保持为vec3(1.0),以最大强度发光。

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

2、更新着色器

void main() 										
{		
		//环境光照
		vec3 ambient=light.ambient*material.ambient;
		//漫反射光照
		vec3 diffuse=light.diffuse*(diff*material.diffuse);
		//镜面光照
		vec3 specular=light.specular*(spec*material.specular);
		//物体
		FragColor=vec4(ambient+diffuse+specular,1.0);		
}

3、设置光照强度

//设置光源变量
myShader->setVec3f("light.position", glm::vec3(8.0f, 10.0f, 5.0f));
myShader->setVec3f("light.ambient",glm::vec3 (0.2f, 0.2f, 0.2f));
myShader->setVec3f("light.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
myShader->setVec3f("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));

2.4 光照贴图

上一节中的那个材质系统它只是一个最简单的模型,所以引入漫反射和镜面光贴图(Map)。对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。

2.4.1 漫反射贴图

1、目的:根据片段在物体上的位置来获取颜色,对物体的每个片段单独设置漫反射颜色。

2、实现:通过一个纹理(覆盖物体的图像),逐片段索引其独立的颜色值。在光照场景中被称为漫反射贴图(Diffuse Map),表现了物体所有的漫反射颜色的纹理图像。

3、将Material结构体中的vec3漫反射分量替换为sampler2D类型的漫反射贴图。

  • 由于环境光颜色在几乎所有情况下都等于漫反射颜色,移除了环境光材质颜色分量。

  • sampler2D是不透明类型(Opaque Type),不能实例化,只能通过uniform来定义它。Material结构体封装了不透明类型变量diffuse,因此只能使用uniform实例化这个结构体。

    struct Material {
        sampler2D diffuse;
        vec3      specular;
        float     shininess;
    }; 
    

4、通过纹理采样器和纹理坐标,即可从纹理中采样片段的漫反射颜色值,通过该颜色值就可影响漫反射分量的大小,颜色值较大的地方,漫反射分量大。

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

5、在顶点数据中加入纹理坐标,更新属性指针,传入顶点坐标

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;

void main()
{
    ...
    TexCoords = aTexCoords;
}

6、加载图像作为纹理,将纹理与纹理单元绑定,并将纹理单元赋值到material.diffuse这个uniform采样器中。

LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA, 0);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, myMaterial->diffuse);

myMaterial->shader->setInt("material.diffuse", 0);

在这里插入图片描述
2.4.2 镜面光贴图

1、在上图中,木质箱子不应该有这么强的镜面高光的,可以将材质镜面光分量设置为vec3(0.0)来解决,但这也意味着钢制边框将不再能够显示镜面高光了。因此,希望可以让物体的某些部分以不同的强度显示镜面高光。

2、同样使用一个镜面高光的纹理贴图,根据纹理贴图的灰度值(或者颜色值),来定义物体每部分的镜面光强度。
在这里插入图片描述
3、镜面高光的强度可以通过图像每个像素的灰度值获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示,在片段着色器中,取样对应的颜色分量并将它乘以光源的镜面强度。一个像素越「白」,乘积就会越大,物体的镜面光分量就会越亮。由于木头材质应该没有镜面高光,所以镜面高光贴图的整个木头部分全部都转换成了黑色。

4、在片段着色器中,设置镜面光贴图的采样器

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};

vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

5、在主程序中,同样加载图像作为贴图

Material* myMaterial = new Material(myShader, 
		LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA, Shader::DIFFUSE),
		LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA, Shader::SPECULAR),
		32.0f );

myMaterial->shader->setInt("material.specular", Shader::SPECULAR);//采样器与纹理单元绑定

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值