================================ 前序===============================
AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合:
系列文章:
4、LearnOpenGL之光照
============================== 显示效果 ===============================
=============================================================
一、颜色及光照场景:
在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。换句话说,那些不能被物体所吸收(Absorb)的颜色(被拒绝的颜色)就是我们能够感知到的物体的颜色。
白色的阳光实际上是所有可见颜色的集合,物体吸收了其中的大部分颜色。它仅反射了代表物体颜色的部分,被反射颜色的组合就是我们所感知到的颜色
1、物体反射的颜色:
当我们在OpenGL中创建一个光源时,我们希望给光源一个颜色。当我们把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色(也就是我们所感知到的颜色)。在图形学中计算出它的反射颜色。我们将这两个颜色向量作分量相乘,结果就是最终的颜色向量了:
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);
我们可以看到玩具的颜色吸收了白色光源中很大一部分的颜色,但它根据自身的颜色值对红、绿、蓝三个分量都做出了一定的反射。这也表现了现实中颜色的工作原理。由此,我们可以定义物体的颜色为物体从一个光源反射各个颜色分量的大小。
2、创建光照场景:
二、基础光照:
现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为风氏光照模型(Phong Lighting Model)。风氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子:
- 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
- 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是风氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
- 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
1、环境光照:
光通常都不是来自于同一个光源,而是来自于我们周围分散的很多光源,即使它们可能并不是那么显而易见。光的一个属性是,它可以向很多方向发散并反弹,从而能够到达不是非常直接临近的点。所以,光能够在其它的表面上反射,对一个物体产生间接的影响。考虑到这种情况的算法叫做全局照明(Global Illumination)算法,但是这种算法既开销高昂又极其复杂。
把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色。
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
2、漫反射光照:
漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。为了能够更好的理解漫反射光照,请看下图:
了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一个向量(这里以黄色箭头表示)
计算漫反射光照的条件:
- 法向量:一个垂直于顶点表面的向量。
- 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。
计算漫反射光照:
- 在世界空间中进行所有的光照计算:
#version 320 es
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
//顶点位置属性乘以模型矩阵(不是观察和投影矩阵)来把它变换到世界空间坐标
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
- 光照方向,光的方向向量是光源位置向量与片段位置向量之间的向量差
- 环境光分量和漫反射分量相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色。
#version 320 es
precision mediump float;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
//光照位置
uniform vec3 lightPos;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
//环境光照
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
//向量进行标准化
vec3 norm = normalize(Normal);
//光照方向 光的方向向量是光源位置向量与片段位置向量之间的向量差
vec3 lightDir = normalize(lightPos - FragPos);
//对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。
//结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小
//如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数。
//为此,我们使用max函数返回两个参数之间较大的参数
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
//环境光分量和漫反射分量相加,之后乘以物体的颜色,
vec3 result = (ambient + diffuse) * objectColor;
//最后来获取片段的输出颜色
FragColor = vec4(result, 1.0);
}
3、镜面光照:
和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。
观察向量是我们计算镜面光照时需要的一个额外变量,我们可以使用观察者的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度,用它乘以光源的颜色,并将它与环境光照和漫反射光照部分加和。
- 法线矩阵(Normal Matrix):它使用了一些线性代数的操作来移除对法向量错误缩放的影响。
- 观察者的世界空间坐标:
要得到观察者的世界空间坐标,我们直接使用摄像机的位置向量即可(它当然就是那个观察者)。那么让我们把另一个uniform添加到片段着色器中,并把摄像机的位置向量传给着色器
//uniform vec3 viewPos; //uniform添加到片段着色器中
//把摄像机的位置向量传给着色器
lightingShader.setVec3("viewPos", camera.Position);
- 计算高光强度:定义一个镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色;
- 计算视线方向向量,和对应的沿着法线轴的反射向量;
- 计算镜面分量;
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
- 加到环境光分量和漫反射分量里,再用结果乘以物体的颜色。
#version 320 es
precision mediump float;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
//光照位置
uniform vec3 lightPos;
//观察者位置
uniform vec3 viewPos;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
//环境光照
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
//向量进行标准化
vec3 norm = normalize(Normal);
//光照方向 光的方向向量是光源位置向量与片段位置向量之间的向量差
vec3 lightDir = normalize(lightPos - FragPos);
//对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。
//结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小
//如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数。
//为此,我们使用max函数返回两个参数之间较大的参数
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
//镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响
float specularStrength = 0.5;
//计算视线方向向量,和对应的沿着法线轴的反射向量
vec3 viewDir = normalize(viewPos - FragPos);
//对lightDir向量进行了取反。reflect函数要求第一个向量是从光源指向片段位置的向量,但是
//lightDir当前正好相反,
//是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。
vec3 reflectDir = reflect(-lightDir, norm);
//计算镜面分量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = specularStrength * spec * lightColor;
//加到环境光分量和漫反射分量里,再用结果乘以物体的颜色
vec3 result = (ambient + diffuse + specular) * objectColor;
//最后来获取片段的输出颜色
FragColor = vec4(result, 1.0);
}
三、材质:
在现实世界里,每个物体会对光产生不同的反应。想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。
定义了一个物体和光的颜色,并结合环境光与镜面强度分量,来决定物体的视觉输出。当描述一个表面时,我们可以分别为三个光照分量定义一个材质颜色(Material Color):环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。现在,我们再添加一个反光度(Shininess)分量,结合上述的三个颜色,我们就有了全部所需的材质属性了:
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
在片段着色器中,我们创建一个结构体(Struct)来储存物体的材质属性。我们也可以把它们储存为独立的uniform值,但是作为一个结构体来储存会更有条理一些。我们首先定义结构体的布局(Layout),然后简单地以刚创建的结构体作为类型声明一个uniform变量。
1、风氏光照模型:
风氏光照模型的每个分量都定义一个颜色向量。ambient材质向量定义了在环境光照下这个表面反射的是什么颜色,通常与表面的颜色相同。diffuse材质向量定义了在漫反射光照下表面的颜色。漫反射颜色(和环境光照一样)也被设置为我们期望的物体颜色。specular材质向量设置的是表面上镜面高光的颜色(或者甚至可能反映一个特定表面的颜色)。最后,shininess影响镜面高光的散射/半径。
在devernay.free.fr中的一个表格展示了一系列材质属性,它们模拟了现实世界中的真实材质。
2、设置材质:
在片段着色器中创建了一个材质结构体的uniform, 可以从uniform变量material中访问它们。
GLSL中一个结构体在设置uniform时并无任何区别,结构体只是充当uniform变量们的一个命名空间。所以如果想填充这个结构体的话,我们必须设置每个单独的uniform,但要以结构体名为前缀。
3、光的属性:
物体过亮的原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都全力反射。光源对环境光、漫反射和镜面光分量也分别具有不同的强度。
4、材质及光属性的使用:
在片段着色器中定义一个struct Material和Light的结构体,把两者的环境光照(ambient)、反射光照(diffuse)、镜面光照(specular)向量相乘结合输出:
#version 320 es
precision mediump float;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
//uniform vec3 lightPos;
//uniform vec3 lightColor;
struct Light {
//光照位置
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
//观察者位置
uniform vec3 viewPos;
uniform Light light;
void main()
{
// 环境光
vec3 ambient = light.ambient * material.ambient;
// 漫反射
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse);
// 镜面光
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
接着在renderFrame中设置uniform的值:
void OpenglesMaterial::renderFrame() {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!
//开启深度测试
glEnable(GL_DEPTH_TEST);
// be sure to activate shader when setting uniforms/drawing objects
lightColorShader->use();
// lightColorShader->setVec3("lightColor", 1.0f, 1.0f, 1.0f);
double timeValue = clock() * 8 / CLOCKS_PER_SEC;
vec3 lightColor;
lightColor.x = sin(timeValue * 2.0f);
lightColor.y = sin(timeValue * 0.7f);
lightColor.z = sin(timeValue * 1.3f);
vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence
vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
lightColorShader->setVec3("light.ambient", ambientColor);
lightColorShader->setVec3("light.diffuse", diffuseColor);
lightColorShader->setVec3("light.specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setVec3("light.position", OpenglesMaterialLightLightPos);
lightColorShader->setVec3("viewPos", mCamera.Position);
lightColorShader->setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightColorShader->setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightColorShader->setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightColorShader->setFloat("material.shininess", 32.0f);
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(mCamera.Zoom),
(float) screenW / (float) screenH, 0.1f, 100.0f);
vec3 cameraMove(0.0f, 0.0f, 5.0f);
mCamera.Position = cameraMove;
glm::mat4 view = mCamera.GetViewMatrix();
lightColorShader->setMat4("projection", projection);
lightColorShader->setMat4("view", view);
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(45.0f), OpenglesMaterialLightLightPos);
lightColorShader->setMat4("model", model);
// render the cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// also draw the lamp object
lightCubeShader->use();
lightCubeShader->setMat4("projection", projection);
lightCubeShader->setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, OpenglesMaterialLightLightPos);
model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
lightCubeShader->setMat4("model", model);
glBindVertexArray(lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
checkGlError("glDrawArrays");
}
四、 光照贴图:
将整个物体的材质定义为一个整体,但现实世界中的物体通常并不只包含有一种材质,而是由多种材质所组成。引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。
1、漫反射贴图:
使用一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。在光照场景中,它通常叫做一个漫反射贴图(Diffuse Map)。
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
//sampler2D是所谓的不透明类型(Opaque Type),也就是说我们不能将它实例化,
//只能通过uniform来定义它
sampler2D diffuse;
vec3 specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient 材质和纹理的环境光照的结合
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
//材质和纹理的漫反射光照的结合
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
2 、镜面光贴图:
镜面高光的强度可以通过图像每个像素的亮度来获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示,比如说黑色代表颜色向量vec3(0.0)
,灰色代表颜色向量vec3(0.5)
。在片段着色器中,我们接下来会取样对应的颜色值并将它乘以光源的镜面强度。一个像素越「白」,乘积就会越大,物体的镜面光分量就会越亮。
3、采样镜面光贴图:
镜面光贴图和其它的纹理非常类似,所以代码也和漫反射贴图的代码很类似。记得要保证正确地加载图像并生成一个纹理对象。由于我们正在同一个片段着色器中使用另一个纹理采样器,我们必须要对镜面光贴图使用一个不同的纹理单元。
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient 材质和纹理的环境光照的结合
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
//材质和纹理的漫反射光照的结合
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
//材质和纹理的镜面光的结合
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
五、透光物:
但现实世界中,我们有很多种类的光照,每种的表现都不同。将光投射(Cast)到物体的光源叫做投光物(Light Caster)。
1、平行光:
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。
因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
//vec3 position; //使用定向光就不再需要了
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient 材质和纹理的环境光照的结合
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
//光照计算需求一个从片段至光源的光线方向
vec3 lightDir = normalize(-light.direction);
float diff = max(dot(norm, lightDir), 0.0);
//材质和纹理的漫反射光照的结合
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
//材质和纹理的镜面光的结合
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
注意我们首先对light.direction向量取反。我们目前使用的光照计算需求一个从片段至光源的光线方向,但人们更习惯定义定向光为一个从光源出发的全局方向。所以我们需要对全局光照方向向量取反来改变它的方向,它现在是一个指向光源的方向向量了。而且,记得对向量进行标准化,假设输入向量为一个单位向量是很不明智的。
2、点光源:
光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。
衰减:
随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)。随距离减少光强度的一种方式是使用一个线性方程。这样的方程能够随着距离的增长线性地减少光的强度,从而让远处的物体更暗。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。
这里dd代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项KcKc、一次项KlKl和二次项KqKq。
- 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
- 一次项会与距离值相乘,以线性的方式减少强度。
- 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。
选择正确的值:
正确地设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等。在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,由Ogre3d提供:
实现衰减:
为了实现衰减,在片段着色器中我们还需要三个额外的值:也就是公式中的常数项、一次项和二次项。它们最好储存在之前定义的Light结构体中。
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
计算出衰减的值:
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear
* distance + light.quadratic * (distance * distance));
设置光属性时添加点光源的衰减的影响值:
// light properties
lightColorShader->setVec3("light.ambient", 0.5f, 0.5f, 0.5f);
lightColorShader->setVec3("light.diffuse", 1.0f, 1.0f, 1.0f);
lightColorShader->setVec3("light.specular", 0.5f, 0.5f, 0.5f);
lightColorShader->setFloat("light.constant", 1.0f);
lightColorShader->setFloat("light.linear", 0.09f);
lightColorShader->setFloat("light.quadratic", 0.032f);
六、 聚光:
聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:
LightDir
:从片段指向光源的向量。- SpotDir:聚光所指向的方向。
Phi
ϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。Theta
θθ :LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θθ值应该比ϕϕ值小。
1、手电筒:
手电筒(Flashlight)是一个位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。基本上说,手电筒就是普通的聚光,但它的位置和方向会随着玩家的位置和朝向不断更新。
在片段着色器中我们需要的值有聚光的位置向量(来计算光的方向向量)、聚光的方向向量和一个切光角。我们可以将它们储存在Light结构体中:
struct Light {
vec3 position;
vec3 direction; //聚光的位置向量
float cutOff; //一个切光角
float outerCutOff; //
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
我们并没有给切光角设置一个角度值,反而是用角度值计算了一个余弦值,将余弦结果传递到片段着色器中。这样做的原因是在片段着色器中,我们会计算LightDir
和SpotDir
向量的点积,这个点积返回的将是一个余弦值而不是角度值,所以我们不能直接使用角度值和余弦值进行比较。为了获取角度值我们需要计算点积结果的反余弦,这是一个开销很大的计算。所以为了节约一点性能开销,我们将会计算切光角对应的余弦值,并将它的结果传入片段着色器中。由于这两个角度现在都由余弦角来表示了,我们可以直接对它们进行比较而不用进行任何开销高昂的计算:
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 执行光照计算
}
else // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗
color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
计算θ值,并将它和切光角ϕ对比,来决定是否在聚光的内部:
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 direction; //聚光的位置向量
float cutOff; //一个切光角
float outerCutOff; //
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
vec3 lightDir = normalize(light.position - FragPos);
// 检查聚光灯锥体内是否有照明
float theta = dot(lightDir, normalize(-light.direction));
//我们使用的角度是余弦而不是度数,因此使用了“>”。
if(theta > light.cutOff)
{
// ambient
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
// attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance
+ light.quadratic * (distance * distance));
//消除环境光的衰减,否则在很远的地方,由于else分支中的环境项,聚光灯内部的光线会比外部的光线暗
// ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
else
{
// 否则,使用环境光,这样聚光灯外的场景就不会完全黑暗。
FragColor = vec4(light.ambient * texture(material.diffuse, TexCoords).rgb, 1.0);
}
}
2、平滑/软化边缘:
为了创建一种看起来边缘平滑的聚光,我们需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。我们可以将内圆锥设置为上一部分中的那个圆锥,但我们也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
为了创建一个外圆锥,我们只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。
这里ϵϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ)。最终的II值就是在当前片段聚光的强度。
很难去表现这个公式是怎么工作的,所以我们用一些实例值来看看:
一个在聚光外是负的,在内圆锥内大于1.0的,在边缘处于两者之间的强度值了。如果我们正确地约束(Clamp)这个值,在片段着色器中就不再需要if-else
了,我们能够使用计算出来的强度值直接乘以光照分量:
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 direction; //聚光的位置向量
float cutOff; //一个切光角
float outerCutOff; //
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
// 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;
diffuse *= attenuation;
specular *= attenuation;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
七、 多光源:
前面已经学习了包括风氏着色(Phong Shading)、材质(Material)、光照贴图(Lighting Map)以及不同种类的投光物(Light Caster),将模拟一个类似太阳的定向光(Directional Light)光源,四个分散在场景中的点光源(Point Light),以及一个手电筒(Flashlight)。
为了在场景中使用多个光源,我们希望将光照计算封装到GLSL函数中。这样做的原因是,每一种光源都需要一种不同的计算方法,而一旦我们想对多个光源进行光照计算时,代码很快就会变得非常复杂。如果我们只在main函数中进行所有的这些计算,代码很快就会变得难以理解。
当我们在场景中使用多个光源时,通常使用以下方法:我们需要有一个单独的颜色向量代表片段的输出颜色。对于每一个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上。所以场景中的每个光源都会计算它们各自对片段的影响,并结合为一个最终的输出颜色。
1、定向光照函数:
// 计算使用平行光时的颜色。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
2、点光源函数:
// 计算使用点光源时的颜色。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
3、聚光光源函数:
// 计算使用聚光灯时的颜色。
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}
4、多光源在片段程序的合成:
#version 320 es
precision mediump float;
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
#define NR_POINT_LIGHTS 4
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform DirLight dirLight;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;
uniform Material material;
// function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
void main()
{
// properties
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// phase 1: 定向光照
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// phase 2: 点光源
for(int i = 0; i < NR_POINT_LIGHTS; i++){
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
}
// phase 3: 边缘平滑的聚光
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
// 计算使用平行光时的颜色。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
// 计算使用点光源时的颜色。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
// 计算使用聚光灯时的颜色。
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}
5、多光源的渲染:
#include "OpenglesMultiLight.h"
#include <iostream>
bool OpenglesMultiLight::setupGraphics(int w, int h) {
screenW = w;
screenH = h;
LOGI("setupGraphics(%d, %d)", w, h);
GLuint lightingProgram = lightColorShader->createProgram();
if (!lightingProgram) {
LOGE("Could not create shaderId.");
return false;
}
GLuint lightingPositionHandle = glGetAttribLocation(lightingProgram, "gl_Position");
checkGlError("lightingProgram glGetAttribLocation");
GLuint lightCubeProgram = lightCubeShader->createProgram();
if (!lightCubeProgram) {
LOGE("Could not create shaderId.");
return false;
}
GLuint lightCubePositionHandle = glGetAttribLocation(lightCubeProgram, "gl_Position");
checkGlError("lightCubeProgram glGetAttribLocation");
glViewport(0, 0, w, h);
checkGlError("glViewport");
LOGI("glViewport successed!");
//清屏
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
checkGlError("glClear");
//绑定物体立方体数据
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(MultiLightVertices), MultiLightVertices, GL_STATIC_DRAW);
glBindVertexArray(cubeVAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
(void *) (3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
(void *) (6 * sizeof(float)));
glEnableVertexAttribArray(2);
//绑定灯光立方体数据
glGenVertexArrays(1, &lightCubeVAO);
glBindVertexArray(lightCubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);
// load and create a texture
LOGI("load and create a texture!");
GLenum format;
if (nrChannels1 == 1) {
format = GL_RED;
} else if (nrChannels1 == 3) {
format = GL_RGB;
} else if (nrChannels1 == 4) {
format = GL_RGBA;
}
// LOGI("texture1 format==%d", format);
if (data1) {
diffuseMapTexture = loadTexture(data1, width1, height1, format);
}
if (nrChannels2 == 1) {
format = GL_RED;
} else if (nrChannels2 == 3) {
format = GL_RGB;
} else if (nrChannels2 == 4) {
format = GL_RGBA;
}
if (data2) {
specularMapTexture = loadTexture(data2, width2, height2, format);
}
lightColorShader->use();
lightColorShader->setInt("material.diffuse", 0);
lightColorShader->setInt("material.specular", 1);
return true;
}
void OpenglesMultiLight::renderFrame() {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // also clear the depth buffer now!
//开启深度测试
glEnable(GL_DEPTH_TEST);
// be sure to activate shader when setting uniforms/drawing objects
lightColorShader->use();
// directional light
lightColorShader->setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f);
lightColorShader->setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f);
lightColorShader->setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f);
lightColorShader->setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f);
// point light 1
lightColorShader->setVec3("pointLights[0].position", pointLightPositions[0]);
lightColorShader->setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
lightColorShader->setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
lightColorShader->setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setFloat("pointLights[0].constant", 1.0f);
lightColorShader->setFloat("pointLights[0].linear", 0.09f);
lightColorShader->setFloat("pointLights[0].quadratic", 0.032f);
// point light 2
lightColorShader->setVec3("pointLights[1].position", pointLightPositions[1]);
lightColorShader->setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f);
lightColorShader->setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f);
lightColorShader->setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setFloat("pointLights[1].constant", 1.0f);
lightColorShader->setFloat("pointLights[1].linear", 0.09f);
lightColorShader->setFloat("pointLights[1].quadratic", 0.032f);
// point light 3
lightColorShader->setVec3("pointLights[2].position", pointLightPositions[2]);
lightColorShader->setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f);
lightColorShader->setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f);
lightColorShader->setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setFloat("pointLights[2].constant", 1.0f);
lightColorShader->setFloat("pointLights[2].linear", 0.09f);
lightColorShader->setFloat("pointLights[2].quadratic", 0.032f);
// point light 4
lightColorShader->setVec3("pointLights[3].position", pointLightPositions[3]);
lightColorShader->setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f);
lightColorShader->setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f);
lightColorShader->setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setFloat("pointLights[3].constant", 1.0f);
lightColorShader->setFloat("pointLights[3].linear", 0.09f);
lightColorShader->setFloat("pointLights[3].quadratic", 0.032f);
// spotLight
lightColorShader->setVec3("spotLight.position", mCamera.Position);
lightColorShader->setVec3("spotLight.direction", mCamera.Front);
lightColorShader->setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f);
lightColorShader->setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f);
lightColorShader->setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f);
lightColorShader->setFloat("spotLight.constant", 1.0f);
lightColorShader->setFloat("spotLight.linear", 0.09f);
lightColorShader->setFloat("spotLight.quadratic", 0.032f);
lightColorShader->setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f)));
lightColorShader->setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(mCamera.Zoom),
(float) screenW / (float) screenH, 0.1f, 100.0f);
vec3 cameraMove(0.0f, 0.0f, 10.0f);
mCamera.Position = cameraMove;
glm::mat4 view = mCamera.GetViewMatrix();
lightColorShader->setMat4("projection", projection);
lightColorShader->setMat4("view", view);
// world transformation
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(45.0f), MultiLightLightPos);
lightColorShader->setMat4("model", model);
// bind diffuse map
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMapTexture);
// bind specular map
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMapTexture);
/* // render the cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);*/
// render containers
glBindVertexArray(cubeVAO);
for (unsigned int i = 0; i < 10; i++) {
// calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, MultiLightCubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightColorShader->setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// also draw the lamp object(s)
lightCubeShader->use();
lightCubeShader->setMat4("projection", projection);
lightCubeShader->setMat4("view", view);
// we now draw as many light bulbs as we have point lights.
glBindVertexArray(lightCubeVAO);
for (unsigned int i = 0; i < 4; i++)
{
model = glm::mat4(1.0f);
model = glm::translate(model, pointLightPositions[i]);
model = glm::scale(model, glm::vec3(0.2f)); // Make it a smaller cube
lightCubeShader->setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
checkGlError("glDrawArrays");
}
bool OpenglesMultiLight::setSharderPath(const char *vertexPath, const char *fragmentPath) {
lightColorShader->getSharderPath(vertexPath, fragmentPath);
return 0;
}
bool OpenglesMultiLight::setColorSharderPath(const char *vertexPath, const char *fragmentPath) {
lightCubeShader->getSharderPath(vertexPath, fragmentPath);
return false;
}
void OpenglesMultiLight::setPicPath(const char *pic1, const char *pic2) {
LOGI("setPicPath pic1==%s", pic1);
LOGI("setPicPath pic2==%s", pic2);
data1 = stbi_load(pic1, &width1, &height1, &nrChannels1, 0);
data2 = stbi_load(pic2, &width2, &height2, &nrChannels2, 0);
}
void OpenglesMultiLight::setMoveXY(float dx, float dy, int actionMode) {
LOGI("setMoveXY dx:%f,dy:%f,actionMode:%d", dy, dy, actionMode);
float xoffset = dx - lastX;
float yoffset = lastY - dy; // reversed since y-coordinates go from bottom to top
lastX = dx;
lastY = dy;
mActionMode = actionMode;
mCamera.ProcessXYMovement(xoffset, yoffset);
}
void OpenglesMultiLight::setOnScale(float scaleFactor, float focusX, float focusY, int actionMode) {
// LOGI("setOnScale scaleFactor:%f,focusX:%f,focusY:%f,actionMode:%d", scaleFactor, focusX, focusY,
// actionMode);
// LOGI("setOnScale scaleFactor:%f", scaleFactor);
float scale;
if (actionMode == 1 || actionMode == 3) {
scale = 45.0f;
} else {
if (scaleFactor > 1) {
scale = (scaleFactor - 1) * 1000 + 45;
} else {
scale = 50 - (1 - scaleFactor) * 1000;
}
}
LOGI("setOnScale scale:%f", scale);
mCamera.ProcessScroll(scale);
}
OpenglesMultiLight::OpenglesMultiLight() {
lightColorShader = new OpenGLShader();
lightCubeShader = new OpenGLShader();
}
OpenglesMultiLight::~OpenglesMultiLight() {
diffuseMapTexture = 0;
specularMapTexture = 0;
//析构函数中释放资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
lightCubeShader = nullptr;
lightColorShader = nullptr;
if (data1) {
stbi_image_free(data1);
data1 = nullptr;
}
if (data2) {
stbi_image_free(data2);
data2 = nullptr;
}
colorVertexCode.clear();
colorFragmentCode.clear();
}
void OpenglesMultiLight::printGLString(const char *name, GLenum s) {
const char *v = (const char *) glGetString(s);
LOGI("OpenGL %s = %s\n", name, v);
}
void OpenglesMultiLight::checkGlError(const char *op) {
for (GLint error = glGetError(); error; error = glGetError()) {
LOGI("after %s() glError (0x%x)\n", op, error);
}
}
/**
* 加载纹理
* @param path
* @return
*/
int OpenglesMultiLight::loadTexture(unsigned char *data, int width, int height, GLenum format) {
unsigned int textureID;
glGenTextures(1, &textureID);
// LOGI("loadTexture format =%d", format);
if (data) {
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
} else {
checkGlError("Texture failed to load at path: ");
stbi_image_free(data);
}
return textureID;
}