前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGL ES 2.0 (iOS)[02]:修复三角形的显示

OpenGL ES 2.0 (iOS)[02]:修复三角形的显示

作者头像
半纸渊
发布2018-09-04 17:02:44
1.1K0
发布2018-09-04 17:02:44
举报
文章被收录于专栏:Code_iOSCode_iOS

一、分析拉伸的原因

1、修复前后照片对比

问题与目标

图片通过 sketch 制作

2、从问题到目标,分析原因

1、它们的顶点数据均为:

顶点数组

VFVertex

2、借助 Matlab 把顶点数据绘制出来:

分布图

从图可以看出,这三个数据形成的其实是一个等边直角三角形,而在 iOS 模拟器中通过 OpenGL ES 绘制出来的是直角三角形,所以是有问题的,三角形被拉伸了。

3、on-Screen (屏幕) 的像素分布情况:

  1. iPhone6s Plus 屏幕:5.5寸,1920 x 1080 像素分辨率,明显宽高比不是 1:1 的;
  2. OpenGL ES 的屏幕坐标系 与 物理屏幕的坐标系对比:

OpenGL ES 的屏幕坐标系

物理屏幕的坐标系

分析:前者是正方体,后者长方体,不拉伸才怪。

  1. 首先,OpenGL 最后生成的都是像素信息,再显示在物理屏幕上;通过 1) 和 2) 可以知道 Y 方向的像素数量大于 X 方向的像素数量,导致真实屏幕所生成的 Y 轴与 X 轴的刻度不一致(就是Y=0.5 > X=0.5),从而引起了最后渲染绘制出来的图形是向 Y 方向拉伸了的。

动画演示修复:

FixTriangle.gif

所以要做的事情是,把顶点坐标的 Y 坐标变小,而且是要根据当前显示屏幕的像素比来进行缩小。

Gif 图片,由 C4D 制作,PS 最终导出;

  1. 在 Shader 里面,v_Position 的数据类型是 vec4 ,即为4分量的向量数据{x,y,z,w};就是说,要把这个向量通过数学运算变成适应当前屏幕的向量。

二、准备知识,三维变换

-- 建议 --:如果向量、矩阵知识不熟悉的可以看看《线性代数》一书;如果已经有相应的基础了,可以直接看《3D数学基础:图形与游戏开发》,了解 3D 的世界是如何用向量和矩阵知识描述的;若对 3D 知识有一定的认识,可以直接看《OpenGL Programming Guide》8th 的变换知识, 或 《OpenGL Superblble》7th 的矩阵与变换知识,明确 OpenGL 是如何应用这些知识进行图形渲染的。

注:以下核心知识均来源于,《3D数学基础:图形与游戏开发》,建议看一下第8章;

4x4 整体

图片通过 sketch 制作,请放大看

1、4 x 4 方阵

4X4方阵

    1. 它其实就是一个齐次矩阵,是对3D运算的一种简便记法;
    1. 3x3矩阵并没有包含平移,所以扩展到4x4矩阵,从而可以引入平移的运算;
2、线性变换(缩放与旋转)

线性变换

  • n,是标准化向量,而向量标准化就是指单位化:

normalied

a、 v不能是零向量,即零向量为{0,0,0}; b、||v||是向量的模,即向量的长度; c、例子是2D向量的,3D/4D向量都是一样的 【 sqrt(pow(x,2)+pow(y,2)+pow(w,2)...) 】

图片来源于《3D数学基础:图形与游戏开发》5.7

  • k,是一个常数;
  • a,是一个弧度角;

1) 线性缩放

线性缩放

  • XYZ 方向的缩放:

X方向,就是{1,0,0};Y方向,就是{0,1,0};Z方向,就是{0,0,1};分别代入上面的公式即可得到。

图片来源于《3D数学基础:图形与游戏开发》8.3.1

2) 线性旋转

线性旋转

  • X方向{1,0,0}的旋转:
  • Y方向{0,1,0}的旋转:
  • Z方向{0,0,1}的旋转:

图片来源于《3D数学基础:图形与游戏开发》8.2.2

3、平移

平移

直接把平移向量,按分量{x, y, z}依次代入齐次矩阵即可;

