重新自学学习openGL 之光照多光源
其实前面我们已经将光照的基本知识学习完毕了.本章主要学习的是如何在shader中使用多个光源对物体进行渲染.
本章主要因为不涉及新的知识点,只是举例说明多光源的应用
例子
模拟一个类似太阳的定向光(Directional Light)光源,四个分散在场景中的点光源(Point Light),以及一个手电筒(Flashlight)。
设计
为了在场景中使用多个光源,我们希望将光照计算封装到GLSL函数中。这样做的原因是,每一种光源都需要一种不同的计算方法,而一旦我们想对多个光源进行光照计算时,代码很快就会变得非常复杂。如果我们只在main函数中进行所有的这些计算,代码很快就会变得难以理解。
GLSL中的函数和C函数很相似,它有一个函数名、一个返回值类型,如果函数不是在main函数之前声明的,我们还必须在代码文件顶部声明一个原型。我们对每个光照类型都创建一个不同的函数:定向光、点光源和聚光。
当我们在场景中使用多个光源时,通常使用以下方法:我们需要有一个单独的颜色向量代表片段的输出颜色。对于每一个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上。所以场景中的每个光源都会计算它们各自对片段的影响,并结合为一个最终的输出颜色。大体的结构会像是这样:
void main()
{
vec3 norm = normalize(normal);
vec3 viewDir = normalize(viewPos - FragPos);
// ///平行光
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// // 点光源
for(int i = 0; i < NR_POINT_LIGHTS; I++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
//// 聚光
result +=CalcSpotLight(spotLight, norm, FragPos, viewDir);;
gl_FragColor = vec4(result, 1.0);
}
实际的代码对每一种实现都可能不同,但大体的结构都是差不多的。我们定义了几个函数,用来计算每个光源的影响,并将最终的结果颜色加到输出颜色向量上。例如,如果两个光源都很靠近一个片段,那么它们所结合的贡献将会形成一个比单个光源照亮时更加明亮的片段。
定向光
我么需要在片段着色器中定义一个函数来计算定向光对相应片段的贡献:它接受一些参数并计算一个定向光照颜色。
首先,我们需要定义一个定向光源最少所需要的变量。我们可以将这些变量储存在一个叫做DirLight的结构体中,并将它定义为一个uniform。
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
接下来我们可以将dirLight传入一个有着一下原型的函数。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
我们在该函数中实现平行光
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 diffuseT =vec3(texture2D(material.diffuse,v_texture));
vec3 specularT =vec3(texture2D(material.specular,v_texture));
///环境光
vec3 ambient = light.ambient * diffuseT;
// 漫反射
vec3 lightDir = normalize(-light.direction);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * diffuseT);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess*128.0);
vec3 specular = light.specular * (spec * specularT);
return( ambient + diffuse + specular);
}
传值核心代码
//绑定shader
weakSelf.bindObject->uniforms[MLS_UniformLocationDirLightDirection] = glGetUniformLocation(self.shader.program, "dirLight.direction");
weakSelf.bindObject->uniforms[MLS_UniformLocationDirLightAmbient] = glGetUniformLocation(self.shader.program, "dirLight.ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationDirLightDiffuse] = glGetUniformLocation(self.shader.program, "dirLight.diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationDirLightSpecular] = glGetUniformLocation(self.shader.program, "dirLight.specular");
//传值shader
MLS_DirLight dirlight ;
dirlight.direction = GLKVector3Make(-0.2f, -1.0f, -0.3f);
dirlight.ambient =GLKVector3Make(0.3f, 0.24f, 0.14f);
dirlight.diffuse =GLKVector3Make( 0.7f, 0.42f, 0.26f);
dirlight.specular =GLKVector3Make(0.5f, 0.5f, 0.5f);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationDirLightDirection], 1, &dirlight.direction);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationDirLightAmbient], 1, &dirlight.ambient);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationDirLightSpecular], 1, &dirlight.specular);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationDirLightDiffuse], 1, &dirlight.diffuse);
只实现该光源的屏幕截图
点光源
和定向光一样,我们也希望定义一个用于计算点光源对相应片段贡献,以及衰减,的函数。同样,我们定义一个包含了点光源所需所有变量的结构体:
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
你可以看到,我们在GLSL中使用了预处理指令来定义了我们场景中点光源的数量。接着我们使用了这个NR_POINT_LIGHTS常量来创建了一个PointLight结构体的数组。GLSL中的数组和C数组一样,可以使用一对方括号来创建。现在我们有四个待填充数据的PointLight结构体。
我们也可以定义一个大的结构体(而不是为每种类型的光源定义不同的结构体),包含所有不同种光照类型所需的变量,并将这个结构体用到所有的函数中,只需要忽略用不到的变量就行了。然而,我个人觉得当前的方法会更直观一点,不仅能够节省一些代码,而且由于不是所有光照类型都需要所有的变量,这样也能节省一些内存。
点光源函数的原型如下:
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
该函数实现
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 diffuseT =vec3(texture2D(material.diffuse,v_texture));
vec3 specularT =vec3(texture2D(material.specular,v_texture));
vec3 lightDir = normalize(light.position - FragPos);
///环境光
vec3 ambient = light.ambient * diffuseT;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * diffuseT);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess*128.0);
vec3 specular = light.specular * (spec * specularT);
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
这些功能抽象到这样一个函数中的优点是,我们能够不用重复的代码而很容易地计算多个点光源的光照了。在main函数中,我们只需要创建一个循环,遍历整个点光源数组,对每个点光源调用CalcPointLight就可以了。
核心传值代码
// 绑定
///点光 0
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroPos] = glGetUniformLocation(self.shader.program, "pointLights[0].position");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroAmbient] = glGetUniformLocation(self.shader.program, "pointLights[0].ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroDiffuse] = glGetUniformLocation(self.shader.program, "pointLights[0].diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroSpecular] = glGetUniformLocation(self.shader.program, "pointLights[0].specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroConstant] = glGetUniformLocation(self.shader.program, "pointLights[0].constant");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroLinear] = glGetUniformLocation(self.shader.program, "pointLights[0].linear");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroQuadratic] = glGetUniformLocation(self.shader.program, "pointLights[0].quadratic");
///点光 1
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOnePos] = glGetUniformLocation(self.shader.program, "pointLights[1].position");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOneAmbient] = glGetUniformLocation(self.shader.program, "pointLights[1].ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOneDiffuse] = glGetUniformLocation(self.shader.program, "pointLights[1].diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOneSpecular] = glGetUniformLocation(self.shader.program, "pointLights[1].specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOneConstant] = glGetUniformLocation(self.shader.program, "pointLights[1].constant");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightZeroLinear] = glGetUniformLocation(self.shader.program, "pointLights[1].linear");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightOneQuadratic] = glGetUniformLocation(self.shader.program, "pointLights[1].quadratic");
///点光 2
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoPos] = glGetUniformLocation(self.shader.program, "pointLights[2].position");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoAmbient] = glGetUniformLocation(self.shader.program, "pointLights[2].ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoDiffuse] = glGetUniformLocation(self.shader.program, "pointLights[2].diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoSpecular] = glGetUniformLocation(self.shader.program, "pointLights[2].specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoConstant] = glGetUniformLocation(self.shader.program, "pointLights[2].constant");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoLinear] = glGetUniformLocation(self.shader.program, "pointLights[2].linear");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightTwoQuadratic] = glGetUniformLocation(self.shader.program, "pointLights[2].quadratic");
///点光 3
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreePos] = glGetUniformLocation(self.shader.program, "pointLights[3].position");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeAmbient] = glGetUniformLocation(self.shader.program, "pointLights[3].ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeDiffuse] = glGetUniformLocation(self.shader.program, "pointLights[3].diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeSpecular] = glGetUniformLocation(self.shader.program, "pointLights[3].specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeConstant] = glGetUniformLocation(self.shader.program, "pointLights[3].constant");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeLinear] = glGetUniformLocation(self.shader.program, "pointLights[3].linear");
weakSelf.bindObject->uniforms[MLS_UniformLocationPointLightThreeQuadratic] = glGetUniformLocation(self.shader.program, "pointLights[3].quadratic");
//传值
GLKVector3 pointLightPositions[] = {
GLKVector3Make( 0.7f, 0.2f, 2.0f),
GLKVector3Make( 2.3f, -3.3f, -4.0f),
GLKVector3Make(-4.0f, 2.0f, -12.0f),
GLKVector3Make( 0.0f, 0.0f, -3.0f)
};
MLS_PointLight pointLight[4];
for (int i=0; i<4; i++) {
pointLight[i].position= pointLightPositions[I];
pointLight[i].ambient = GLKVector3Make(0.05f, 0.05f, 0.05f);;
pointLight[i].diffuse = GLKVector3Make(0.8f, 0.8f, 0.8f);
pointLight[i].specular = GLKVector3Make( 1.0f, 1.0f, 1.0f);
pointLight[i].constant = 1.0;
pointLight[i].linear = 0.09;
pointLight[i].quadratic = 0.032;
}
for (int i=0; i<4; i++) {
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroPos+i*7], 1, &pointLight[i].position);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroAmbient+i*7], 1, &pointLight[i].ambient);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroDiffuse+i*7], 1, &pointLight[i].diffuse);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroSpecular+i*7], 1, &pointLight[i].specular);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroConstant+i*7], 1, &pointLight[i].constant);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroLinear+i*7], 1, &pointLight[i].linear);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationPointLightZeroQuadratic+i*7], 1, &pointLight[i].quadratic);
}
只有点光源四个灯的效果
聚光
和定向光一样,我们定义一个包含了点光源所需所有变量的结构体:
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform SpotLight spotLight;
聚光 函数的原型如下:
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
该函数实现如下
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 diffuseT =vec3(texture2D(material.diffuse,v_texture));
vec3 specularT =vec3(texture2D(material.specular,v_texture));
//环境光
vec3 ambient = light.ambient * diffuseT;
vec3 lightDir = normalize(light.position - FragPos);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * diffuseT);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * specularT);
// spotlight (soft edges)
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = (light.cutOff - light.outerCutOff);
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
diffuse *= intensity;
specular *= intensity;
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// ambient *= attenuation; // remove attenuation from ambient, as otherwise at large distances the light would be darker inside than outside the spotlight due the ambient term in the else branche
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
核心传值代码
///绑定
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightPos] = glGetUniformLocation(self.shader.program, "spotLight.position");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightDirection] = glGetUniformLocation(self.shader.program, "spotLight.direction");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightAmbient] = glGetUniformLocation(self.shader.program, "spotLight.ambient");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightDiffuse] = glGetUniformLocation(self.shader.program, "spotLight.diffuse");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightSpecular] = glGetUniformLocation(self.shader.program, "spotLight.specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightConstant] = glGetUniformLocation(self.shader.program, "spotLight.constant");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightLinear] = glGetUniformLocation(self.shader.program, "spotLight.linear");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightQuadratic] = glGetUniformLocation(self.shader.program, "spotLight.specular");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightcutOff] = glGetUniformLocation(self.shader.program, "spotLight.cutOff");
weakSelf.bindObject->uniforms[MLS_UniformLocationSpotLightouterCutOff] = glGetUniformLocation(self.shader.program, "spotLight.outerCutOff");
///传值
MLS_SpotLight spotLight;
spotLight.position = GLKVector3Make(0.0f, 0.0f, 3.0f);
spotLight.ambient =GLKVector3Make( 0.0f, 0.0f, 0.0f);
spotLight.diffuse =GLKVector3Make(1.0f, 1.0f, 1.0f);
spotLight.specular =GLKVector3Make(1.0,1.0,1.0);
spotLight.constant = 1.0;
spotLight.linear = 0.09f ;
spotLight.quadratic = 0.032f;
// 0.0f, 0.0f, 3.0f
const float YAW = -90.0f;
const float PITCH = 0.0f;
GLKVector3 front;
front.x = cos(MLS_radians(YAW)) * cos(MLS_radians(PITCH));
front.y = sin(MLS_radians(PITCH));
front.z = sin(MLS_radians(YAW)) * cos(MLS_radians(PITCH));
front = GLKVector3Normalize(front);
spotLight.direction = front;
spotLight.cutOff = cos(MLS_radians(12.5));
spotLight.outerCutOff =cos(MLS_radians(15.0));
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightPos], 1, &spotLight.position);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightDirection], 1, &spotLight.direction);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightAmbient], 1, &spotLight.ambient);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightSpecular], 1, &spotLight.specular);
glUniform3fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightDiffuse], 1, &spotLight.diffuse);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightConstant], 1, &spotLight.constant);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightLinear], 1, &spotLight.linear);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightQuadratic], 1, &spotLight.quadratic);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightcutOff], 1, &spotLight.cutOff);
glUniform1fv(self.bindObject->uniforms[MLS_UniformLocationSpotLightouterCutOff], 1, &spotLight.outerCutOff);
只实现聚光灯效果图片
光源合并
每个光源类型都将它们的贡献加到了最终的输出颜色上,直到所有的光源都处理完了。最终的颜色包含了场景中所有光源的颜色影响所合并的结果
void main()
{
vec3 norm = normalize(normal);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 result = vec3(0.0);
///平行光
result += CalcDirLight(dirLight, norm, viewDir);
// 点光源
for(int i = 0; i < NR_POINT_LIGHTS; I++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
// 聚光
result +=CalcSpotLight(spotLight, norm, FragPos, viewDir);;
//
gl_FragColor = vec4(result, 1.0);
}
最终效果
旋转效果如下
发现点光源是卡顿的主要原因.暂时不解决