美文网首页
(四) 了解GLSL

(四) 了解GLSL

作者: SunnyDay_ab5f | 来源:发表于2019-11-25 15:44 被阅读0次

    glsl介绍
    https://blog.csdn.net/u010029439/article/details/86682490
    https://www.cnblogs.com/renhui/p/8126121.html

    光照计算
    https://blog.csdn.net/weixin_33712987/article/details/93182313
    https://juejin.im/post/5d09f5cf6fb9a07eaa227fd3

    image.png

    冯氏光照模型

    首先,在现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)光照、漫反射(Diffuse)光照和镜面(Specular)光照。 下面我们用示例图来看一下这几种光照:

    [图片上传失败...(image-495656-1568275917656)]

    <figcaption></figcaption>

    环境(Ambient)光照:即使在最黑暗的正常环境下,世界上依然会存在一些光亮,也就是说物体几乎永远不会是完全黑暗的。环境光照一般是由室外的太阳通过各种折射来让我们看到,这样的光是没有任何起点,没有方向的光。主要通过设置物体颜色来获取。 漫反射(Diffuse)光照:模拟的是光源对物体方向的影响,一个物体面向光源的时候,朝向光的那个面会亮一点,物体背面和其他面会暗一点。 镜面(Specular)光照:模拟的是有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

    光照基础

    OpenGL在处理光照时把光照系统分为三部分:分别是光源、材质和光照环境。光源就是光的来源,可以是前面所说的太阳或者电灯,蜡烛等。材质是指接受光照的各种物体的表面,由于物体如何反射光线只由物体表面决定,材质特点就决定了物体反射光线的特点。光照环境是指一些额外的参数,它们将影响最终的光照画面,比如一些光线经过多次反射后,已经无法分清它究竟是由哪个光源发出,这时,指定一个环境亮度参数,可以使最后形成的画面更接近于真实情况。

    在物理学中,光线如果射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。光线如果射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。现实生活中的物体在反射光线时,并不是绝对的镜面反射或漫反射,但可以看成是这两种反射的叠加。对于光源发出的光线,可以分别设置其经过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也可以分别设置光线经过镜面反射和漫反射后的光线强度。这些因素综合起来,就形成了最终的光照效果。

    光照特性 1.发射光:由物体自身发光 2.环境光:就是在环境中充分散射的光,而且无法分辨光的方向 3.漫反射光:光线来自某个方向,但是在物体上各个方向反射 4.镜面高光:光线来自一个特定的方向,然后在物体表面上以一个特定的方向反射出去

    材质属性 1.泛射材质:光线直射,反射率较高 2.漫反射材质:需要考虑光的入射角和反射角的 3.镜面反射材质:斑点 4.发射材质:物体本身就可以发光的材质

    光照计算

    1.环境光的计算

    环境光是不来自任何特定方向的光,在整个场景中经典光照模型把它当成一个常量,组成一个合适的第一近似值来缩放场景中的光照部分。 环境光 = 光源的环境光颜色 * 物体的材质颜色

    [图片上传失败...(image-84d3-1568275917656)]

    <figcaption></figcaption>

    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.漫反射光的计算

    漫反射光是散射在各个方向上的均匀的表面特定光源,漫反射光依赖于表面法线方向和光源方向来计算,但是没有包含视线方向,它同样依赖于表面的颜色。 首先来看一下环境光和漫反射光的比较:

    [图片上传失败...(image-11e428-1568275917656)]

    <figcaption></figcaption>

    可以看到环境光下的苹果是没有阴面和阳面的,看到的苹果感觉不够逼真,而漫反射光加深了苹果的真实度,模拟出了在现实生活中的真实环境。[图片上传失败...(image-a6c133-1568275917656)]

    <figcaption></figcaption>

    上图是当漫反射光照射到物体表面时:其中N表示法向量,L表示光源,法向量N和光源L之间的夹角决定了光照射的面积。夹角越大照射面积越大。

    光线照射到物体表面,决定了物体表面的光照强度,光照强度是光本身强度和光线与物体表面法线夹角cos的乘积

    [图片上传失败...(image-e91fc8-1568275917656)]

    <figcaption></figcaption>

    有效光的光照方向是与物体表面法线夹角在0~90度之间的。

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

    [图片上传失败...(image-816880-1568275917656)]

    <figcaption></figcaption>

    漫反射因子DiffuseFactor 是光线与顶点法线向量的点积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.镜面光照计算 镜面光是由表面直接反射的高亮光,这个高亮光就像镜子一样跟表面材质多少有关。

    [图片上传失败...(image-4569f7-1568275917656)]

    <figcaption></figcaption>

    其中:N表示平面法线,R表示反射光线,@表示视点与反射光的夹角

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

    镜面反射因子SpecularFactor = power(max(0,dot(N,R)),shininess)

    dot(N,R):H,R的点积几何意义:平⽅线与法线夹角的cos值

    shiniess : ⾼光的反光度/发光值;(值越大反射度越强)

    一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。在下面的图片里,你会看到不同发光值对视觉(效果)的影响:

    [图片上传失败...(image-e62a0a-1568275917656)]

    <figcaption></figcaption>

    一般我们不希望镜面成分过于显眼,所以我们通常把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;
    复制代码
    

    我们都知道光的传播是会衰减的,所以光照颜色的公式可总结为:

    光照颜色 =(环境颜色 + 漫反射颜色 + 镜⾯反射颜色)* 衰减因子

    [图片上传失败...(image-2ba7e7-1568275917656)]

    <figcaption></figcaption>

    衰减因子 = 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(单位光源位置,单位光线向量)),聚光灯指数);

    单位光线向量是从光源指向顶点的单位向量 聚光灯指数,表示聚光灯的亮度程度 公式解读:单位光源位置 * 单位光线向量点积的聚光灯指数次⽅。

    [图片上传失败...(image-9e2258-1568275917656)]

    <figcaption></figcaption>

    聚光灯因子 = 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绘制金字塔案例来看一下光照的使用:

    [图片上传失败...(image-a74ae-1568275917655)]

    <figcaption></figcaption>

    如果把效果图里的三角形的顶点全部画到平面上来,如下图所示:

    [图片上传失败...(image-9449dc-1568275917655)]

    <figcaption></figcaption>

    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];
    }
    复制代码
    

    在实际的开发中,我们在绘图时是不会把法线绘制在图形中的,所以绘制法线部分代码可以忽略。

    [图片上传失败...(image-e0d3c1-1568275917655)]

    作者:Henry_Jeannie
    链接:https://juejin.im/post/5d09f5cf6fb9a07eaa227fd3
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:(四) 了解GLSL

          本文链接:https://www.haomeiwen.com/subject/eyvdyctx.html