图片来源于《3D数学基础:图形与游戏开发》9.4.2

4、向量(四元数)

四元数

a.向量,即4D向量,也称齐次坐标{x, y, z, w}; 4D->3D,{x/w, y/w, z/w};

b.四元数,[ w, v ]或[ w, (x,y,z) ]两种记法,其中 w 就是一个标量,即一个实数;

c.点乘

矩阵乘法,点乘 c.1 上面两种是合法的,而下面两种是不合法的,就是没有意义的; c.2 第一个为 A(1x3) 行向量(矩阵)与 B(3x3)方阵的点乘,第二个是 A(3x3) 的方阵与 A(3x1) 的列向量(矩阵)的点乘;

图片来源于《3D数学基础:图形与游戏开发》7.1.7

5、w 与 其它

这块内容现在先不深究,不影响对本文内容的理解。

  • W

w

w,与平移向量{x, y, z}组成齐次坐标;一般情况下,都是1;

  • 投影

投影

这里主要是控制投影,如透视投影;如:

图片来源于《3D数学基础:图形与游戏开发》9.4.6


三、OpenGL 下的三维变换

这里主要讨论第一阶段 Vertex 的 3D 变换,对于视图变换、投影变换,不作过多讨论;如果要完全掌握后面两个变换,还需要掌握 OpenGL 下的多坐标系系统,以及摄像机系统的相关知识。

1、OpenGL 的坐标系
  • 坐标系方向定义分两种:

图片来源于,《3D数学基础:图形与游戏开发》8.1;左右手坐标系是用来定义方向的。

  • 旋转的正方向

右手坐标

图片来源于,Diney Bomfim 的《Cameras on OpenGL ES 2.x - The ModelViewProjection Matrix》;这个就是 OpenGL 使用的坐标系,右手坐标系;其中白色小手演示了在各轴上旋转的正方向(黑色箭头所绕方向);

2、OpenGL 的 gl_Position 是行向量还是列向量
  • 这里讨论的核心是,gl_Position 接收的是 行向量,还是列向量?

行向量

列向量

  • 讨论行列向量的目的是明确,3D 矩阵变换在做乘法的时候是使用左乘还是右乘;

图片来源于,《线性代数》矩阵及其运算一节

从图中的结果就可以看出,左乘和右乘运算后是完全不一样的结果;虽然图片中的矩阵是 2 x 2 方阵,但是扩展到 n x n 也是一样的结果;

  • 那么 OpenGL 使用的是什么向量?

图1,列向量

  • 英文大意:矩阵和矩阵乘法在处理坐标系显示模型方面是一个非常有用的途径,而且对于处理线性变换而言也是非常方便的机制。

图2

红框处的向量就是 v_Position 顶点数据;即 OpenGL 用的是列向量;(木有找到更有力的证据,只能这样了)

  • 左乘右乘问题?

图3

  • 英文大意:在我们的视图模型中,我们想通过一个向量来与矩阵变换进行乘法运算,这里描述了一个矩阵乘法,向量先乘以 A 矩阵再乘以 B 矩阵:

很明显,例子使用的就是左乘,即 OpenGL 用的是左乘;

图 1、3 来源于,《OpenGL Programming Guide 8th》第5章第二节 图 2 来源于,《3D数学基础:图形与游戏开发》7.1.8

3、单次三维变换与多次三维变换问题

多次变换

  1. OpenGL 的三维变换整体图:

4x4 整体 OpenGL

因为列向量的影响,在做点乘的时候,平移放在下方与右侧是完全不一样的结果,所以进行了适应性修改

  • 平移部分的内容:

4X4方阵 OpenGL

平移 OpenGL

  • 矩阵平移公式

等式左侧:A(4x4)方阵点乘{v.x, v.y, v.z, 1.0}是顶点数据列向量;右侧就是一个 xyz 均增加一定偏移的列向量

图片来源于,《OpenGL Superblble》7th, Part 1, Chapter 4. Math for 3D Graphics

  • 投影(就是零)

投影 OpenGL

  1. 所有的变换图例演示

物体的坐标是否与屏幕坐标原点重叠

Linaer Transforms

  • 单次变换(原点重叠)

Identity

