[OpenGL ES 07]光照原理

http://blog.csdn.net/kesalin/article/details/8451595

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

 

[OpenGL ES 07-1]光照原理


前言

在前文《[OpenGL ES 05]相对空间变换及颜色》中讲到人的眼睛捕捉色彩的过程:在光照的作用下,物体表面反射不同频率的光子到人的眼睛中刺激感光细胞从而形成视觉。在那一篇文章中,演示了如何使用颜色,但没有提及光照,在接下来的文章中,我将来详细介绍光的特性,物体的材质属性,光照原理以及在 OpenGL ES 2.0中的应用示例。

 

一,光照的基本概念

运行 OpenGL 程序在屏幕上显示的最终颜色,受场景中光线的特性以及物体反射和吸收光的属性(即材质)影响。光线可能来自特定的位置与方向,也可能是散布在整个场景中(环境光);而物体表面能够吸收,反射光线,有些物体本身还能够发射光线,物体的这些属性被称为材质。在 OpenGL 中,物体的材质属性通过反射不同方向的环境光,漫反射光,镜面光的RGB颜色来表示的。光照计算就是将发射光,泛射光,漫反射光以及镜面高光四个成分分别计算,然后再累加起来。

光特性

发射光(emission):由物体自身发射的光。如果物体本身不发光,则无此属性;

环境光(ambient):就是哪些在环境中进行了充分散射的光,而无法分辨其方向的光。光线在物体表面上向各个方向上均匀泛射,场景中的物体都会泛射光,这些泛射光又会照射到其他物体上继续被泛射,直到光子能量耗尽为止,这样整个场景中散布着这样的泛射光。在编程时,可通过设置一个颜色常量来表示环境光或使用 ambient occlusion map (环境闭包贴图)来处理环境光。在 OpenGL 中,全局环境光的强度为 (0.2, 0.2, 0.2, 1.0),这弱弱的白色全局环境光确保即使没有额外的光源,场景中的物体依然是可见的。

漫反射光(diffuse):光线来自某个方向,但在物体表面上向各个方向上反射,无论在何处观察,散射光看上去亮度都相同。我们之所以能看到物体,就是因为物体将入射的光然后向各个方向反射(所以称之为漫反射)。物体的漫反射材质属性对物体的颜色起着决定性作用。

镜面高光(specular):光线来自一个特定的方向,然后在物体表面上以一个特定的方向反射出去(镜面反射)。正是由于镜面高光让物体看起来有光泽,如台球,金属表面的光泽。镜面反射与物体的镜面反射材质属性有很大关系,如金属表面能产生很高的镜面发射,而地毯几乎没有镜面反射。在 OpenGL 中,镜面光的强度可通过光泽度(shininess)来调节。

材质属性

物体的材质属性取决于物体反射的红绿蓝光的比例。与光源的特性相似,材质也分为泛射材质,漫反射材质,镜面材质和反射材质,分别表示反射相应类型光的反射率。

泛射材质:环境光的反射率影响物体的整体颜色,因为直射到物体上的漫反射光最亮,而没有被直射的物体的环境反射光最明显。一个物体的泛射光反射率受全局环境光和来自光源的环境光的双重影响。泛射光的反射率不受观察点点位置的影响。

漫反射材质:漫反射的反射率对物体的颜色起着最重要的作用,它受入射的漫反射光颜色以及入射光与法线的夹角的影响,但不受观察点位置的影响。

镜面反射材质:表面较为光洁的物体由于反射光线而会产生亮斑(看起来有光泽),在这种亮斑被称为镜面反射光。在 OpenGL 中物体对光的镜面反射材质属性决定了光泽的颜色、大小和亮度。镜面反射光的强度还取决于观察点的位置,当观察点正好处于入射光的反射光线上,亮斑的亮度到达最大值。

发射材质:前面提到的三种材质都是被动地反射来自外界的光线,而有些物体本身可以发射光。在 OpenGL 中是通过给物体设置发射材质颜色来模拟的,物体表面的发射颜色可以增加物体的强度,但不受任何光源的影响。此外,在整个场景中,发射光并没有被当作一种额外的光照。

 

二,光照的计算

前面说了,光照效果是由发射光,环境光,漫反射光以及镜面高光四部分组成,这四部分各自独立计算,然后再累加起来得到最终的光照效果。下面就来详细介绍这些组成部分是如何计算的。

发射光计算

前面说过,在 OpenGL 中是给物体材质设置发射颜色来模拟发射光的,因此它的计算非常简单:

发射颜色 = 物体的发射材质颜色

环境光计算

前面说到,一般我们是设置一个颜色常量来表示环境光(或来自 ambient occlusion map),因此环境光的计算也是很简单的。

环境颜色 = 光源的环境光颜色 × 物体的环境材质颜色

漫反射光计算

漫反射如下图所示:


漫反射颜色 = 光源的漫反射光颜色 × 物体的漫反射材质颜色 × DiffuseFactor

其中漫反射因子 DiffuseFactor 是光线与顶点法线向量的点积:

DiffuseFactor = max(0, dot(N, L))

在图形学中,点积几何意义其实就是表示两个向量之间夹角的 cos 值。因此这个公式直观地揭示了漫反射的规律:顶点法线正对入射光线,漫反射效果最强,顶点法线背对入射光线(角度大于等于90度)就完全没有漫反射效果。

注意:在光照计算中,顶点法线必须是经过规范化的(normalize)。

我们可以设置一个颜色常量来表示漫反射材质颜色,来得到平滑颜色的表面。但有些物体表面是很粗糙的,如裂缝,隆起,刮痕等。为了获得这样的粗糙表面效果,可以使用 displaced polygon 技术,但这需要大量的计算,效率低下。另外一种更为高效的方式就是使用凹凸贴图(bump map)。凹凸贴图就是一种纹理,其内容包含编码在RGB颜色空间中的经过扰动的顶点法线。在使用凹凸贴图进行漫反射计算时时,首先和前面一样用正常法线计算出 NormalDiffuseFactor,再根据扰动的顶点法线计算出 PerturbedDiffuseFactor,然后将两者相乘作为最终的 DiffuseFactor。如下图所示:

普通漫反射凹凸贴图

镜面反射

镜面反射如下图所示:


镜面反射颜色 = 光源的镜面光颜色 × 物体的镜面材质颜色 × SpecularFactor

SpecularFactor = power(max(0, dot(N, H)), shininess)

H = normalise(L + E)

与漫反射不同,镜面反射受观察者的位置影响,这一点在上面的计算公式中可以清楚地看出来。H 向量是视线向量 E 与光线向量 L 的半向量(注意:它经过规划化的),其几何意义就是视线与光线夹角的平分线。而 H 和 N 的点积的几何意义就是说这个平分线与法线的夹角的 cos 值,然后将这个 cos 值进行 shininess 次乘方计算得到最终的镜面反射因子 Specularfactor。 这里的关键点在于视线与光线的平分线与法线的点积计算上,这表示,当视线与光线在表面处的反射光线夹角(可看成是N与H的夹角)越少时,镜面反射效果最明显(角度越小,cos 值越大)

一般可使用常量颜色作为镜面材质颜色,但这样得到的效果有时候并不理想。同漫反射一样,我们也可以使用镜面贴图来获得更好的镜面反射效果。镜面贴图通过控制物体表示上特定像素允许的镜面反射强度来获得较为真实的效果。

普通镜面反射镜面贴图纹理镜面贴图反射


衰减因子

光源发射的光线在其传播过程中,会与空气中的其他粒子碰撞,其能量会逐渐衰减。在 OpenGL 中,这是通过将光照强度乘以随传播距离变化的衰减因子来模拟实现的。这个衰减因子的计算公式如下:

衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 × 距离 + 二次衰减常量 × 距离的平方)

其中距离衰减常量,线性衰减常量和二次衰减常量均为常量值。

注意:环境光,漫反射光和镜面光的强度都会受随着距离的增大而衰减,只有发射光和全局环境光的强度不会受此影响。

聚光灯因子

在这里,我们讨论聚光灯的发射光照计算(也即位置型光源:如台灯,相对方向性光源:如太阳)。聚光灯就是朝某个特定发射发射光线的光源。你可以想象下漆黑的夜晚里,一个手电筒给与你光明的这个场景,手电筒就是一个很好的聚光灯示例。聚光灯的计算分为两部分:在光线照射角度范围之外的部分被忽略,只有在照射角度范围之内的部分需要计算。

聚光灯夹角cos值 = power(max(0, dot(单位光源向量, 单位光线向量)), 聚光灯指数)

其中单位光线向量是从光源指向顶点的单位向量,聚光灯指数表示聚光灯的亮度程度。前面说过点积的几何意义就是表示角度的,这里聚光灯因子就表示光源向量与光线向量之间的夹角。

