重新自学学习openGL 之光照投光物

2019-08-08  本文已影响0人  充满活力的早晨

概念

将光投射(Cast)到物体的光源叫做投光物(Light Caster)

分类

定向光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。

定向光非常好的一个例子就是太阳。太阳距离我们并不是无限远,但它已经远到在光照计算中可以把它视为无限远了。所以来自太阳的所有光线将被模拟为平行光线,我们可以在下图看到:


定向光

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。

我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的direction向量而不是通过direction来计算lightDir向量。

struct Light{
    vec3 direction;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
...
void main(){
    vec3 lightDir = normalize(-light.direction);
...
}

注意我们首先对light.direction向量取反。我们目前使用的光照计算需求一个从片段至光源的光线方向,但人们更习惯定义定向光为一个从光源出发的全局方向。所以我们需要对全局光照方向向量取反来改变它的方向,它现在是一个指向光源的方向向量了。而且,记得对向量进行标准化,假设输入向量为一个单位向量是很不明智的。

最终的lightDir向量将和以前一样用在漫反射和镜面光计算中。

最终结果如下图


我们一直将光的位置和位置向量定义为vec3,但一些人会喜欢将所有的向量都定义为vec4。当我们将位置向量定义为一个vec4时,很重要的一点是要将w分量设置为1.0,这样变换和投影才能正确应用。然而,当我们定义一个方向向量为vec4的时候,我们不想让位移有任何的效果(因为它仅仅代表的是方向),所以我们将w分量设置为0.0。
方向向量就会像这样来表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)。这也可以作为一个快速检测光照类型的工具:你可以检测w分量是否等于1.0,来检测它是否是光的位置向量;w分量等于0.0,则它是光的方向向量,这样就能根据这个来调整光照计算了:

if(lightVector.w == 0.0) // 注意浮点数据类型的误差
  // 执行定向光照计算
else if(lightVector.w == 1.0)

// 根据光源的位置做光照计算(与上一节一样)
你知道吗:这正是旧OpenGL(固定函数式)决定光源是定向光还是位置光源(Positional Light Source)的方法,并根据它来调整光照。

点光源

定向光对于照亮整个场景的全局光源是非常棒的,但除了定向光之外我们也需要一些分散在场景中的点光源(Point Light)。点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。


在之前的demo中,我们一直都在使用一个(简化的)点光源。我们在给定位置有一个光源,它会从它的光源位置开始朝着所有方向散射光线。但是,以上demo中定义的光源都是不会衰减的光源。在大部分的3D模拟中,我们都希望模拟的光源仅照亮光源附近的区域而不是整个场景。

如果在场景中有十个箱子,我们使用不会衰减的光源.那么不管箱子距离光源是否很远,都是会被照亮的. 如果想让箱子被照亮的强度随着光源的远近进行变化.我们需要在shader中重新定义这样的衰减公式才可以.

衰减

随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)。
随距离减少光强度的一种方式是使用一个线性方程。这样的方程能够随着距离的增长线性地减少光的强度,从而让远处的物体更暗。然而,这样的线性方程通常会看起来比较假。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。

幸运的是一些聪明的人已经帮我们解决了这个问题。下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:


image.png

在这里d代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项Kc、一次项Kl和二次项Kq

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:


image

你可以看到光在近距离的时候有着最高的强度,但随着距离增长,它的强度明显减弱,并缓慢地在距离大约100的时候强度接近0。这正是我们想要的。

公式的具体使用

但是,该对这三个项设置什么值呢?正确地设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等。在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,它们由Ogre3D的Wiki所提供:

距离 常数项Kc 一次项Kl 二次项Kq
7 1.0 0.7 1.8
13 1.0 0.35 0.44
20 1.0 0.22 0.20
32 1.0 0.14 0.07
50 1.0 0.09 0.032
65 1.0 0.07 0.017
100 1.0 0.045 0.0075
160 1.0 0.027 0.0028
200 1.0 0.022 0.0019
325 1.0 0.014 0.0007
600 1.0 0.007 0.0002
3250 1.0 0.0014 0.000007