无变换,即此矩阵与任一向量相乘,不改变向量的所有分量值,能做到这种效果的就是单位矩阵,而我们使用的向量是齐次坐标{x, y, z, w},所以使用 4 x 4 方阵;{w === 1}.

  • 缩放

Scale

单一的线性变换——缩放,缩放变换是作用在蓝色区域的 R(3x3) 方阵的正对角线(从m11(x)->m22(y)->m33(z))中;例子是 X、Y、Z 均放大 3 倍。

  • 旋转

Rotate

单一的线性变换——旋转,旋转变换是作用在蓝色区域的 R(3x3) 方阵中;例子是绕 Z 轴旋转 50 度。

  • 平移

Translation

单一的线性变换——平移,平移变换是作用在绿色区域的 R(3x1) 矩阵中({m11, m21, m31}对应{x, y, z});例子是沿 X 正方向平移 2.5 个单位。

  • 单次变换(原点不重叠)

Translation&Scale

Translation&Rotate

以上图片内容来源于《OpenGL Programming Guide》8th, Linear Transformations and Matrices 一小节,使用 skecth 重新排版并导出

  1. 多次变换

连续变换

这里的问题就是先旋转还是后旋转。旋转前后,变化的是物体的坐标系(虚线(变换后),实线(变换前)),主要是看你要什么效果,而不是去评论它的对错。

图片来源于,《OpenGL Superblble》7th, Matrix Construction and Operators 一节;

4、OpenGL 的变换是在那个阶段发生的,如何发生

3D变换

ES 主要看红框处的顶点着色阶段即可,所以我们的变换代码是写在 Vertex Shader 的文件中。

变换转换

这里描述了三个变换阶段,第一个阶段是模型变换,第二个是视图变换阶段,第三个是投影变换阶段,最后出来的才是变换后的图形。本文讨论的是第一个阶段。

详细过程

作为了解即可

以上图片均来源于,《OpenGL Programming Guide》8th, 5. Viewing Transformations, Clipping, and Feedback 的 User Transformations 一节;


四、修复拉伸问题

1、改写 Shader Code

增加了一个 uniform 变量,而且是 mat4 的矩阵类型,同时左乘于顶点数据;

  • 为什么使用 uniform 变量?
    • 首先, Vertex Shader 的输入量可以是 : attribute、unforms、samplers、temporary 四种;
    • 其次,我们的目的是把每一个顶点都缩小一个倍数,也就是它是一个固定的变量,即常量,所以排除 arrribute、temporary ;
    • 同时,既然是一个常量数据,那么 samplers 可以排除,所以最后使用的是 uniforms 变量;
  • 为什么使用 mat4 类型? v_Position 是{x, y, z, w}的列向量,即为 4 x 1 的矩阵,如果要最终生成 gl_Position 也是 4 x 1 的列向量,那么就要左乘一个 4 x 4 方阵;而 mat4 就是 4 x 4 方阵。

补充:n x m · 4 x 1 -> 4 x 1,如果要出现最终 4 x 1 那么,n 必须要是 4;如果矩阵点乘成立,那么 m 必须要是 4; 所以最终结果是 n x m = 4 x 4 ;

2、应用 3D 变换知识,重新绑定数据

这里主要解决,如何给 uniform 变量赋值,而且在什么时候进行赋值的问题

核心步骤

1、在 glLinkProgram 函数之后,利用 glGetUniformLocation 函数得到 uniform 变量的 location (内存标识符);

2、从 Render Buffer 得到屏幕的像素比(宽:高)值,即为缩小的值;

3、使用 Shader Program , 调用 glUseProgram 函数;

4、使用 3D 变换知识,得到一个缩放矩阵变量 scaleMat4;

5、使用 glUniform* 函数把 scaleMat4 赋值给 uniform 变量;

  • 如何给 uniform 变量赋值?

1、得到 uniform 的内存标识符

要在 glLinkProgram 后,再获取 location 值,因为只有链接后 Program 才会 location 的值