而为了模拟真实聚光灯光环效果,在照射角度范围之内与之外的接壤处,设置一个渐变过渡区域,以避免光照从有到无巨变:

无过渡

有过渡

增加过渡区之后的计算如下:


内环是完全光照的,而过渡区域(中环)是从完全光照到完全没有光照的渐变过渡,外环是完全没有光照的。

聚光灯因子 = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值)/(外环的聚光灯角度cos值 - 内环聚光灯角度cos值), 0, 1)

clamp 函数是将聚光灯因子限定在[0, 1]之间,这样,内环是完全光照的,而中环是从完全光照到没有光照的渐变过渡,外环是没有光照的。因此:

聚光灯效果 = 聚光灯光源颜色  × 聚光灯因子


光照计算的终极公式

光照颜色 = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜色 + 镜面反射颜色) × 聚光灯效果 × 衰减因子

如果场景中有多个光源(包括环境光),那么分别计算来每个光源的光照颜色,然后把这些光照颜色累加即可。如果物体不发射光,则没有发射颜色这一成分。


三,高洛德着色(Gouraud Shading)与冯氏着色(Phong Shading)

在图形渲染中有两种着色方式,高洛德着色与冯氏着色。高洛德着色也被称为Per-Vertex着色,它是在顶点着色阶段对顶点进行颜色计算,然后在光栅化阶段对这些顶点颜色进行线性插值形成片元的颜色;冯氏着色也被称为Per-Pixel像素着色,它是在片元着色阶段对每一个片元(像素)进行颜色计算。无疑,插值的颜色效果没有针对每一个片元进行颜色计算的效果好(除非你的图元切分到像素近似大小,不过这样 GPU 肯定吃不消,计算量巨大!)。

Gouraud 着色Phong 着色


四,结语

光照是个很复杂的话题,尤其是多光源,阴影的计算。在这篇文章中,我详细介绍了不同类型光的性质以及物体的材质属性,以及光照计算的过程。这些都是光照的基石,掌握了这些才有可能继续攀登高峰。在下一篇文章中,我将演示一个 Gouraud 着色的光照示例。

 

五,参考

1. "Lambertian reflectance"Wikipedia

2. "Phong shading" . Wikipedia.

3. Devmaster.net. The basics of 3d lighting

4. OpenGL 编程指南



 

[OpenGL ES 07-2]Per-Vertex Light及深度缓存

http://blog.csdn.net/kesalin/article/details/8166905

前言

在前文《[OpenGL ES 07-1]光照原理》中已经介绍 Opengl 中的光照原理,接下来将演示如何将这些原理用 OpenGL ES 2.0 来实现。今天的这篇文章将介绍 Per-Vertex Light 以及深度缓存,下一篇文章将介绍 Per-Pixel Light 以及卡通效果。还记得在第六篇文章的末尾留了一个小作业,用顶点缓存描绘一个立方体么 Cube?在这篇文章就会用到它。Per-Vertex Light 示例源码在这里,运行效果如下:


 

在开始之前,先来回顾一下 Per-Vertex Light 是怎么回事。Per-Vertex Light 也称为 Gauroud 着色,它是在顶点着色阶段对每一个顶点进行颜色计算,然后在光栅化阶段利用这些顶点颜色进行线性插值形成片元的颜色。

 

一,准备工作

1,新建工程

和前面的文章一样,新建名为 Tutorial07 的 Single View Application,导入 OpenGLES.framework 和 QuartzCore.framework。然后将 Tutorial06 中的 Utils, Shader,Surface三个目录以及 OpenGLView.h/m 两个文件拷贝到 Tutorial07 中,并在 XCode 中将它们加入进来。

2,添加 Cube 类型的 VBO

将第六篇的小作业:为 Cube 的 VBO 的那部分代码 - (DrawableVBO *)createVBOsForCube 加入到 OpenGLView.m 中,并修改 - (void)setupVBOs 的实现,在 _vboArray 加入 Cube 这个类型。这部分代码与本文主题不相干,所以就不在这里累述了,详情请参看源代码

3,添加控制控件

参照效果图,在 Storyboard 中添加相关控件:


4,添加响应代码

和前面的示例一样,在 ViewController 中加入相关响应代码,并使用拖拽技巧与 Storyboard 中的对应控件关联起来。下面只列出一部分代码,完整代码请参考源代码

@property (nonatomic, strong) IBOutlet OpenGLView * openGLView;

@property (nonatomic, strong) IBOutlet UISlider * lightXSlider;
// ...