你可以看到,常数项Kc在所有的情况下都是1.0。一次项Kl为了覆盖更远的距离通常都很小,二次项Kq甚至更小。尝试对这些值进行实验,看看它们在你的实现中有什么效果。在我们的环境中,32到100的距离对大多数的光源都足够了。

点光源衰减编码

为了实现衰减,在片段着色器中我们还需要三个额外的值:也就是公式中的常数项、一次项和二次项。

struct Light {
    vec3 position;  

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

shader 对点光源的处理

void main(){
   
    
    // 环境光
    vec3 diffuseT =vec3(texture2D(material.diffuse,v_texture));
    vec3 specularT =vec3(texture2D(material.specular,v_texture));

    vec3 ambient = light.ambient * diffuseT;

    // 漫反射
    vec3 norm = normalize(normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * diffuseT);

  // 镜面光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    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;   
     vec3 result = ambient + diffuse + specular;

    gl_FragColor =vec4(result, 1.0);;

}

给shader绑定以及传值

//绑定
  weakSelf.bindObject->uniforms[PL_UniformLocationLightPos] = glGetUniformLocation(self.shader.program, "light.position");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightAmbient] = glGetUniformLocation(self.shader.program, "light.ambient");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightSpecular] = glGetUniformLocation(self.shader.program, "light.specular");
        weakSelf.bindObject->uniforms[PL_UniformLocationLightTDiffuse] = glGetUniformLocation(self.shader.program, "light.diffuse");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightConstant] = glGetUniformLocation(self.shader.program, "light.constant");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightLinear] = glGetUniformLocation(self.shader.program, "light.linear");
          weakSelf.bindObject->uniforms[PL_UniformLocationLightQuadratic] = glGetUniformLocation(self.shader.program, "light.quadratic");

传值
  PL_Light light;
    light.position =  GLKVector3Make(1.2f, 1.0f, 2.0f);
    light.ambient  =GLKVector3Make(0.2,0.2,0.2);
    light.diffuse  =GLKVector3Make(0.5,0.5,0.5);
    light.specular  =GLKVector3Make(1.0,1.0,1.0);
    light.constant = 1.0;
    light.linear = 0.09f;
    light.quadratic = 0.032f;
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightPos], 1, &light.position);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightAmbient], 1, &light.ambient);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightSpecular], 1, &light.specular);
    glUniform3fv(self.bindObject->uniforms[PL_UniformLocationLightTDiffuse], 1, &light.diffuse);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightConstant], 1, &light.constant);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightLinear], 1, &light.linear);
    glUniform1fv(self.bindObject->uniforms[PL_UniformLocationLightQuadratic], 1, &light.quadratic);

结果如下


点光源就是一个能够配置位置和衰减的光源。它是我们光照工具箱中的又一个光照类型。

聚光

聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。

OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:

上面的图需要看的仔细点
LightDir 指的是图中黑线
φ指的是 红线和蓝线之间的夹角,这个角度需要我们设置
lightDir 和 spotDir 相乘 结果是 θ的余弦值,因此这里我们最好传入到片段着色器一个切光角的余弦值进行比较. 余弦值越大说明角度越小
SpotDir 是我们传入的光的方向

LightDir 可以用光源位置和顶点位置求值
vec3 lightDir = normalize(light.position - FragPos);
SpotDir 这个需要我们直接指定聚光灯的方向
Thetaθ 可以通过上面的LightDirSpotDir 值求得
float theta = dot(lightDir, normalize(-light.direction));
Phiϕ 需要我们指定.
因此,我们给片段着色器传入 光的位置放方向以及 Phiϕ 就可以计算出 我们需要的聚光效果了

所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积(还记得它会返回两个单位向量夹角的余弦值吗?),并将它与切光角ϕ
值对比。

手电筒

手电筒(Flashlight)是一个位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。基本上说,手电筒就是普通的聚光,但它的位置和方向会随着玩家的位置和朝向不断更新。

从上面的分析中,聚光需要 光源的位置和方向以及切光角.我们可以重新定义光照为

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

