GLSL 逐像素的光照

来源:互联网 发布:剑灵网吧捏脸数据导入 编辑:程序博客网 时间:2024/06/05 15:53
逐像素的方向光(Directional Light per Pixel)
这一节将把前面的shader代码改为逐像素计算的方向光。我们需要将工作按照两个shader拆分,以确定哪些是需要逐像素操作的。
首先看看每个顶点接收到的信息:
•法线
•半向量
•光源方向
我们需要将法线变换到视点空间然后归一化。我们还需要将半向量和光源方向也归一化,不过它们已经位于视点空间中了。这些归一化之后的向量会进行插值,然后送入片断shader,所以需要声明易变变量保存这些向量。
我们也可以在顶点shader中完成一些与光和材质相关的计算,这样可以帮助平衡顶点shader和片断shader的负载。

顶点shader代码可以写成如下形式:

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中,首先要声明同样的易变变量。此外还要再次对法线进行归一化,光线向量不需要进行归一化了,因为方向光对所有顶点都是一致的,插值得到的结果自然也不会变。之后就是计算插值过的法线向量与光线向量的点积。

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传过来的散射项乘以这个点积。我们还需要计算镜面反射光,计算时首先对接收到的半向量归一化,然后计算半向量和法线之间的点积。

    ...
    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如下:

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中需要计算衰减,还需要将插值得到的光线方向向量归一化,因为一般来说照到每个顶点的光线方向都不同。

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,否则此点位于聚光之外,只能接收到全局环境光。

...

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表示在光锥内光强是常数。

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



原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 回国海关电子盖章怎么办 户籍在学校怎么办护照 割过双眼皮护照怎么办 想换电子护照怎么办 有美签的护照丢失怎么办 护照在国外掉了怎么办 上海动迁房户口怎么办 政府强制拆房怎么办 农村无证宅基地怎么办 身份证没磁怎么办护照 买安置房怎么办产权 安置房房东违约怎么办 安置房产权到期怎么办 安置房怎么办房产证吗 美甲后指甲长了怎么办 在菲律宾怎么办持枪证? 绝地求生打不准怎么办 身份证号码变更后社保怎么办 社保与身份不符怎么办 年龄改了学籍怎么办 结婚证信息错误怎么办 六级身份证过期怎么办 身份号泄露了怎么办 身体证信息泄露怎么办 手机号被泄漏了怎么办 姓名和电话泄露怎么办 个人身份证信息泄露怎么办 身份号码泄露了怎么办 我身份证泄露了怎么办 身份证信息泄漏了怎么办 无锡身份证丢了怎么办 人在外地怎么办身份证 欠空放公司不还怎么办 兼职要身份证照片怎么办 身份证刷不了磁怎么办 身份证不能刷了怎么办 身份证指纹错了怎么办 指纹手机丢了怎么办 异地办理临时身份证怎么办 杭州办理外地身份证怎么办 办理身份证没有户口本怎么办