【GLSL教程】(七)逐像素的光照

逐像素的方向光(Directional Light per Pixel)
这一节将把前面的shader代码改为逐像素计算的方向光。我们需要将工作按照两个shader拆分,以确定哪些是需要逐像素操作的。
首先看看每个顶点接收到的信息:
•法线
•半向量
•光源方向
我们需要将法线变换到视点空间然后归一化。我们还需要将半向量和光源方向也归一化,不过它们已经位于视点空间中了。这些归一化之后的向量会进行插值,然后送入片断shader,所以需要声明易变变量保存这些向量。
我们也可以在顶点shader中完成一些与光和材质相关的计算,这样可以帮助平衡顶点shader和片断shader的负载。
顶点shader代码可以写成如下形式:
  1. varying vec4 diffuse,ambient; 
  2. varying vec3 normal,lightDir,halfVector; 
  3.  
  4. void main() 
  5.     /* first transform the normal into eye space and
  6.     normalize the result */ 
  7.     normal = normalize(gl_NormalMatrix * gl_Normal); 
  8.     /* now normalize the light's direction. Note that
  9.     according to the OpenGL specification, the light
  10.     is stored in eye space. Also since we're talking about
  11.     a directional light, the position field is actually direction */ 
  12.     lightDir = normalize(vec3(gl_LightSource[0].position)); 
  13.     /* Normalize the halfVector to pass it to the fragment shader */ 
  14.     halfVector = normalize(gl_LightSource[0].halfVector.xyz); 
  15.     /* Compute the diffuse, ambient and globalAmbient terms */ 
  16.     diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; 
  17.     ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; 
  18.     ambient += gl_FrontMaterial.ambient * gl_LightModel.ambient; 
  19.  
  20.     gl_Position = ftransform(); 
varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;

void main()
{
    /* first transform the normal into eye space and
    normalize the result */
    normal = normalize(gl_NormalMatrix * gl_Normal);
    /* now normalize the light's direction. Note that
    according to the OpenGL specification, the light
    is stored in eye space. Also since we're talking about
    a directional light, the position field is actually direction */
    lightDir = normalize(vec3(gl_LightSource[0].position));
    /* Normalize the halfVector to pass it to the fragment shader */
    halfVector = normalize(gl_LightSource[0].halfVector.xyz);
    /* Compute the diffuse, ambient and globalAmbient terms */
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
    ambient += gl_FrontMaterial.ambient * gl_LightModel.ambient;

    gl_Position = ftransform();
}
接下来在片断shader中,首先要声明同样的易变变量。此外还要再次对法线进行归一化,光线向量不需要进行归一化了,因为方向光对所有顶点都是一致的,插值得到的结果自然也不会变。之后就是计算插值过的法线向量与光线向量的点积。
  1. varying vec4 diffuse,ambient; 
  2. varying vec3 normal,lightDir,halfVector; 
  3.  
  4. void main() 
  5.     vec3 n,halfV; 
  6.     float NdotL,NdotHV; 
  7.     /* The ambient term will always be present */ 
  8.     vec4 color = ambient; 
  9.     /* a fragment shader can't write a varying variable, hence we need
  10.     a new variable to store the normalized interpolated normal */ 
  11.     n = normalize(normal); 
  12.     /* compute the dot product between normal and ldir */ 
  13.     NdotL = max(dot(n,lightDir),0.0); 
  14.     ... 
varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;

void main()
{
    vec3 n,halfV;
    float NdotL,NdotHV;
    /* The ambient term will always be present */
    vec4 color = ambient;
    /* a fragment shader can't write a varying variable, hence we need
    a new variable to store the normalized interpolated normal */
    n = normalize(normal);
    /* compute the dot product between normal and ldir */
    NdotL = max(dot(n,lightDir),0.0);
    ...
如果点积结果NdotL大于0,我们就必须计算散射光,也就是用顶点shader传过来的散射项乘以这个点积。我们还需要计算镜面反射光,计算时首先对接收到的半向量归一化,然后计算半向量和法线之间的点积。
  1. ... 
  2. if (NdotL > 0.0) 
  3.     color += diffuse * NdotL; 
  4.     halfV = normalize(halfVector); 
  5.     NdotHV = max(dot(n,halfV),0.0); 
  6.     color += gl_FrontMaterial.specular * 
  7.             gl_LightSource[0].specular * 
  8.             pow(NdotHV, gl_FrontMaterial.shininess); 
  9.  
  10. gl_FragColor = color; 
    ...
    if (NdotL > 0.0)
    {
        color += diffuse * NdotL;
        halfV = normalize(halfVector);
        NdotHV = max(dot(n,halfV),0.0);
        color += gl_FrontMaterial.specular *
                gl_LightSource[0].specular *
                pow(NdotHV, gl_FrontMaterial.shininess);
    }

    gl_FragColor = color;
}
下图显示了逐像素光照和逐顶点光照效果的区别:

本节内容Shader Designer的工程下载地址:
http://www.lighthouse3d.com/wp-content/uploads/2011/03/dirpixsd.zip

逐像素的点光(Point Light Per Pixel)
本节基于前面有关方向光的内容,大部分代码都相同。本节内容主要涉及方向光和点光的不同之处。方向光一般假设光源在无限远的地方,所以到达物体时是平行光。相反,点光源有一个空间中的位置,并向四面八方辐射光线。此外,点光的强度会随到达顶点的距离而衰弱。
对于OpenGL程序来说,这两种光的区别主要有:
•光源的position域的w分量:对方向光来说它是0,表面这个position实际是一个方向(direction);对点光来说,这个分量是1。
•点光源的衰减由三个系数决定:一个常数项,一个线性项和一个二次项。
对方向光来说,光线的方向对所有顶点相同,但是对点光来说,方向是从顶点指向光源位置的向量。因此对我们来说需要修改的就是在顶点shader中加入计算光线方向的内容。
在OpenGL中衰减是按照如下公式计算的:

式中k0是常数衰减系数,k1是线性衰减系数,k2是二次衰减系数,d是光源位置到顶点的距离。
注意衰减与距离是非线性关系,所以我们不能逐顶点计算衰减再在片断shader中使用插值结果,不过我们可以在顶点shader中计算距离,然后在片断shader中使用距离的插值计算衰减。
使用点光计算颜色值的公式为:

在上面公式中,环境光部分必须分解为两项:使用光照模型的全局环境光设置和光源中的环境光设置。顶点shader也必须分别计算这两个环境光成分。新的顶点shader如下:
  1. varying vec4 diffuse,ambientGlobal,ambient; 
  2. varying vec3 normal,lightDir,halfVector; 
  3. varying float dist; 
  4.  
  5. void main() 
  6.     vec4 ecPos; 
  7.     vec3 aux; 
  8.     normal = normalize(gl_NormalMatrix * gl_Normal); 
  9.  
  10.     /* these are the new lines of code to compute the light's direction */ 
  11.     ecPos = gl_ModelViewMatrix * gl_Vertex; 
  12.     aux = vec3(gl_LightSource[0].position-ecPos); 
  13.     lightDir = normalize(aux); 
  14.     dist = length(aux); 
  15.     halfVector = normalize(gl_LightSource[0].halfVector.xyz); 
  16.  
  17.     /* Compute the diffuse, ambient and globalAmbient terms */ 
  18.     diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; 
  19.     /* The ambient terms have been separated since one of them */ 
  20.     /* suffers attenuation */ 
  21.     ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; 
  22.     ambientGlobal = gl_FrontMaterial.ambient * gl_LightModel.ambient; 
  23.     gl_Position = ftransform(); 
varying vec4 diffuse,ambientGlobal,ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;

void main()
{
    vec4 ecPos;
    vec3 aux;
    normal = normalize(gl_NormalMatrix * gl_Normal);

    /* these are the new lines of code to compute the light's direction */
    ecPos = gl_ModelViewMatrix * gl_Vertex;
    aux = vec3(gl_LightSource[0].position-ecPos);
    lightDir = normalize(aux);
    dist = length(aux);
    halfVector = normalize(gl_LightSource[0].halfVector.xyz);

    /* Compute the diffuse, ambient and globalAmbient terms */
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    /* The ambient terms have been separated since one of them */
    /* suffers attenuation */
    ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
    ambientGlobal = gl_FrontMaterial.ambient * gl_LightModel.ambient;
    gl_Position = ftransform();
}
在片断shader中需要计算衰减,还需要将插值得到的光线方向向量归一化,因为一般来说照到每个顶点的光线方向都不同。
  1. varying vec4 diffuse,ambientGlobal, ambient; 
  2. varying vec3 normal,lightDir,halfVector; 
  3. varying float dist; 
  4.  
  5. void main() 
  6.     vec3 n,halfV,viewV,ldir; 
  7.     float NdotL,NdotHV; 
  8.     vec4 color = ambientGlobal; 
  9.     float att; 
  10.     /* a fragment shader can't write a varying variable, hence we need
  11.     a new variable to store the normalized interpolated normal */ 
  12.     n = normalize(normal); 
  13.     /* compute the dot product between normal and normalized lightdir */ 
  14.     NdotL = max(dot(n,normalize(lightDir)),0.0); 
  15.     if (NdotL > 0.0) 
  16.     { 
  17.         att = 1.0 / (gl_LightSource[0].constantAttenuation + 
  18.                 gl_LightSource[0].linearAttenuation * dist + 
  19.                 gl_LightSource[0].quadraticAttenuation * dist * dist); 
  20.         color += att * (diffuse * NdotL + ambient); 
  21.         halfV = normalize(halfVector); 
  22.         NdotHV = max(dot(n,halfV),0.0); 
  23.         color += att * gl_FrontMaterial.specular * gl_LightSource[0].specular * 
  24.                         pow(NdotHV,gl_FrontMaterial.shininess); 
  25.     } 
  26.     gl_FragColor = color; 