- (BOOL)linkShaderWithProgramID:(GLuint)programID {
    // 绑定 attribute 变量的下标
    // 如果使用了两个或以上个 attribute 一定要绑定属性的下标,不然会找不到数据源的
    // 因为使用了一个的时候,默认访问的就是 0 位置的变量,必然存在的,所以才不会出错
    [self bindShaderAttributeValuesWithShaderProgramID:programID];
    // 链接 Shader 到 Program
    glLinkProgram(programID);
    // 获取 Link 信息
    GLint linkSuccess;
    glGetProgramiv(programID, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLint infoLength;
        glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLength);
        if (infoLength > EmptyMessage) {
            GLchar *messages = malloc(sizeof(GLchar *) * infoLength);
            glGetProgramInfoLog(programID, infoLength, NULL, messages);
            NSString *messageString = [NSString stringWithUTF8String:messages];
            NSLog(@"Error: Link Fail %@ !", messageString);
            free(messages);
        }
        return Failure;
    }
    // 在这里
    [self.shaderCodeAnalyzer updateActiveUniformsLocationsWithShaderFileName:@"VFVertexShader"
                                                                   programID:programID];
    return Successfully;
}
- (void)updateActiveUniformsLocationsWithShaderFileName:(NSString *)fileName programID:(GLuint)programID {
    
    NSDictionary *vertexShaderValueInfos = self.shaderFileValueInfos[fileName];
    ValueInfo_Dict *uniforms = vertexShaderValueInfos[UNIFORM_VALUE_DICT_KEY];
    
    NSArray *keys = [uniforms allKeys];
    for (NSString *uniformName in keys) {
        const GLchar * uniformCharName = [uniformName UTF8String];
        // 在这里
        GLint location = glGetUniformLocation(programID, uniformCharName); 
        VFShaderValueInfo *info = uniforms[uniformName];
        info.location = location;
    }
    
}

补充:

glGetActiveUniform

void glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type, char* name)

program 指 Shader Program 的内存标识符

index 指下标,第几个 uniform 变量,[0, activeUniformCount]

bufSize 所有变量名的字符个数,如:v_Projection , 就有 12 个,如果还定义了 v_Translation 那么就是12 + 13 = 25个

length * NULL 即可*

size 数量,uniform 的数量,如果不是 uniform 数组,就写 1,如果是数组就写数组的长度

type uniform 变量的类型,GL_FLOAT, GL_FLOAT_VEC2,GL_FLOAT_VEC3, GL_FLOAT_VEC4,GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4, GL_BOOL,GL_BOOL_VEC2, GL_BOOL_VEC3, GL_BOOL_VEC4,GL_FLOAT_MAT2, GL_FLOAT_MAT3, GL_FLOAT_MAT4,GL_SAMPLER_2D, GL_SAMPLER_CUBE

name uniform 变量的变量名

// 这个函数可以得到,正在使用的 uniform 个数,即可以知道 index 是从 0 到几;
// 还有可以得到,bufSize 的长度
glGetProgramiv(progObj, GL_ACTIVE_UNIFORMS, &numUniforms);
glGetProgramiv(progObj, GL_ACTIVE_UNIFORM_MAX_LENGTH,
&maxUniformLen);

注:VFShaderValueRexAnalyzer 类就是一个方便进行调用的一种封装而已,你可以使用你喜欢的方式进行封装;

图片来源于,《OpenGL ES 2.0 Programming Guide》4. Shaders and Programs,Uniforms and Attributes 一节

  • 在什么时候进行赋值操作? 一定要在 glUseProgram 后再进行赋值操作,不然无效
- (void)drawTriangle {

    [self.shaderManager useShader];
    [self.vertexManager makeScaleToFitCurrentWindowWithScale:[self.rboManager windowScaleFactor]];
    [self.vertexManager draw];
    [self.renderContext render];
    
}

2、得到屏幕的像素比

- (CGFloat)windowScaleFactor {
    
    CGSize renderSize = [self renderBufferSize];
    float scaleFactor = (renderSize.width / renderSize.height);
    
    return scaleFactor;
    
}

补充:renderBufferSize

- (CGSize)renderBufferSize {
    GLint renderbufferWidth, renderbufferHeight;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &renderbufferWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &renderbufferHeight);
    return CGSizeMake(renderbufferWidth, renderbufferHeight);
}

3、使用 Shader Program