传值

 FL_Light light;
    light.position =  GLKVector3Make(0.0f, 0.0f, 3.0f);
    light.ambient  =GLKVector3Make(0.2,0.2,0.2);
    light.diffuse  =GLKVector3Make(0.5,0.5,0.5);
    light.specular  =GLKVector3Make(1.0,1.0,1.0);
    light.constant = 1.0;
    light.linear = 0.09f    ;
    light.quadratic = 0.032f;
//     0.0f, 0.0f, 3.0f
    const float YAW         = -90.0f;
    const float PITCH       =  0.0f;
    GLKVector3 front;
    front.x = cos(radians(YAW)) * cos(radians(PITCH));
    front.y = sin(radians(PITCH));
    front.z = sin(radians(YAW)) * cos(radians(PITCH));
    front = GLKVector3Normalize(front);
    light.direction = front;
    light.cutOff = cos(12.5*M_PI/180);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightPos], 1, &light.position);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightAmbient], 1, &light.ambient);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightSpecular], 1, &light.specular);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightTDiffuse], 1, &light.diffuse);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightConstant], 1, &light.constant);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightLinear], 1, &light.linear);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightQuadratic], 1, &light.quadratic);
    glUniform3fv(self.bindObject->uniforms[FL_UniformLocationLightDirection], 1, &light.direction);
    glUniform1fv(self.bindObject->uniforms[FL_UniformLocationLightCutOff], 1, &light.cutOff);

从前面的知识我们知道,我们不需要给切光角传入一个角度值,而用角度值计算了一个余弦值,将余弦结果传递到片段着色器中。这样做的原因是在片段着色器中,我们会计算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);

我们首先计算了lightDir和取反的direction向量(取反的是因为我们想让向量指向光源而不是从光源出发)之间的点积。记住要对所有的相关向量标准化。

你可能奇怪为什么在if条件中使用的是 > 符号而不是 < 符号。theta不应该比光的切光角更小才是在聚光内部吗?这并没有错,但不要忘记角度值现在都由余弦值来表示的。一个0度的角度表示的是1.0的余弦值,而一个90度的角度表示的是0.0的余弦值,你可以在下图中看到:


image

你现在可以看到,余弦值越接近1.0,它的角度就越小。这也就解释了为什么theta要比切光值更大了。切光值目前设置为12.5的余弦,约等于0.9978,所以在0.9979到1.0内的<var style="box-sizing: border-box; font-style: normal; font-family: "Courier New", Courier, monospace; color: rgb(34, 34, 119);">theta</var>值才能保证片段在聚光内,从而被照亮。

运行结果如图
shader 编码
precision lowp float;

attribute vec3 beginPostion; ///开始位置
attribute vec2 a_texture;  //纹理贴图
attribute vec3 a_normal; //法向量

uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4  u_inverModel;

varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec2 v_texture;

void main(){
    gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
    FragPos = vec3(u_model * vec4(beginPostion, 1.0));
    normal =  mat3(u_inverModel) * a_normal;;
    v_texture = a_texture;
}
precision mediump float;

uniform vec3 viewPos;

varying lowp vec3 normal;
varying lowp vec3 FragPos;

varying lowp vec2 v_texture;

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

struct Material{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};
uniform Material material;

void main()
{
    vec3 lightDir = normalize(light.position - FragPos);
    
    // check if lighting is inside the spotlight cone
    float theta = dot(lightDir, normalize(-light.direction));
    
    if(theta > light.cutOff) // remember that we're working with angles as cosines instead of degrees so a '>' is used.
    {
        // ambient
        vec3 ambient = light.ambient * texture2D(material.diffuse, v_texture).rgb;
        
        // diffuse
        vec3 norm = normalize(normal);
        float diff = max(dot(norm, lightDir), 0.0);
        vec3 diffuse = light.diffuse * diff * texture2D(material.diffuse, v_texture).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 * texture2D(material.specular, v_texture).rgb;
        
        // 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;
        
        vec3 result = ambient + diffuse + specular;
        gl_FragColor = vec4(result, 1.0);
    }
    else
    {
        // else, use ambient light so scene isn't completely dark outside the spotlight.
        gl_FragColor = vec4(light.ambient * texture2D(material.diffuse, v_texture).rgb, 1.0);
    }
}