- (IBAction)lightXSliderValueChanged:(id)sender;
// ...

- (IBAction)segmentSelectionChanged:(id)sender;

 

二,Per-Vertex Light 实现

1,修改顶点着色器

在本文中,光照计算的实际工作都是在顶点着色器中进行的,因此首先修改顶点着色器 VertexShader.glsl 如下:

uniform mat4 projection;
uniform mat4 modelView;
attribute vec4 vPosition;

uniform mat3 normalMatrix;
uniform vec3 vLightPosition;
uniform vec4 vAmbientMaterial;
uniform vec4 vSpecularMaterial;
uniform float shininess;

attribute vec3 vNormal;
attribute vec4 vDiffuseMaterial;

varying vec4 vDestinationColor;

void main(void)
{
    gl_Position = projection * modelView * vPosition;
    
    vec3 N = normalMatrix * vNormal;
    vec3 L = normalize(vLightPosition);
    vec3 E = vec3(0, 0, 1);
    vec3 H = normalize(L + E);

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

    vDestinationColor = vAmbientMaterial + df * vDiffuseMaterial + sf * vSpecularMaterial;
    
    //vDestinationColor = vec4(1.0, 0.0, 0.0, 1.0);
}

在这里,添加了光源位置:vLightPosition,以及三种类型的材质属性:环境材质 vAmbientMaterial,漫反射材质 vDiffuseMaterial 和镜面反射材质 vSpecularMaterial。这些都在前文《光照原理》中介绍过了,在这里就不再重复了。光照计算的最终颜色等于三种类型的光照效果的累加:

vDestinationColor = vAmbientMaterial + df * vDiffuseMaterial + sf * vSpecularMaterial;

df 漫反射因子:它是光线与顶点法线向量的点积,几何意义就是光线 L 与法线 N 之间夹角的 cos 值;

sf 镜面反射因子:它是视线 E 与光线 L 形成的夹角的平分线 H 与顶点法线向量的点积(几何意义就是平分线 H 与顶点法线 N 之间夹角的 cos 值),再 shininess 次方;

平分线向量 H:它是通过将视线向量 E 与 光线向量 L 相加,并规范化计算而来;

shininess 光泽强度:它由 OpenGL 程序传入。当然啦,最终是在 UI 通过 shininess 这个滑块控制的,该值越小,光泽强度越大。

前面提到过,OpenGL 中的很多计算都要将向量规划化,在这里就体现出来了,比如法线,平分线,位置向量等。在上面的代码中,还从 OpenGL 程序中传入了法线变换矩阵 normalMatrix,这个值得一说。

2,法线变换矩阵

为什么需要法线变换矩阵呢?因为法线向量与顶点向量一样,是在物体的模型空间中,而光照计算通常是在视图空间中进行的,因此我们需要将模型空间中的法线向量变换到视图空间,这是原因之一。或许你或说,这个变换直接用和用于顶点变换的模型视图变换矩阵 modelView 就可以了呀。诚然,当模型视图变换是刚体变换时,法线变换矩阵与模型视图变换矩阵完全一样;但如果模型视图变换不是刚体变换时,两者就不相同了。所谓刚体变换就是说物体在 x, y, z 三个方向进行了等比的缩放操作。只有刚体变换这种情况下,顶点的法线向量方向才不会改变,而在非刚体变换时,法线向量的方向会有变化。试想一下,圆球上顶点的法线向量是从球心指向顶点,当将圆球在 y 方向进行缩放而 x,z 方向保持不变,经过这样的非刚体变换之后,这个压扁的“橄榄球”上的顶点的法线向量肯定有一部分不再是从球心指向顶点了。非刚体变换的法线变换矩阵计算公式如下:

非刚体变换的法线变换矩阵 = 模型视图变换矩阵的逆矩阵的倒置矩阵

这个计算过程分为两步:首先对模型视图变换矩阵求逆,然后再倒置(即交换行列元素)。进行刚体变换的模型视图变换矩阵的逆矩阵的倒置矩阵就等于模型视图变换矩阵自身,所以在进行刚体变换时(如本例),只需要将,法线变换矩阵的值设置为模型视图变换矩阵的值即可。

 

三,设置光照

1,访问顶点着色器变量

