[OpenGL ES 08]Per-Pixel Light及卡通效果

[OpenGL ES 08]Per-Pixel Light及卡通效果

罗朝辉 (http://blog.csdn.com/kesalin/)

本文遵循“署名-非商业用途-保持一致”创作公用协议

 

这是《OpenGL ES 教程》的第九篇,前八篇请参考如下链接:


前言

本文是基于前文《光照原理》以及《Per-Vertex Light及深度缓存》两篇文章的,如果你还不熟悉光照相关的基础知识,请先阅读那两篇文章。在今天的这篇文章中,我们来研究 Per-Pixel 光照效果以及卡通效果。Per-Pixel 光照效果就是在片元着色阶段针对每个像素进行光照计算,而卡通效果是将散射光因子“分级”从而不再是连续(打个比方说,考试成绩上百分制是连续的,而分级制:好/良好/及格/不及格就不是连续的),这样就能获得漫反射跳跃的卡通效果。Per-Pixel Light 示例源码在这里,运行效果如下:


 

一,创建工程

Per-Vertex light 与 Per-Pixel 的光照计算基本上相同,只是进行的时机不同,Per-Vertex Light 在顶点着色阶段针对每个顶点进行光照计算,而 Per-Pixel 是在片元着色阶段针对每个像素进行光照计算。因此,本文将在前文《Per-Vertex Light及深度缓存》源码的基础上继续进行。

 

二,Per-Pixel Light

1,修改顶点着色

这次,顶点着色脚本非常简单,因为光照计算工作都将转移到片元着色脚本中进行。为了方便与前文中的脚本进行对比,在这里,保留前文中的脚本,新建 PerPixelVertex.glsl 以及 PerPixelFragment.glsl 脚本。

PerPixelVertex.glsl 脚本内容如下:

uniform mat4 projection;
uniform mat4 modelView;
uniform mat3 normalMatrix;

attribute vec4 vPosition;
attribute vec3 vNormal;
attribute vec3 vDiffuseMaterial;

varying vec3 vEyeSpaceNormal;
varying vec3 vDiffuse;

void main(void)
{
    gl_Position = projection * modelView * vPosition;
    
    vEyeSpaceNormal = normalMatrix * vNormal;
    vDiffuse = vDiffuseMaterial;
}

从上面的代码中可以看到,顶点着色器只是简单地转换 local space 中的法线到 view space,然后将相关 varying 传递给片元着色器。

PerPixelFragment.glsl 脚本内容如下:

varying mediump vec3 vEyeSpaceNormal;
varying mediump vec3 vDiffuse;

uniform highp vec3 vLightPosition;
uniform highp vec3 vAmbientMaterial;
uniform highp vec3 vSpecularMaterial;
uniform highp float shininess;

void main()
{
    highp vec3 N = normalize(vEyeSpaceNormal);
    highp vec3 L = normalize(vLightPosition);
    highp vec3 E = vec3(0, 0, 1);
    highp vec3 H = normalize(L + E);

    highp float df = max(0.0, dot(N, L));
    highp float sf = max(0.0, dot(N, H));
    sf = pow(sf, shininess);

    mediump vec3 color = vAmbientMaterial + df * vDiffuse + sf * vSpecularMaterial;
    
    gl_FragColor = vec4(color, 1);
}

从上面的代码可以看到,原先在顶点着色器中进行的光照计算被转移到片元着色器中了。这里没有什么特别的,光照计算过程还是前面两篇文章介绍的那些内容,因此在这里就不再累述了。

为了方便在不同着色脚本之间进行切换,我定义了一个 LightMode 枚举:

enum LightMode {
    PerVertex,
    PerPixel,
    PerPixelToon,
};
const LightMode CurrentLightMode = PerPixel;

并在 setProgram 中根据当前的光照计算模式来载入对应的脚本:

- (void)setupProgram
{
    // Load shaders
    //
    NSString * vertexShaderPath = nil;
    NSString * fragmentShaderPath = nil;

    if (CurrentLightMode == PerVertex) {
        vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
                                                           ofType:@"glsl"];
        fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
                                                             ofType:@"glsl"];
    }
    else if (CurrentLightMode == PerPixelToon) {
        vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"PerPixelVertex"
                                                           ofType:@"glsl"];
        fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"PerPixelToonFragment"
                                                             ofType:@"glsl"];
    }
    else  {
        // default per-pixel light
        vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"PerPixelVertex"
                                                           ofType:@"glsl"];
        fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"PerPixelFragment"
                                                             ofType:@"glsl"];
    }
    
    //......
}

编译运行,效果如下图。细心的童鞋可以比较 Per-Vertex 与 Per-Pixel 两种光照的效果。Per-Vertex 光照计算是在顶点着色阶段进行,然后在光栅化阶段进行线性插值;而 Per-Pixel 光照计算是在片元着色阶段针对每一个像素进行,因此后者要比前者更加细致逼真,效果更好一些,当然计算量自然也要大。


 

三,卡通效果