- (void)useShader {
    
    glUseProgram(self.shaderProgramID);
    
}

4、使用 3D 变换知识,得到一个缩放矩阵变量 scaleMat4

 VFMatrix4 scaleMat4 = VFMatrix4MakeScaleY(scale);

扩展1:

    VFMatrix4 VFMatrix4MakeXYZScale(float sx, float sy, float sz) {
        VFMatrix4 r4 = VFMatrix4Identity;
        VFMatrix4 _mat4 = {
              sx  , r4.m12, r4.m13, r4.m14,
            r4.m21,   sy  , r4.m23, r4.m24,
            r4.m31, r4.m32,   sz  , r4.m34,
            r4.m41, r4.m42, r4.m43, r4.m44,
        };
        return _mat4;
    };
    VFMatrix4 VFMatrix4MakeScaleX(float sx) {
        return VFMatrix4MakeXYZScale(sx, 1.f, 1.f);
    };
    VFMatrix4 VFMatrix4MakeScaleY(float sy) {
        return VFMatrix4MakeXYZScale(1.f, sy, 1.f);
    };
    VFMatrix4 VFMatrix4MakeScaleZ(float sz) {
        return VFMatrix4MakeXYZScale(1.f, 1.f, sz);
    };

它们都定义在:

VFMath

注:如果不想自己去写这些函数,那么可以直接使用 GLKit 提供的

数学函数

个人建议,自己去尝试写一下会更好

5、使用 glUniform 函数把 scaleMat4 赋值给 uniform 变量*

- (void)makeScaleToFitCurrentWindowWithScale:(float)scale {
    
    NSDictionary *vertexShaderValueInfos = self.shaderCodeAnalyzer.shaderFileValueInfos[@"VFVertexShader"];
    ValueInfo_Dict *uniforms = vertexShaderValueInfos[UNIFORM_VALUE_DICT_KEY];
//    NSLog(@"uniforms %@", [uniforms allKeys]);
    
    // v_Projection 投影
//    VFMatrix4 scaleMat4 = VFMatrix4Identity;
    VFMatrix4 scaleMat4 = VFMatrix4MakeScaleY(scale);
    VFMatrix4 transMat4 = VFMatrix4Identity; //VFMatrix4MakeTranslationX(0.3)
    glUniformMatrix4fv((GLint)uniforms[@"v_Projection"].location,   // 定义的 uniform 变量的内存标识符
                       1,                                           // 不是 uniform 数组,只是一个 uniform -> 1
                       GL_FALSE,                                    // ES 下 只能是 False
                       (const GLfloat *)scaleMat4.m1D);             // 数据的首指针
    
    glUniformMatrix4fv((GLint)uniforms[@"v_Translation"].location,   // 定义的 uniform 变量的内存标识符
                       1,                                           // 不是 uniform 数组,只是一个 uniform -> 1
                       GL_FALSE,                                    // ES 下 只能是 False
                       (const GLfloat *)transMat4.m1D);             // 数据的首指针

}

扩展2:

  • 赋值函数有那些? 它们分别是针对不同的 uniform 变量进行的赋值函数

3、完整工程:Github: DrawTriangle_Fix

glsl 代码分析类

核心的知识是正则表达式,主要是把代码中的变量解析出来,可以对它们做大规模的处理。有兴趣可以看一下,没有兴趣的可以忽略它完全不影响学习和练习本文的内容。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.11.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、分析拉伸的原因
    • 1、修复前后照片对比
      • 2、从问题到目标,分析原因
      • 二、准备知识,三维变换
        • 1、4 x 4 方阵
          • 2、线性变换(缩放与旋转)
            • 3、平移
              • 4、向量(四元数)
                • 5、w 与 其它
                  • 三、OpenGL 下的三维变换
                    • 1、OpenGL 的坐标系
                      • 2、OpenGL 的 gl_Position 是行向量还是列向量
                        • 3、单次三维变换与多次三维变换问题
                          • 4、OpenGL 的变换是在那个阶段发生的,如何发生
                          • 四、修复拉伸问题
                            • 1、改写 Shader Code
                              • 2、应用 3D 变换知识,重新绑定数据
                              相关产品与服务
                              图像处理
                              图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档