和教程 6 一样,需要在 OpenGLView 中添加访问顶点着色器中变量的相关槽位变量,以及设置光照参数的变量。在OpenGLView.h 中,添加如下变量:

    GLuint _positionSlot;
    GLuint _modelViewSlot;
    GLuint _projectionSlot;
    GLuint _normalMatrixSlot;
    GLuint _lightPositionSlot;
    
    GLint _normalSlot;
    GLint _ambientSlot;
    GLint _diffuseSlot;
    GLint _specularSlot;
    GLint _shininessSlot;
    
    KSMatrix4 _modelViewMatrix;
    KSMatrix4 _projectionMatrix;
    
    KSVec3 _lightPosition;
    KSColor _ambient;
    KSColor _diffuse;
    KSColor _specular;
    
    GLfloat _shininess;

由于我们需要在 UI 上控制一些光照参数,因此需要将上面中的一些变量声明为属性,以方便 UI 更新它们,这些光照参数在更新之后需要重绘才能立即看到效果:

// OpenGLView.h
//
@property (nonatomic, assign) KSVec3 lightPosition;
@property (nonatomic, assign) KSColor ambient;
@property (nonatomic, assign) KSColor diffuse;
@property (nonatomic, assign) KSColor specular;
@property (nonatomic, assign) GLfloat shininess;

// OpenGLView.m
//
@synthesize lightPosition = _lightPosition;
@synthesize ambient = _ambient;
@synthesize diffuse = _diffuse;
@synthesize specular = _specular;
@synthesize shininess = _shininess;

#pragma mark Properties

-(void)setAmbient:(KSColor)ambient
{
    _ambient = ambient;
    [self render];
}

-(void)setSpecular:(KSColor)specular
{
    _specular = specular;
    [self render];
}

- (void)setLightPosition:(KSVec3)lightPosition
{
    _lightPosition = lightPosition;
    [self render];
}

-(void)setDiffuse:(KSColor)diffuse
{
    _diffuse = diffuse;
    [self render];
}

-(void)setShininess:(GLfloat)shininess
{
    _shininess = shininess;
    [self render];
}

2,访问槽位

在 OpenGLView.m 中添加 getSlotsFromProgram 方法,并在 setupProgram 方法的最后调用它。

- (void)getSlotsFromProgram
{
    // Get the attribute and uniform slot from program
    //
    _projectionSlot = glGetUniformLocation(_programHandle, "projection");
    _modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
    _normalMatrixSlot = glGetUniformLocation(_programHandle, "normalMatrix");
    _lightPositionSlot = glGetUniformLocation(_programHandle, "vLightPosition");
    _ambientSlot = glGetUniformLocation(_programHandle, "vAmbientMaterial");
    _specularSlot = glGetUniformLocation(_programHandle, "vSpecularMaterial");
    _shininessSlot = glGetUniformLocation(_programHandle, "shininess");
    
    _positionSlot = glGetAttribLocation(_programHandle, "vPosition");
    _normalSlot = glGetAttribLocation(_programHandle, "vNormal");
    _diffuseSlot = glGetAttribLocation(_programHandle, "vDiffuseMaterial");
}

3,初始化和更新光照参数

在 OpenGLView.m 中添加 setupLights 和 updateLights 的方法,对光照参数进行初始化和更新光照参数到顶点着色器中:

- (void)setupLights
{                 
    // Initialize various state.
    //
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_normalSlot);
    
    // Set up some default material parameters.
    //
    _lightPosition.x = _lightPosition.y = _lightPosition.z = 1.0;
    
    _ambient.r = _ambient.g = _ambient.b = 0.04;
    _specular.r = _specular.g = _specular.b = 0.5;
    _diffuse.r = 0.0;
    _diffuse.g = 0.5;
    _diffuse.b = 1.0;

    _shininess = 10;
}

- (void)updateLights
{
    glUniform3f(_lightPositionSlot, _lightPosition.x, _lightPosition.y, _lightPosition.z);
    glUniform4f(_ambientSlot, _ambient.r, _ambient.g, _ambient.b, _ambient.a);
    glUniform4f(_specularSlot, _specular.r, _specular.g, _specular.b, _specular.a);
    glVertexAttrib4f(_diffuseSlot, _diffuse.r, _diffuse.g, _diffuse.b, _diffuse.a);
    glUniform1f(_shininessSlot, _shininess);
}

setupLights 方法在 - (id)initWithCoder:(NSCoder *)aDecoder 中 setProjection 之后被调用,而 updateLights 在 updateSurface 的最后被调用,该方法在每次渲染时都会被调用(其实只需要在光照参数有变换时调用即可,在这里偷懒没有做这个优化了)。