前面说过,卡通效果是将散射光因子“分级”从而不再是连续的,打个比方说,考试成绩上百分制是连续的,而分级制:好/良好/及格/不及格就不是连续的,这样就能获得漫反射跳跃的卡通效果。

新建 PerPixelToonFragment.glsl 脚本,其内容如下:

varying mediump vec3 vEyeSpaceNormal;
varying mediump vec3 vDiffuse;

uniform highp vec3 vLightPosition;
uniform highp vec3 vAmbientMaterial;
uniform highp vec3 vSpecularMaterial;
uniform highp float shininess;

void main()
{
    highp vec3 N = normalize(vEyeSpaceNormal);
    highp vec3 L = normalize(vLightPosition);
    highp vec3 E = vec3(0, 0, 1);
    highp vec3 H = normalize(L + E);

    highp float df = max(0.0, dot(N, L));
    highp float sf = max(0.0, dot(N, H));
    sf = pow(sf, shininess);
    
    if (df < 0.1)
        df = 0.0;
    else if (df < 0.2)
        df = 0.2;
    else if (df < 0.4)
        df = 0.4;
    else if (df < 0.6)
        df = 0.6;
    else if (df < 0.8)
        df = 0.8;
    else
        df = 1.0;

    mediump vec3 color = vAmbientMaterial + df * vDiffuse + sf * vSpecularMaterial;
    
    gl_FragColor = vec4(color, 1);
}

注意看粗体部分,这就是新增的部分。这部分代码将漫反射因子调整为五个级别:0.0,0.2,0.6,0.8,1.0,因此漫反射就有层次效果了。如下图所示:


 

四,总结

Per-Vertex 与 Per-Pixel 两种光照的异同:两者都是基于相同的光照原理来进行光照计算的,Per-Vertex 光照计算是在顶点着色阶段进行,然后在光栅化阶段进行线性插值;而 Per-Pixel 光照计算是在片元着色阶段针对每一个像素进行。因此后者要比前者效果更好,看上去更加细致逼真,当然计算量自然也要多一些。

卡通效果是将漫反射因子分级,从而形成不连续的跳跃的漫反射效果。在本文中,是在片元着色阶段进行卡通效果处理的,它也可以在顶点着色阶段进行。

在这个系列的介绍中,只提及了一些简单的光照效果,还有很多更加逼真的光照算法或技巧没有涉及,比如菲涅尔效果或使用光照贴图。

菲涅尔效果:根据观察者的观察表面来调整反射率来实现的。比如你从水面,油漆表面或者丝绸的正上方看,反射光泽的柔和效果基本没有,如果侧着或平着看的话,反射光泽的柔和效果就很明显。

光照贴图:使用预先处理好的明暗纹理来模拟光照,这样可以减少实时的光照计算,但这样的技巧只适用于静态场景。

 

相关推荐
<p> <span style="font-size:14px;color:#E53333;">限时福利1:</span><span style="font-size:14px;">购课进答疑群专享柳峰(刘运强)老师答疑服务</span> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;"></span> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>为什么需要掌握高性能的MySQL实战?</strong></span> </p> <p> <span><span style="font-size:14px;"><br /> </span></span> <span style="font-size:14px;">由于互联网产品用户量大、高并发请求场景多,因此对MySQL的性能、可用性、扩展性都提出了很高的要求。使用MySQL解决大量数据以高并发请求已经是程序员的必备技能,也是衡量一个程序员能力和薪资的标准之一。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">为了让大家快速系统了解高性能MySQL核心知识全貌,我为你总结了</span><span style="font-size:14px;">「高性能 MySQL 知识框架图」</span><span style="font-size:14px;">,帮你梳理学习重点,建议收藏!</span> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006031401338860.png" /> </p> <p> <br /> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>【课程设计】</strong></span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;">课程分为四大篇章,将为你建立完整的 MySQL 知识体系,同时将重点讲解 MySQL 底层运行原理、数据库的性能调优、高并发、海量业务处理、面试解析等。</span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;"></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>一、性能优化篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括经典 MySQL 问题剖析、索引底层原理和事务与锁机制。通过深入理解 MySQL 的索引结构 B+Tree ,学员能够从根本上弄懂为什么有些 SQL 走索引、有些不走索引,从而彻底掌握索引的使用和优化技巧,能够避开很多实战中遇到的“坑”。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>二、MySQL 8.0新特性篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括窗口函数和通用表表达式。企业中的许多报表统计需求,如果不采用窗口函数,用普通的 SQL 语句是很难实现的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>三、高性能架构篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括主从复制和读写分离。在企业的生产环境中,很少采用单台MySQL节点的情况,因为一旦单个节点发生故障,整个系统都不可用,后果往往不堪设想,因此掌握高可用架构的实现是非常有必要的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>四、面试篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">程序员获得工作的第一步,就是高效的准备面试,面试篇主要从知识点回顾总结的角度出发,结合程序员面试高频MySQL问题精讲精练,帮助程序员吊打面试官,获得心仪的工作机会。</span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页