【实践LearnOpenGL项目】光照基础篇

练习一、实现简单的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,通过点积计算出两者的余弦值与设定范围角比较,若超出范围的物体,则只显示环境光,效果如下。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​  

本来还想展示柔光灯和混合多种光的,但写到这里发现内容有点多了,后续内容就放到提升篇吧~

利用空余时间,不知不自觉这篇文章又写了一周了。

最后,水平有限,欢迎指正和交流~觉得有用的话点点赞或收藏吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值