前面说过,在顶点着色器中需要利用法线变换矩阵变换法线到视图空间,因此,也需要在程序中设置法线变换矩阵。在这里进行的是刚体变换,所以只需要将模型变换矩阵的值赋值给法线变换矩阵即可。这个赋值是在 updateSurface 方法中进行的,下面是 updateSurface 的完整代码:

- (void)updateSurface
{
    ksMatrixLoadIdentity(&_modelViewMatrix);
    
    ksTranslate(&_modelViewMatrix, 0.0, 0.0, -8);
    
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    
    // Load the model-view matrix
    glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
    
    // Load the normal matrix.
    // It's orthogonal, so its Inverse-Transpose is itself!
    //
    KSMatrix3 normalMatrix3;
    ksMatrix4ToMatrix3(&normalMatrix3, &_modelViewMatrix);
    glUniformMatrix3fv(_normalMatrixSlot, 1, GL_FALSE, (GLfloat*)&normalMatrix3.m[0][0]);
    
    [self updateLights];
}

4,至此,光照设置完成。如果没出什么差错的话,编译应该是能够运行了。效果如下:


在上图中,我们确实可以看到光照的效果了。但是,这样的效果实在太二了!为什么会有不该出现的阴影呢?哪些阴影就是啥?且看下段分解!

 

四,Depth Buffer-深度缓存

1,上面的阴影问题分析

从上面的图中我们可以看到圆球的表面有一部分似乎被一些阴影给遮盖了,没错,确实是这样的。那这些阴影又是从何而来呢?它们也是球体的一部分,只不过是属于另外一个半球的-后半球面上的。在现实生活中,当我们看到一个球时,只能看到一个球的前半球,后半球是被挡住了,看不到。但在 OpenGL 中渲染一个球时,前半球和后半球都会被渲染出来,这样就会出现前后两个半球上的x,y相同只是z值不同两个点会被描绘到屏幕上的同一个像素上,先渲染的点会被后渲染的点给覆盖掉。因此,如果前半球面上的点(Z值较小的那一个)先被渲染,随后后半球面上的点(Z值较大的那一个)被渲染,会覆盖前半球面上的点,因而出现了上面那样的情况。解决这个问题的办法就是在渲染之前,对Z值进行比较,不渲染 Z 值较大的点。在 OpenGL 中,这是通过 Depth Test 实现的,之所以叫做深度测试就是因为比较的是 Z 值 - 深度-从屏幕往里。还记得教程02中渲染管线流程图么?深度测试是在模版测试之后,blending 之前。


2,Depth Buffer 简介

要进行Z值的比较(深度测试),那么 OpenGL 需要有一个地方来保存Z值,这个地方就是 Depth Buffer。Depth Buffer 与 Stencil Buffer,Color Buffer 并称 OpenGL 三大缓存。Depth Buffer 和 Stencil Buffer 也是 render buffer,但与 Color Buffer 不同(RGBA四元组),它们均只有一个组成元素,Depth Buffer 只需要保存Z值,而 Stencil Buffer(模版缓存)也只需要保存一个用于模版测试的值(后面会有文章介绍)。

3,使用 Depth Buffer

既然 Depth Buffer 也是 render buffer,那么其创建与删除与之前在教程01中介绍的 color buffer 别无二样:

// OpenGLView.h
//
GLuint _depthRenderBuffer;

// OpenGLView.m
//

// Create a depth buffer that has the same size as the color buffer.
int width, height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                              GL_RENDERBUFFER, _depthRenderBuffer);

删除也是使用 glDeleteRenderbuffers 方法。

默认情况下,OpenGL 是不会开启深度测试的,因此需要明确调用 glEnable(GL_DEPTH_TEST) 来开启。在 setupProjection 方法的最后添加这一句即可。

4,编译运行,啊哈,一切OK!


在本示例中,有很多滑块用来控制各种光照参数(环境材质,漫反射材质,镜面材质以及光泽强度),并有两个模型可供切换。不妨多多滑动,体验下不同的参数会有什么效果,从而加深对 OpenGL 光照的理解。

 

五,结语

有了前文《光照原理》 的理论基础,今天来实践 Per-Vextex 光照,非常容易吧。接下来将介绍 Per-Pixel 光照以及卡通效果,性急的童鞋可以先浏览源代码,看看能不能看个明白。BTW,教程代码已经非常超前了-写到教程13了,写文章的速度大大落后了。写文章花费的时间和精力实在是不少的,深度体会在中国写一本书的辛苦,而往往回报远不及付出,嗯,要多多支持正版。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值