内容简介:首先,在现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)光照、漫反射(Diffuse)光照和镜面(Specular)光照。 下面我们用示例图来看一下这几种光照::模拟的是有光泽物体上面出现的亮点。镜
首先,在现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)光照、漫反射(Diffuse)光照和镜面(Specular)光照。 下面我们用示例图来看一下这几种光照:
环境(Ambient)光照 :即使在最黑暗的正常环境下,世界上依然会存在一些光亮,也就是说物体几乎永远不会是完全黑暗的。环境光照一般是由室外的太阳通过各种折射来让我们看到,这样的光是没有任何起点,没有方向的光。主要通过设置物体颜色来获取。 漫反射(Diffuse)光照 :模拟的是光源对物体方向的影响,一个物体面向光源的时候,朝向光的那个面会亮一点,物体背面和其他面会暗一点。 镜面(Specular)光照:模拟的是有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
光照基础
OpenGL在处理光照时把光照系统分为三部分:分别是光源、材质和光照环境。光源就是光的来源,可以是前面所说的太阳或者电灯,蜡烛等。材质是指接受光照的各种物体的表面,由于物体如何反射光线只由物体表面决定,材质特点就决定了物体反射光线的特点。光照环境是指一些额外的参数,它们将影响最终的光照画面,比如一些光线经过多次反射后,已经无法分清它究竟是由哪个光源发出,这时,指定一个环境亮度参数,可以使最后形成的画面更接近于真实情况。
在物理学中,光线如果射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。光线如果射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。现实生活中的物体在反射光线时,并不是绝对的镜面反射或漫反射,但可以看成是这两种反射的叠加。对于光源发出的光线,可以分别设置其经过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也可以分别设置光线经过镜面反射和漫反射后的光线强度。这些因素综合起来,就形成了最终的光照效果。
光照特性1.发射光:由物体自身发光 2.环境光:就是在环境中充分散射的光,而且无法分辨光的方向 3.漫反射光:光线来自某个方向,但是在物体上各个方向反射 4.镜面高光:光线来自一个特定的方向,然后在物体表面上以一个特定的方向反射出去
材质属性1.泛射材质:光线直射,反射率较高 2.漫反射材质:需要考虑光的入射角和反射角的 3.镜面反射材质:斑点 4.发射材质:物体本身就可以发光的材质
光照计算
1.环境光的计算
环境光是不来自任何特定方向的光,在整个场景中经典光照模型把它当成一个常量,组成一个合适的第一近似值来缩放场景中的光照部分。 环境光 = 光源的环境光颜色 * 物体的材质颜色
varying vec3 objectColor; void main() { //⾄至少有%10的光找到物体所有⾯面 float ambientStrength = 0.1; //环境光颜⾊色 vec3 ambient = ambientStrength * lightColor; //最终颜⾊色 = 环境光颜⾊色 * 物体颜⾊色 vec3 result = ambient * objectColor; gl_FragColor = vec4(result, 1.0); } 复制代码
2.发射光的计算
如果这个物体本身就是有颜色的,比如说夜明珠,那么这个时候这个光就是这个物体材质的颜色 发射颜色 = 物体的反射材质颜色
3.漫反射光的计算
漫反射光是散射在各个方向上的均匀的表面特定光源,漫反射光依赖于表面法线方向和光源方向来计算,但是没有包含视线方向,它同样依赖于表面的颜色。 首先来看一下环境光和漫反射光的比较:
可以看到环境光下的苹果是没有阴面和阳面的,看到的苹果感觉不够逼真,而漫反射光加深了苹果的真实度,模拟出了在现实生活中的真实环境。
上图是当漫反射光照射到物体表面时:其中N表示法向量,L表示光源,法向量N和光源L之间的夹角决定了光照射的面积。夹角越大照射面积越大。
光线照射到物体表面,决定了物体表面的光照强度,光照强度是光本身强度和光线与物体表面法线夹角cos的乘积
有效光的光照方向是与物体表面法线夹角在0~90度之间的。
漫反射颜色 = 光源的漫反射光颜色 × 物体的漫反射材质颜色 × 漫反射因子
DiffuseFactor = max(0, dot(N, L))
uniform vec3 lightColor; //光源色 uniform vec3 lightPo; //光源位置 uniform vec3 objectColor; //物体⾊色 uniform vec3 viewPo; //物体位置 varying vec3 outNormal; //传⼊当前顶点平面的法向量 //确保法线为单位向量量 vec3 norm = normalize(outNormal); //顶点指向光源 单位向量量 vec3 lightDir = normalize(lightPo - FragPo); //得到两向量量的cos值 ⼩小于0则则为0 float diff = max(dot(norm, lightDir),0.0); //得到漫反射收的光源向量量 vec3 diffuse = diff * lightColor; vec3 result = diffuse * ojbectColor; gl_FragColor = vec4(result,1.0); 复制代码
4.镜面光照计算 镜面光是由表面直接反射的高亮光,这个高亮光就像镜子一样跟表面材质多少有关。
其中:N表示平面法线,R表示反射光线,@表示视点与反射光的夹角
镜面反射颜色 = 光源的镜面光颜色 × 物体的镜面材质颜色 × 镜面反射因子
镜面反射因子 SpecularFactor = power(max(0,dot(N,R)),shininess)
dot(N,R):H,R的点积几何意义:平⽅线与法线夹角的cos值
shiniess : ⾼光的反光度/发光值;(值越大反射度越强)
一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。在下面的图片里,你会看到不同发光值对视觉(效果)的影响:
一般我们不希望镜面成分过于显眼,所以我们通常把shiniess指数设置为32.
镜面光的GLSL实现代码:
//镜⾯面强度 float specularStrength = 0.5; //顶点指向观察点的单位向量量 vec3 viewDir = normalize(viewPo - FragPo); //求得光线 在 顶点的反射线(传⼊入光源指向顶点的向量量) vec3 reflectDir = reflect(-lightDir ,outNormal); // 求得夹⻆角cos值 取256次幂 注意 pow(float,float)函数参数类型 float spec = pow(max(dot(viewDir, reflectDir),0.0),256.0); vec3 specular = specularStrength * spec * lightColor; 复制代码
我们都知道光的传播是会衰减的,所以光照颜色的公式可总结为:
光照颜色 =(环境颜色 + 漫反射颜色 + 镜⾯反射颜色)* 衰减因子
衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + ⼆次衰减常量 * 距离的平⽅)
//距离衰减常量量 float constantPara = 1.0f; //线性衰减常量量 float linearPara = 0.09f; //⼆二次衰减因⼦子 float quadraticPara = 0.032f; //距离 float LFDistance = length(lightPo - FragPo); //衰减因⼦子 float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance)); 复制代码
距离衰减常量,线性衰减常量和⼆次衰减常量均为常量值.
环境光,漫反射光和镜面光的强度都会受距离的增大⽽衰减,只有发射光和全局环境光的强度不会受影响.
聚光灯夹角cos值 = power(max(0,dot(单位光源位置,单位光线向量)),聚光灯指数);
单位光线向量是从光源指向顶点的单位向量 聚光灯指数,表示聚光灯的亮度程度 公式解读:单位光源位置 * 单位光线向量点积的聚光灯指数次⽅。
聚光灯因子 = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值)/ (外环的聚光灯角度cos值- 内环聚光灯的角度的cos值),0,1);
//聚光灯过渡计算 //(⼀些复杂的计算操作 应该让CPU做,提⾼效率,不变的量也建议外部传输,避免重复计算) //内锥角cos值 float inCutOff = cos(radians(10.0f)); //外锥角cos值 float outCutOff = cos(radians(15.0f)); //聚光朝向 vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f); //光源指向物体的向量和聚光朝向的 cos值 float theta = dot(lightDir ,normalize(-spotDir)); //内外锥⻆角cos差值 float epsilon = inCutOff - outCutOff; //clamp(a,b,c);若b<a<c 则函数返回值为a //若不是,则返回值最小为b ,最大为c // (theta - outCutOff)/epsilon 若theta的角度⼩于内锥角 则其值 >=1 //若theta的⻆度大于外锥角,则其值<=0 这样光线就在内外锥角之间平滑变化. float intensity = clamp((theta - outCutOff)/epsilon, 0.0,1.0) 复制代码
光照颜色的最终公式为:
光照颜色 = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜色 + 镜⾯反射颜色) * 聚光灯效果 * 衰减因子
下面通过一个GLKit绘制金字塔案例来看一下光照的使用:
如果把效果图里的三角形的顶点全部画到平面上来,如下图所示:
1.设置OpenGL ES
@property(nonatomic,strong)EAGLContext *mContext; //基本Effect 绘图 @property(nonatomic,strong)GLKBaseEffect *baseEffect; //额外Effect 辅助线段 @property(nonatomic,strong)GLKBaseEffect *extraEffect; //顶点缓存区 (顶点,颜色,纹理, 法线...) @property(nonatomic,strong)AGLKVertexAttribArrayBuffer *vertexBuffer; //法线位置缓存区(法线辅助线段也有顶点) @property(nonatomic,strong)AGLKVertexAttribArrayBuffer *extraBuffer; //是否绘制法线 @property(nonatomic,assign)BOOL shouldDrawNormals; //中心点的高 默认在(0,0,0) @property(nonatomic,assign) GLfloat centexVertexHeight; { //三角形-8面 SceneTriangle triangles[NUM_FACES]; } 复制代码
设置GLKitView并设置上下文
//1.新建OpenGL ES 上下文 self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2]; //2.设置GLKView GLKView *view = (GLKView *)self.view; view.context = self.mContext; view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; view.drawableDepthFormat = GLKViewDrawableDepthFormat24; [EAGLContext setCurrentContext:self.mContext]; 复制代码
2. 设置金字塔Effect
//1.金字塔Effect self.baseEffect = [[GLKBaseEffect alloc]init]; self.baseEffect.light0.enabled = GL_TRUE; //光的漫射部分 GLKVector4Make(R,G,B,A) self.baseEffect.light0.diffuseColor = GLKVector4Make(0.7f, 0.7f, 0.7, 1.0f); //世界坐标中的光的位置。 self.baseEffect.light0.position = GLKVector4Make(1.0f, 1.0f, 0.5f, 0.0f); //2.法线Effect self.extraEffect = [[GLKBaseEffect alloc]init]; self.extraEffect.useConstantColor = GL_TRUE; //3.调整模型矩阵,更好的观察 //可以尝试不执行这段代码,改为false if (true) { //围绕x轴旋转-60度 //返回一个4x4矩阵进行绕任意矢量旋转 GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-60.0f), 1.0f, 0.0f, 0.0f); //围绕z轴,旋转-30度 modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix,GLKMathDegreesToRadians(-30.0f), 0.0f, 0.0f, 1.0f); //围绕Z方向,移动0.25f modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, 0.0f, 0.0f, 0.25f); //设置baseEffect,extraEffect 模型矩阵 self.baseEffect.transform.modelviewMatrix = modelViewMatrix; self.extraEffect.transform.modelviewMatrix = modelViewMatrix; } 复制代码
3.设置顶点
//确定图形的8个面 triangles[0] = SceneTriangleMake(vertexA, vertexB, vertexD); triangles[1] = SceneTriangleMake(vertexB, vertexC, vertexF); triangles[2] = SceneTriangleMake(vertexD, vertexB, vertexE); triangles[3] = SceneTriangleMake(vertexE, vertexB, vertexF); triangles[4] = SceneTriangleMake(vertexD, vertexE, vertexH); triangles[5] = SceneTriangleMake(vertexE, vertexF, vertexH); triangles[6] = SceneTriangleMake(vertexG, vertexD, vertexH); triangles[7] = SceneTriangleMake(vertexH, vertexF, vertexI); //初始化缓存区 self.vertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles usage:GL_DYNAMIC_DRAW]; self.extraBuffer = [[AGLKVertexAttribArrayBuffer alloc]initWithAttribStride:sizeof(SceneVertex) numberOfVertices:0 bytes:NULL usage:GL_DYNAMIC_DRAW]; self.centexVertexHeight = 0.0f; 复制代码
4.开始绘制
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect { //1. glClearColor(0.3f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); //2. [self.baseEffect prepareToDraw]; //准备绘制顶点数据 [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:offsetof(SceneVertex,position)shouldEnable:YES]; //准备绘制光照数据 [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribNormal numberOfCoordinates:3 attribOffset:offsetof(SceneVertex, normal) shouldEnable:YES]; [self.vertexBuffer drawArrayWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:sizeof(triangles)/sizeof(SceneVertex)]; //3.是否要绘制光照法线 if (self.shouldDrawNormals) { [self drawNormals]; } } 复制代码
绘制法线
//绘制法线 -(void)drawNormals { GLKVector3 normalLineVertices[NUM_LINE_VERTS]; //1.以每个顶点的坐标为起点,顶点坐标加上法向量的偏移值作为终点,更新法线显示数组 //参数1: 三角形数组 //参数2:光源位置 //参数3:法线显示的顶点数组 SceneTrianglesNormalLinesUpdate(triangles, GLKVector3MakeWithArray(self.baseEffect.light0.position.v), normalLineVertices); //2.为extraBuffer重新开辟空间 [self.extraBuffer reinitWithAttribStride:sizeof(GLKVector3) numberOfVertices:NUM_LINE_VERTS bytes:normalLineVertices]; //3.准备绘制数据 [self.extraBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition numberOfCoordinates:3 attribOffset:0 shouldEnable:YES]; //4.修改extraEffect //法线 /* 指示是否使用常量颜色的布尔值。 如果该值设置为gl_true,然后存储在设置属性的值为每个顶点的颜色值。如果该值设置为gl_false,那么你的应用将使glkvertexattribcolor属性提供每顶点颜色数据。默认值是gl_false。 */ self.extraEffect.useConstantColor = GL_TRUE; //设置光源颜色为绿色,画顶点法线 self.extraEffect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f); //准备绘制-绿色的法线 [self.extraEffect prepareToDraw]; //绘制线段 [self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:0 numberOfVertices:NUM_NORMAL_LINE_VERTS]; //设置光源颜色为黄色,并且画光源线 //Red+Green =Yellow self.extraEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 0.0f, 1.0f); //准备绘制-黄色的光源方向线 [self.extraEffect prepareToDraw]; //(NUM_LINE_VERTS - NUM_NORMAL_LINE_VERTS) = 2 .2点确定一条线 [self.extraBuffer drawArrayWithMode:GL_LINES startVertexIndex:NUM_NORMAL_LINE_VERTS numberOfVertices:2]; } //更新法向量 -(void)updateNormals { //更新每个点的平面法向量 SceneTrianglesUpdateFaceNormals(triangles); [self.vertexBuffer reinitWithAttribStride:sizeof(SceneVertex) numberOfVertices:sizeof(triangles)/sizeof(SceneVertex) bytes:triangles]; } 复制代码
更新中心顶点
- (IBAction)changeCenterVertexHeight:(UISlider *)sender { self.centexVertexHeight = sender.value; } -(void)setCentexVertexHeight:(GLfloat)centexVertexHeight { _centexVertexHeight = centexVertexHeight; //更新顶点 E SceneVertex newVertexE = vertexE; newVertexE.position.z = _centexVertexHeight; triangles[2] = SceneTriangleMake(vertexD, vertexB, newVertexE); triangles[3] = SceneTriangleMake(newVertexE, vertexB, vertexF); triangles[4] = SceneTriangleMake(vertexD, newVertexE, vertexH); triangles[5] = SceneTriangleMake(newVertexE, vertexF, vertexH); //更新法线 [self updateNormals]; } 复制代码
在实际的开发中,我们在绘图时是不会把法线绘制在图形中的,所以绘制法线部分代码可以忽略。
以上所述就是小编给大家介绍的《OpenGL ES 入门之旅 -- GLSL光照计算》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- OpenGL 光照系列之环境光
- Unity的Enlighten全局光照实现
- OpenGL(十一) 可编程管线 基础光照 的实现
- 改进的阴影抑制用于光照鲁棒的人脸识别
- CSharpGL(54)用基于图像的光照(IBL)来计算PBR的Specular部分
- TiDB入门(四):从入门到“跑路”
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
UNIX编程环境
Brian W.Kernighan、Rob Pike / 陈向群/等 / 机械工业出版社 / 1999-10-1 / 24.00
本书对UNIX操作系统的编程环境做了详细而深入的讨论,内容包括UNIX的文件系统、Shell、过滤程序、I/O编程、系统调用等,并对UNIX中的程序开发方法做了有针对性的指导。本书内容深入浅出,实例丰富,无论是UNIX系统的初学者还是专业人员都可从本书受益。本书亦可作为大学生、研究生学习UNIX的教材。一起来看看 《UNIX编程环境》 这本书的介绍吧!