varying vec4 diffuse,ambientGlobal, ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;

void main()
{
    vec3 n,halfV,viewV,ldir;
    float NdotL,NdotHV;
    vec4 color = ambientGlobal;
    float att;
    /* a fragment shader can't write a varying variable, hence we need
    a new variable to store the normalized interpolated normal */
    n = normalize(normal);
    /* compute the dot product between normal and normalized lightdir */
    NdotL = max(dot(n,normalize(lightDir)),0.0);
    if (NdotL > 0.0)
    {
        att = 1.0 / (gl_LightSource[0].constantAttenuation +
                gl_LightSource[0].linearAttenuation * dist +
                gl_LightSource[0].quadraticAttenuation * dist * dist);
        color += att * (diffuse * NdotL + ambient);
        halfV = normalize(halfVector);
        NdotHV = max(dot(n,halfV),0.0);
        color += att * gl_FrontMaterial.specular * gl_LightSource[0].specular *
                        pow(NdotHV,gl_FrontMaterial.shininess);
    }
    gl_FragColor = color;
}
下图显示了固定功能的逐顶点与本节中逐像素计算得到的点光效果:

本节内容Shader Designer工程下载地址:
http://www.lighthouse3d.com/wp-content/uploads/2011/03/pointlightsd.zip

逐像素的聚光(Spot Light Per Pixel)
本节内容与上一节基本一致,唯一不同的就是聚光不同于点光,其发出的光线被限制在一个圆锥体中。
对于OpenGL程序来说,这两种光的区别主要有:
•聚光包含一个方向向量spotDirection,表示圆锥体的轴。
•圆锥体包含一个角度,在GLSL中可以使用应用程序设置的角度值以及对应的余弦值spotCosCutoff。
•最后还有一个衰减速率spotExponent,它表示从圆锥的中心轴向外表面变化时光强度的衰减。
聚光的顶点shader与点光完全相同,我们只需要对片断shader进行一些修改。只有当当前片断位于聚光的光锥内时,才需要对散射光、镜面反射光和环境光成分进行着色。所以我们首先要检查这个条件。
光源与某点连线向量以及聚光方向向量(spotDirection)之间夹角的余弦值必须大于spotCosCutoff,否则此点位于聚光之外,只能接收到全局环境光。
  1. ... 
  2.  
  3. n = normalize(normal); 
  4.  
  5. /* compute the dot product between normal and ldir */ 
  6. NdotL = max(dot(n,normalize(lightDir)),0.0); 
  7.  
  8. if (NdotL > 0.0) 
  9.     spotEffect = dot(normalize(gl_LightSource[0].spotDirection), 
  10.             normalize(-lightDir)); 
  11.     if (spotEffect > gl_LightSource[0].spotCosCutoff) 
  12.     { 
  13.         /* compute the illumination in here */ 
  14.     } 
  15.  
  16. gl_FragColor = ... 
...

n = normalize(normal);

/* compute the dot product between normal and ldir */
NdotL = max(dot(n,normalize(lightDir)),0.0);

if (NdotL > 0.0)
{
    spotEffect = dot(normalize(gl_LightSource[0].spotDirection),
            normalize(-lightDir));
    if (spotEffect > gl_LightSource[0].spotCosCutoff)
    {
        /* compute the illumination in here */
    }
}

gl_FragColor = ...
下面的光照计算与点光非常相似,唯一区别是衰减必须乘以聚光效果(spotlight effect),这个值按如下公式计算:

上式中spotDirection来自OpenGL中设置的状态,lightDir是光源到某点的向量,spotExp是聚光衰减率,这个值也是在OpenGL程序中设置的,它用来控制从聚光光锥中心到边缘的衰减。spotExp越大衰减越快,如果为0表示在光锥内光强是常数。
  1. spotEffect = pow(spotEffect, gl_LightSource[0].spotExponent); 
  2. att = spotEffect / (gl_LightSource[0].constantAttenuation + 
  3.         gl_LightSource[0].linearAttenuation * dist + 
  4.         gl_LightSource[0].quadraticAttenuation * dist * dist); 
  5.  
  6. color += att * (diffuse * NdotL + ambient); 
  7.  
  8. halfV = normalize(halfVector); 
  9. NdotHV = max(dot(n,halfV),0.0); 
  10. color += att * gl_FrontMaterial.specular * 
  11.             gl_LightSource[0].specular * 
  12.             pow(NdotHV,gl_FrontMaterial.shininess); 