平滑/软化边缘

上边实现的聚光灯看起来有点假,主要是因为聚光有一圈硬边。当一个片段遇到聚光圆锥的边缘时,它会完全变暗,没有一点平滑的过渡。一个真实的聚光将会在边缘处逐渐减少亮度。

为了创建一种看起来边缘平滑的聚光,我们需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。我们可以将内圆锥设置为上一部分中的那个圆锥,但我们也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。

为了创建一个外圆锥,我们只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。

我们可以用下面这个公式来计算这个值:


这里ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ)。最终的I值就是在当前片段聚光的强度。

我们可以这样理解


基本模型

这里在 θ 在r~ϕ 之间移动,值正好是 0~1 ,正好是余弦值的变化.
r~ϕ 是内边距到外边距的变化根据光照需要逐渐变弱
而余弦值正好符号上述条件, 在0~1 之间值随着 θ的变大正好是变小的 .因此我们可以用余弦值来表示光照强度变化

我们再举例说明下,看下列实例:

θ θ(角度) ϕ(内光切) ϕ(角度) γ(外光切) γ(角度) ϵ I
0.87 30 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.87 - 0.82 / 0.09 = 0.56
0.9 26 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.9 - 0.82 / 0.09 = 0.89
0.97 14 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0.97 - 0.82 / 0.09 = 1.67
0.83 34 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0 .83 - 0.82 / 0.09 = 0.11
0.64 50 0.91 25 0.82 35 0.91 - 0.82 = 0.09 0 .64 - 0.82 / 0.09 = -2.0
0.966 15 0.9978 12.5 0.953 17.5 0.966 - 0.953 = 0.0448 0.966 - 0.953 / 0.0448 = 0.29

你可以看到,我们基本是在内外余弦值之间根据θ
插值

我们现在有了一个在聚光外是负的,在内圆锥内大于1.0的,在边缘处于两者之间的强度值了。如果我们正确地约束(Clamp)这个值,在片段着色器中就不再需要if-else了,我们能够使用计算出来的强度值直接乘以光照分量:

precision lowp float;

attribute vec3 beginPostion; ///开始位置
attribute vec2 a_texture;  //纹理贴图
attribute vec3 a_normal; //法向量

uniform mat4 u_mvpMatrix;
uniform mat4 u_model;
uniform mat4  u_inverModel;

varying lowp vec3 normal;
varying lowp vec3 FragPos;
varying lowp vec2 v_texture;

void main(){
    gl_Position =u_mvpMatrix *u_model* vec4(beginPostion, 1.0);
    FragPos = vec3(u_model * vec4(beginPostion, 1.0));
    normal =  mat3(u_inverModel) * a_normal;;
    v_texture = a_texture;
}

 // 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;

注意我们使用了clamp函数,它把第一个参数约束(Clamp)在了0.0到1.0之间。这保证强度值不会在[0, 1]区间之外。

最终效果如图

光滑 shader 编码
precision mediump float;

uniform vec3 viewPos;

varying lowp vec3 normal;
varying lowp vec3 FragPos;

varying lowp vec2 v_texture;

struct Light{
    vec3 position;
    vec3 direction;
    float cutOff;
    float outerCutOff;
    
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    
    float constant;
    float linear;
    float quadratic;
};
uniform Light light;

struct Material{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};
uniform Material material;

void main()
{
        // ambient
        vec3 ambient = light.ambient * texture2D(material.diffuse, v_texture).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 * texture2D(material.diffuse, v_texture).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 * texture2D(material.specular, v_texture).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; // 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;
        
        vec3 result = ambient + diffuse + specular;
        gl_FragColor = vec4(result, 1.0);
   
}

源码地址 对应的demo是OpenGLZeroStudyDemo(7)-光照

参考博客

上一篇 下一篇

猜你喜欢

热点阅读