Computer Graphics (6)

Lighting and Shading

STEP1 实现Phong光照模型

  • 在场景中绘制一个cube
    ①首先是单独显示一个立方体,这一步在前面已经做过很多次。
    ②然后为这个立方体添加一个光源,我们的最终目的是使场景中的物体能够更加真实地显示出来,所以在添加光照时所参考的也是自然界物体显示颜色的规律:将光源的颜色值与物体的颜色值相乘所得到的就是这个物体所反射的颜色(人眼所能观察的颜色)。在这次的作业中,我们设置光源的颜色为白色,那么应用对物体的颜色计算方法,cube对应的片段着色器中的改变如下:
#version 330 core
out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

如果想要改变光源的颜色,只需要对lightColor进行修改即可。
③接下来是代码的具体实现部分。在OpenGL中实现光源时,我们将光源同样也看做是一个物体,该物体的颜色设置为所期望的光源颜色,接着绑定VAO、VBO。
需要注意为了使光源与物体的改变时互相独立的,需要为光源创建一个新的VAO。同样地,为了使光源与物体的颜色改变时相互独立的,需要为光源建立一个新的着色器。光源对应的着色器(lightv,lightf)是最简单的形式,保持光源的颜色不变即可。
实现结果如下:
这里写图片描述
这样实现的光照场景比较奇怪,相当于只给场景内的物体添加了环境光照明,移动摄像机可以看到六个面的颜色是一致的,并没有预期中的光照效果。

  • 自己写 shader 实现两种 shading: Phong Shading 和 Gouraud Shading ,并解释两种 shading 的实现原理
    如前面所看到的,要模拟现实世界中的光照仅仅修改着色器中cube的渲染颜色是远远不够的。我们需要使用一些光照明模型来使我们的结果更加真实。
    对于lighting而言,我们需要考虑到的因素主要包括光源(位置、类型、颜色)、物体表面的材质(影响各个光照分量的系数)、光在空间传播的轨迹(可以通过物体与光源的相对位置来处理)以及观察者所处的位置。
    光源在上一步中我们已经确定是白色的点光源,位置暂时是固定的;而观察者所处位置也就是观察空间对摄像机的处理,我们在上一次作业中已经实现,所以这次的重点是对于各个光照分量以及物体与光源之间相对位置的处理。这些内容的处理均在着色器中完成。
    ①Phong Shading
    Phong 光照明模型是经验模型,在Phong光照模型中三个主要的光照分量是环境光照(ambient)、漫反射(diffuse)和镜面反射(specular)。具体的计算方式老师在课上已经讲的很清楚:
    这里写图片描述
    其中K分别代表不同材质对不同光照分量的影响因素,I代表不同光照分量的强度。
    首先对于环境光照,处理较为简单,直接将影响系数与光源颜色相乘即可。接着再将得到的环境光照分量同物体颜色相乘作为片段着色器的输出:
void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

接着是漫反射光照。漫反射的结果与物体表面的材质是密切相关的,漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。
我们需要计算的是漫反射后得到的反射光的强度,计算的原理很简单,根据入射光的角度以及对应的法向量就可以计算出结果。显然我们需要将法向量由定点着色器传到片段着色器中,在顶点着色器中的处理如下:

const char *phongv = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
// 法向量的输入
"layout (location = 1) in vec3 aNormal;\n"
"out vec3 FragPos;\n"
"out vec3 Normal;\n"
...
// 如果直接将法向量的值输出到片段着色器,由于顶点着色器对应的
// 是物体坐标系,而片段着色器进行渲染时对应的是世界坐标系,所以
// 会出现错误。这里需要引入法线矩阵来调整输出的法向量
"Normal = mat3(transpose(inverse(model))) * aNormal;"
...
"}\0";

在片段着色器中的处理如下:

...
"void main()\n"
"{\n"
"vec3 norm = normalize(Normal);\n"
// 获取入射光线的角度
"vec3 lightDir = normalize(lightPos - FragPos);\n"
// 设置材质相关的系数
"float diffStrength = 1.0f;\n"
// 入射角的cos值计算,即公式中的cosα
"float diff = max(dot(norm, lightDir), 0.0);\n"
// 漫反射分量的大小
"vec3 diffuse = diffStrength * diff * lightColor;\n"
...
"vec3 result =  (ambient + diffuse) * objectColor;\n"
"FragColor = vec4(result, 1.0);\n"
"}\n\0";

最后是对于镜面反射(高光)的处理。镜面反射出了依赖于入射光和法向量之外,也依赖于观察者所处的位置,以及材质的发光常数。这里就需要我们对于观察者和物体、光线之间的相对位置进行处理。
在这里我们又需要将摄像机(观察者)的位置传到片段着色器中去,在片段着色器中的处理如下:

// 通过main函数中获取camera的位置传入即可
"uniform vec3 viewPos;\n"
...
"float specularStrength = 0.5;\n"
// 计算视线的方向向量
"vec3 viewDir = normalize(viewPos - FragPos);\n"
// 入射光线对应的出射光
"vec3 reflectDir = reflect(-lightDir, norm);\n"
//计算镜面分量,其中32对应反光度,反光度越高得到的散射光越少,高光点就会越小
"float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);\n"
"vec3 specular = specularStrength * spec * lightColor;\n"
...

将以上三个光照分量整合后我们就可以得到Phong Shading下的光照结果:
这里写图片描述
②Gouraud模型
Gouraud模型与Phong模型不同,是在顶点着色器中对光照进行处理。Gouraud模型通过对顶点的赋值来决定像素的颜色值。具体的思路是计算顶点的法向量,决定顶点的光照颜色,然后根据多边形上各点距顶点的距离进行插值,从而绘制多边形上各点投影对应的像素。
但是在对光照的处理方法上二者的方法是相似的。所以Gouraud模型的顶点着色器中对于不同光照分量的处理与Phong模型的片段着色器中对于光照的处理是相似的。将经过这样的计算所得到的光照向量传入片段着色器后,与物体的颜色相乘即可得到片段着色器对每个像素的赋值结果:

const char *gouraudf = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 LightingColor;\n"
"uniform vec3 objectColor;\n"
"void main()\n"
"{\n"
"FragColor = vec4(LightingColor * objectColor, 1.0);\n"
"}\n\0";

最终实现的结果如下图:
这里写图片描述

STEP2 使用 GUI ,使参数可调节,效果实时更改

实现较为简单,重点在于将GUI中获得的参数传入到着色器当中去。在前面对于着色器的学习过程中得知,如果要向着色器中传递数据,添加uniform变量并使用对应的对uniform赋值的函数即可。

  • myShader.h中添加setFloat函数
void setFloat(const std::string& name, float value) {
        glUniform1f(glGetUniformLocation(shaderID, name.c_str()), value);
    }
  • 在render loop中设置可以调节不同的参数
//使用slideFloat获取所希望调节的值
    phongShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
    ...
    phongShader.setFloat("ambientStrength", ambientStrength);
    ...
    phongShader.setMat4("projection", projection);
    ...

实现结果见演示。

Bonus

使光照实时更改。实现方法也十分简单,通过glfwGetTime获取时间后,使用类似作业5中的做法对光源的位置进行更改即可:

if (light_move_with_time) {
    lightPos.x = sin(glfwGetTime()) * 1.0f;
    lightPos.z = cos(glfwGetTime()) * 1.0f;
}
// 将位置传入着色器
phongShader.setVec3("lightPos", lightPos);

实现结果见演示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值