spotEffect = pow(spotEffect, gl_LightSource[0].spotExponent);
att = spotEffect / (gl_LightSource[0].constantAttenuation +
        gl_LightSource[0].linearAttenuation * dist +
        gl_LightSource[0].quadraticAttenuation * dist * dist);

color += att * (diffuse * NdotL + ambient);

halfV = normalize(halfVector);
NdotHV = max(dot(n,halfV),0.0);
color += att * gl_FrontMaterial.specular *
            gl_LightSource[0].specular *
            pow(NdotHV,gl_FrontMaterial.shininess);
下图分别显示了使用固定功能流水线的逐顶点光照计算,以及使用本节shader的逐像素光照计算得到的聚光效果。

本节内容Shader Designer的工程下载地址:
http://www.lighthouse3d.com/wp-content/uploads/2011/03/spotlightsd.zip
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: GLSL是一种高级着色语言,用于编写OpenGL着色器。其中,片源着色器是其中的一种。在GLSL中,一个片元代表一个像素,片元着色器会在每个像素处运行一次,计算出对应像素的最终颜色。 这篇GLSL片源着色器教程将会介绍一些基本的概念和操作,包括数据类型、变量、控制流语句、函数和一些常用的函数库。 在编写GLSL片源着色器时,我们需要定义一些输入和输出变量,以便与其他部分的着色器和应用程序进行通信,在片源着色器中主要是传入顶点数据,其中包括顶点的位置、法线、纹理坐标等。我们还可以声明一些中间变量和输出变量用于存储过程中计算出的结果,这些变量是片源着色器中非常重要的概念。 在GLSL片源着色器中,我们可以使用控制流语句来进行条件判断和循环,这些语句类似于其他编程语言中的语句,包括if/else、for、while等。 GLSL着色语言提供了许多内置函数和库,来进行常用的计算、采样、光照和常见效果的计算等。在编写片源着色器时,使用这些函数和库可以显著地简化代码,加速处理速度。 总之,GLSL片源着色器是OpenGL实现高效渲染的关键部分。熟练掌握GLSL片源着色器的编写技巧,可以帮助我们编写高效的渲染引擎和处理器。 ### 回答2: GLSL是OpenGL着色器语言的标准,它用于编写图形渲染的着色器程序。其中的一种着色器就是片源着色器。片源着色器是在像素级别对图形的颜色进行处理的程序。GLSL片源着色器的编写其实非常简单,只需要使用一些基本的语言结构,就可以进行像素级别的颜色处理了。 首先,我们要定义变量。GLSL是强类型编程语言,所以要先定义需要用到的变量。可以定义各种类型的变量,比如int、float、vec2、vec3等。 其次,我们要获取图形的当前像素坐标和颜色。片源着色器可以访问当前像素的坐标和颜色信息。这两个变量可以通过gl_FragCoord和gl_FragColor来获取。 然后,我们可以对颜色进行各种处理。GLSL提供了各种内置函数,可以对颜色进行处理复杂的运算。我们也可以自定义一些函数来进行更加复杂的颜色操作。 最后,我们可以将处理后的颜色写入到渲染缓冲区中。这个可以通过gl_FragColor来实现。 总之,GLSL片源着色器的编写非常简单,只需要掌握一些基本的语言结构就可以了。要实现复杂的效果,可以结合使用各种运算和内置函数。通过灵活使用GLSL片源着色器,可以实现各种炫酷的图形渲染效果。 ### 回答3: GLSL是OpenGL着色语言的缩写,它是一种高级着色器语言。在OpenGL中,着色器被用于渲染物体和场景。 GLSL着色器分为两种类型,分别是顶点着色器和片源着色器。其中,片源着色器用于计算每个像素的颜色值。 GLSL片源着色器教程的目的是为了帮助开发者了解如何创建自己的着色器程序。在GLSL中,着色器程序由一系列指令组成,这些指令被称为着色器代码。 首先,我们需要创建一个基本的片源着色器程序。在GLSL中,片源着色器程序由以下几个部分组成:输入,输出和主程序。 输入部分定义了着色器需要接收的数据类型,包括颜色,法线向量等。输出部分定义了着色器程序需要返回的颜色值。主程序部分包括了对输入数据的处理和计算步骤。 在GLSL中,像素颜色值是通过处理输入数据得出的,具体的颜色计算过程可以在主程序中实现。通过这种方式,开发者可以利用GLSL着色器来创建各种不同的效果和渲染方式。 总之,如果你想学习GLSL片源着色器编程,你需要了解着色器程序的基本结构和语法,掌握各种自定义函数和变量,以及学会在主程序中进行高级计算和渲染操作。这些技能将有助于你创建出最佳的渲染效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值