练习一、实现简单的ADS模型
ADS分别指环境光反射(ambient reflection)、漫反射(diffuse reflection)、镜面反射(specular reflection)。环境光反射会影响场景中的所有物体、所有面,漫反射根据光线的入射角度计算物体亮度,镜面反射用来展示物体的光泽度。
想要实现以上效果,我们需要定义光照颜色分量、光照位置坐标、物体颜色分量以及视图位置坐标,传入到着色器中。部分代码如下
glm::vec3 lightColorVec = glm::vec3(1.0, 1.0, 1.0);
glm::vec3 lightPositionVec = glm::vec3(1.0, 1.0, -3.0);
glm::vec3 objectColorVec = glm::vec3(1.0, 0.3, 0.4);
GLuint lightColorLoc = glGetUniformLocation(renderingProgram1, "lightColor");
GLuint lightPositionLoc = glGetUniformLocation(renderingProgram1, "lightPosition");
GLuint objectColorLoc = glGetUniformLocation(renderingProgram1, "objectColor");
GLuint viewPositionLoc = glGetUniformLocation(renderingProgram1, "viewPosition");
glUniform3fv(lightColorLoc, 1, &lightColorVec[0]);
glUniform3fv(lightPositionLoc, 1, &lightPositionVec[0]);
glUniform3fv(objectColorLoc, 1, &objectColorVec[0]);
glUniform3fv(viewPositionLoc, 1, &cameraPos[0]);
需要注意的是这里使用的是glUniform3fv,而非之前的glUniformMatrix4fv。之后需要在着色器中处理光照颜色值。对了,之前还需要传入正方体各个面的法向量,基础篇有讲,这里就不多赘述了。顶点着色器部分代码如下
//顶点着色器
...
layout (location = 1) in vec3 normal;
...
out vec3 modelPos;
out vec3 outNormal;
void main(void)
{
modelPos = vec3(model * vec4(aPos,1.0));
gl_Position = proj * view * vec4(modelPos,1.0);
outNormal = normal;
}
由于需要计算指向光源的向量,在顶点着色器中将模型坐标传了出来。片段着色器部分如下
//片段着色器
...
void main()
{
//ambient 环境光
vec3 ambient = 0.1 * lightColor;
//diffuse 漫反射光
vec3 lightVec = normalize(lightPosition - modelPos);
vec3 normal = normalize(outNormal);
float diff = max(dot(normal,lightVec),0.0);
vec3 diffuse = diff * lightColor;
//specular 镜面反射光
vec3 viewDir = normalize(viewPosition - modelPos);
vec3 reflectDir = reflect(-lightVec,normal);
float specu = pow(max(dot(reflectDir,viewDir),0.0),48);
vec3 specular = specu * lightColor * 0.5;
//result
vec3 result = (diffuse + ambient + specular) * objectColor;
color = vec4(result,1.0);
}
实际生活中若光源照射到与光源方向向量垂直的物体面上,则该面漫反射光照是很强的。在计算漫反射光照值时,首先需要计算指向光源的向量,然后通过光源向量和当前像素法向量的点积(俩向量夹角为0时点积最大)计算出光照值,并且用max函数确保该光照值 diff ≥ 0。
对于镜面反射光,则是镜面反射方向和视图方向夹角为0,其光照值最强。因此通过视图位置和模型位置,计算得到视图方向。而对于镜面反射方向,则需要用到reflect函数。代码中变量viewDir对应的是下图中的向量V,反方向的lightVec与向量L才对应,normal对应的是向量N,这里通过reflect(-lightVec,normal)计算出向量R,注意函数参数的顺序不能乱。同样的用max函数确保该光照值 specu ≥ 0,并且48次方使得高光更加集中,48代表光泽度。
最后将ADS三种反射光加起来与物体颜色相乘便可得到最终的结果。
练习二、材质纹理ADS模型
为了达到上面较精细的模拟真实物体光照效果,我们需要添加材质对应的对照。部分代码如下
Utils::setVec3(renderingProgram1, "lightColor", 1.0, 1.0, 1.0); //精细控制
Utils::setVec3(renderingProgram1, "material.ambient", 0.2, 0.02, 0.08);
Utils::setVec3(renderingProgram1, "material.diffuse", 0.6, 0.3, 0.3);
Utils::setVec3(renderingProgram1, "material.specular", 0.5, 0.5, 0.5);
Utils::setVec3(renderingProgram1, "lightPosition", 1.0, 1.0, -3.0);
Utils::setVec3(renderingProgram1, "viewPosition", cameraPos);
为了方便添加uniform变量,这里将glUniform3fv函数封装到了Utils::setVec3函数中。这里是给了材质较昏暗的红色环境光、稍亮的红色漫反射光以及白色的反射光,顶点着色器代码同练习一,对应的片段着色器部分代码如下
...
struct Material{
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Material material;
...
vec3 ambient = lightColor * material.ambient;
...
vec3 diffuse = lightColor * (diff * material.diffuse);
...
vec3 specular = lightColor * (specu * material.specular);
...
若是一个物体有多种材质,比如木箱子有金属边框呢,这个时候我们就需要用到光照贴图技术,可达到如下的效果~
只需要将材质的ADS光照替换成贴图就可,部分代码如下
//cpp文件
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,texture1);
glUniform1i(glGetUniformLocation(renderingProgram1, "material.diffuse"), 0);
//Utils::setVec3(renderingProgram1, "material.diffuse", 0.6, 0.3, 0.3)被替换
//同理,镜面反射material.specular用纹理texture2代替
//glsl文件
...
struct Light{
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct Material {
sampler2D diffuse;
sampler2D specular;
};
...
vec3 ambient = light.ambient * texture(material.diffuse,textCoods).rgb;
...
vec3 diffuse = light.diffuse * diff * texture(material.diffuse,textCoods).rgb;
...
vec3 specular = light.specular * specu * texture(material.specular,textCoods).rgb;
...
这里为了更准确的模拟光照,将光源也细分成了ADS。
练习三、模拟不同的光源
1、平行光
一般模拟太阳这种无限远的光源,光线方向始终不变并且强度不会衰减,因此只需要添加光源方向就欧克啦。部分代码如下
//cpp代码
//Utils::setVec3(renderingProgram1, "lightPosition", cameraPos);
Utils::setVec3(renderingProgram1, "light.direction", 0.0, 0.0, 3.0);
//glsl代码
struct Light{
vec3 direction;
...
};
...
//vec3 lightVec = normalize(lightPosition - modelPos);
vec3 lightVec = normalize(-light.direction);
最后多加个几个箱子,看起来丰富一点~
2、点光源
一般模拟灯泡、火把这种,光线强度随距离衰减。因此还需要定义光源衰减的分量,部分代码如下
//cpp文件代码
Utils::setFloat(renderingProgram1, "light.constant", 1.0);
Utils::setFloat(renderingProgram1, "light.linear", 0.05);
Utils::setFloat(renderingProgram1, "light.quadratic", 0.032);
//片段着色器代码
...
//attenuation 衰减
float distance = length(light.position - modelPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic*(distance*distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
其中distance为当前片段到光源的距离,constant为常数项,一般为1,保证最近处的亮度稳定。linear线性项控制中距离的衰减程度,quadratic二次项控制远距离的衰减。可得到如下效果
原LearnOpenGL点光源项目,用正方体模拟的光源,这里参照了《计算机图形学编程(使用OpenGL和C++)》,实现了球体,这里略微说下原理,具体的话可查阅这本书哈。
// 计算三角形顶点
for (int i = 0; i <= prec; i++) {
for (int j = 0; j <= prec; j++) {
float y = (float)cos(Utils::toRadians(180.0f - i * 180.0f / prec));
float x = -(float)cos(Utils::toRadians(j * 360.0f / prec)) * (float)abs(cos(asin(y)));
float z = (float)sin(Utils::toRadians(j * 360.0f / prec)) * (float)abs(cos(asin(y)));
vertices[i * (prec + 1) + j] = glm::vec3(x, y, z);
texCoords[i * (prec + 1) + j] = glm::vec2(((float)j / prec), ((float)i / prec));
normals[i * (prec + 1) + j] = glm::vec3(x, y, z);
}
}
部分代码如上,首先将球体拆分成prec层圆,再将对应(i)层圆拆分成prec个点,然后根据相应的角度值计算坐标。
3、聚光灯
一般模拟手电筒,除了强度随距离衰减外,聚光范围外的漫反射和镜面反射强度没有或很弱,因此我们需要计算聚光范围值,部分代码如下
//cpp文件代码
Utils::setVec3(renderingProgram1, "light.position", cameraPos);
Utils::setVec3(renderingProgram1, "light.direction", cameraFront);
Utils::setFloat(renderingProgram1, "light.cutOff", glm::cos(glm::radians(7.5)));
//片段着色器代码
...
vec3 lightVec = normalize(light.position - modelPos);
//ambient 环境光
vec3 ambient = light.ambient * texture(material.diffuse,textCoods).rgb;
float theta = dot(lightVec,-normalize(light.direction));
if(theta > light.cutOff)
{
//diffuse 漫反射光
...
}
else{
color = vec4(ambient,1.0);
}
可以这样理解,我们看向的方向为lightVec,手里拿着的手电筒朝向为light.direction,通过点积计算出两者的余弦值与设定范围角比较,若超出范围的物体,则只显示环境光,效果如下。
本来还想展示柔光灯和混合多种光的,但写到这里发现内容有点多了,后续内容就放到提升篇吧~
利用空余时间,不知不自觉这篇文章又写了一周了。
最后,水平有限,欢迎指正和交流~觉得有用的话点点赞或收藏吧~