内容简介:首先,在现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)光照、漫反射(Diffuse)光照和镜面(Specular)光照。 下面我们用示例图来看一下这几种光照::模拟的是有光泽物体上面出现的亮点。镜
首先,在现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(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入门(四):从入门到“跑路”
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Music Recommendation and Discovery
Òscar Celma / Springer / 2010-9-7 / USD 49.95
With so much more music available these days, traditional ways of finding music have diminished. Today radio shows are often programmed by large corporations that create playlists drawn from a limited......一起来看看 《Music Recommendation and Discovery》 这本书的介绍吧!