美文网首页程序员日常
OpenGLES-纹理的初步认识

OpenGLES-纹理的初步认识

作者: 听风轻咛 | 来源:发表于2018-05-03 20:47 被阅读1次

    Title: OpenGLES-纹理的初步认识
    Date: 2016-06-04 23:51
    Modified: 2016-06-04 23:51
    Category: OpenGL/ES
    Tags: opengles
    Slug: opgles_texture
    Authors: lizhonghuan
    Summary: 上周看书学习OpenGLES关于纹理的知识,云里雾里的,看完的感觉是好多东西都明白,涉及到许多opengl的内容,不过照着书里的内容运行了几个Demo,大致算是有些总结,现做一下回顾,上几个例子。

    上周看书学习OpenGLES关于纹理的知识,云里雾里的,看完的感觉是好多东西都明白,涉及到许多opengl的内容,不过照着书里的内容运行了几个Demo,大致算是有些总结,现做一下回顾,上几个例子。不过这里有一点,这本书看完下一步还是要细研究一下opengl,初步决定从《计算机图形学》这本书入手。好了,之后的事情不多说,先做好当下的事情。

    Demo:渲染图片Demo代码

    首先是关于纹理的一些概念:

    纹理是什么?纹理是一个用来保存图像的元素值的OGE缓存,就是颜色缓存;

    当用一个图像初始化一个纹理缓存后,每一个像素就变成了纹理中的纹素(texel);

    像素通常表示屏幕上的一个实际的颜色点,纹素是在一个虚拟的坐标系中;

    GPU会转换OGE坐标系中的每个点为帧缓存中的真实像素坐标(视口viewport坐标);

    转换几何形状数据为帧缓存中的颜色像素的过程叫做点阵化(rasterizing),每个颜色像素叫做片元(fragment);

    纹素决定片元的对齐过程,叫做映射(mapping);

    取样(sampling)是GPU从每个片元的U、V位置选择纹素的过程;

    MIP贴图是为一个纹理存储多个细节的技术,它通过减少GPU的取样来提高渲染的性能;

    下面的例子展示了使用一个图片渲染的图片缓存:

    GLKViewController中,设置OGE的上下文:

        GLKView *view = (GLKView *)self.view;
        view.backgroundColor = [UIColor whiteColor];
       NSAssert([view isKindOfClass:[GLKView class]],
          @"View controller's view is not a GLKView");
       
       view.context = [[AGLKContext alloc] 
          initWithAPI:kEAGLRenderingAPIOpenGLES2];
       
       // Make the new context current
       [AGLKContext setCurrentContext:view.context];
    

    上面的AGLKContextEAGLContext的子类,setCurrentContext:是继承自父类的方法,设备当前上下文;

    设置提供基础功能:

    // Create a base effect that provides standard OpenGL ES 2.0
       // shading language programs and set constants to be used for 
       // all subsequent rendering
       self.baseEffect = [[GLKBaseEffect alloc] init];
       self.baseEffect.useConstantColor = GL_TRUE;
       self.baseEffect.constantColor = GLKVector4Make(
          1.0f, // Red
          1.0f, // Green
          1.0f, // Blue
          1.0f);// Alpha
    

    上面的这个constantColor,说是顶点缓冲,这里可能是顶点的颜色缓存,具体应该是与opengl相关的东西,这里这么用着,我改过这里的值,会修改渲染出来图的背景色,暂且认为是渲染用的一个底色吧;

    设置当前上下文的“清除颜色”:

    // Set the background color stored in the current context
    GLKVector4 clearColorRGBA = GLKVector4Make(
          1.0f, // Red
          1.0f, // Green
          1.0f, // Blue
          1.0f);// Alpha 
    glClearColor(
          clearColorRGBA.r, 
          clearColorRGBA.g, 
          clearColorRGBA.b, 
          clearColorRGBA.a); 
    

    接下来要进行渲染图片了,大致与渲染一个三角形的过程差不多,可对照开始OpenGLES的探索,同样也是6步:

    这里我们自定义了一个AGLKVertexAttribArrayBuffer封装了关于一些OGE的操作,来说明一下:

    // This data type is used to store information for each vertex
    //这个类型用来保存每个顶点的信息
     typedef struct {
       GLKVector3  positionCoords;
       GLKVector2  textureCoords;
    }SceneVertex;
    
    // Define    data for a triangle to use in example
    //定义三角形顶点
    static const SceneVertex vertices[] = 
    {
       {{-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f}}, // lower left corner
       {{ 0.5f, -0.5f, 0.0f}, {1.0f, 0.0f}}, // lower right corner
       {{-0.5f,  0.5f, 0.0f}, {0.0f, 1.0f}}, // upper left corner
    };
    
    // Create vertex buffer containing vertices to draw
    //这里是定义渲染区域
       self.vertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]
          initWithAttribStride:sizeof(SceneVertex)
          numberOfVertices:sizeof(vertices) / sizeof(SceneVertex)
          bytes:vertices
          usage:GL_STATIC_DRAW];
    
    self.baseEffect.texture2d0.name = textureInfo.name;
    self.baseEffect.texture2d0.target = textureInfo.target;
    
    

    下面是init方法:

    // This method creates a vertex attribute array buffer in
    // the current OpenGL ES context for the thread upon which this 
    // method is called.
    - (id)initWithAttribStride:(GLsizei)aStride
       numberOfVertices:(GLsizei)count
       bytes:(const GLvoid *)dataPtr
       usage:(GLenum)usage;
    {
       NSParameterAssert(0 < aStride);
       NSAssert((0 < count && NULL != dataPtr) ||
          (0 == count && NULL == dataPtr),
          @"data must not be NULL or count > 0");
          
       if(nil != (self = [super init]))
       {
          stride = aStride;
          bufferSizeBytes = stride * count;
          
          glGenBuffers(1,                // STEP 1
             &name);
          glBindBuffer(GL_ARRAY_BUFFER,  // STEP 2
             self.name); 
          glBufferData(                  // STEP 3
             GL_ARRAY_BUFFER,  // Initialize buffer contents
             bufferSizeBytes,  // Number of bytes to copy
             dataPtr,          // Address of bytes to copy
             usage);           // Hint: cache in GPU memory
             
          NSAssert(0 != name, @"Failed to generate name");
       }
       
       return self;
    }   
    

    从这里可以看到图形渲染的前三步,生成缓存glGenBuffers,绑定缓存glBindBuffer,复制数据到缓存glBufferData

    下面我们来进入今天的重点:

       // Setup texture
       CGImageRef imageRef = 
          [[UIImage imageNamed:@"leaves.gif"] CGImage];
          
       AGLKTextureInfo *textureInfo = [AGLKTextureLoader 
          textureWithCGImage:imageRef 
          options:nil 
          error:NULL];
       
       self.baseEffect.texture2d0.name = textureInfo.name;
       self.baseEffect.texture2d0.target = textureInfo.target;
    
    

    textureWithCGImage:实现具体细节:

    // This method generates a new OpenGL ES texture buffer and 
    // initializes the buffer contents using pixel data from the 
    // specified Core Graphics image, cgImage. This method returns an
    // immutable AGLKTextureInfo instance initialized with 
    // information about the newly generated texture buffer.
    //    The generated texture buffer has power of 2 dimensions. The
    // provided image data is scaled (re-sampled) by Core Graphics as
    // necessary to fit within the generated texture buffer.
    
    + (AGLKTextureInfo *)textureWithCGImage:(CGImageRef)cgImage
                                    options:(NSDictionary *)options
       error:(NSError **)outError;
    {
       // Get the bytes to be used when copying data into new texture
       // buffer
       size_t width;
       size_t height;
       NSData *imageData = AGLKDataWithResizedCGImageBytes(
          cgImage,
          &width,
          &height);
       
       // Generation, bind, and copy data into a new texture buffer
       GLuint      textureBufferID;
       
       glGenTextures(1, &textureBufferID);                  // Step 1
       glBindTexture(GL_TEXTURE_2D, textureBufferID);       // Step 2
       
        //该函数是OGE最复杂的函数
       glTexImage2D(                                        // Step 3
          GL_TEXTURE_2D, //用于2D纹理
          0, //指定MIP帖图的初始细节级别
          GL_RGBA, //指定每个纹素需要保存信息的数量
          (GLuint)width,//
          (GLuint)height,
          0, //围绕纹素的边界的大小,总是被设置为0
          GL_RGBA, 
          GL_UNSIGNED_BYTE, 
          [imageData bytes]);
       
       // Set parameters that control texture sampling for the bound
       // texture
      glTexParameteri(GL_TEXTURE_2D,
         GL_TEXTURE_MIN_FILTER, 
         GL_LINEAR); 
       
       // Allocate and initialize the AGLKTextureInfo instance to be
       // returned
       AGLKTextureInfo *result = [[AGLKTextureInfo alloc] 
          initWithName:textureBufferID
          target:GL_TEXTURE_2D
          width:(GLuint)width
          height:(GLuint)height];
       
       return result;
    }
    

    上面这个方法使用Core Graphics图像的像素数据生成一个新的OGE缓存并初始化它,此方法返回一个不变的AGLKtextureinfo实例。

    解释一下这个方法glTexParameteri,该方法为创建的纹理缓存设置OGE取样和循环模式。如果使用了MIP贴图,第二个参数会被设置成GL_LINEAR_MIPMAP_LINEAR,这会告诉OGE使用与被取样的S,T坐标最接近的纹素的线性插值取样两个最合适的MIP贴图图像尺寸。然后,来自MIP贴图的两个样本被线性差值来产生最终的片元颜色。这里涉及的两个概念需要对opengl进行深入了解。

    其中AGLKDataWithResizedCGImageBytes把指定的cgImmage拖入imageData提供的字节中,Core Graphics把cgImage拖入一个适当大小的Core Graphics上下文中,这个过程会把图像的尺寸调整为2的幂,图像在绘制的时候还会被上下翻转,这是因为OGE的原点在左下角而iOS的实现原点却是在左上角,翻转Y轴确保了图像字节拥有适用于纹理缓存的正确的方向。

    static NSData *AGLKDataWithResizedCGImageBytes(
       CGImageRef cgImage,
       size_t *widthPtr,
       size_t *heightPtr)
    {
       NSCParameterAssert(NULL != cgImage);
       NSCParameterAssert(NULL != widthPtr);
       NSCParameterAssert(NULL != heightPtr);
       
       GLuint originalWidth = (GLuint)CGImageGetWidth(cgImage);
       GLuint originalHeight = (GLuint)CGImageGetWidth(cgImage);
       
       NSCAssert(0 < originalWidth, @"Invalid image width");
       NSCAssert(0 < originalHeight, @"Invalid image width");
       
       // Calculate the width and height of the new texture buffer
       // The new texture buffer will have power of 2 dimensions.
       GLuint width = AGLKCalculatePowerOf2ForDimension(
          originalWidth);
       GLuint height = AGLKCalculatePowerOf2ForDimension(
          originalHeight);
          
          //注意这个函数AGLKCalculatePowerOf2ForDimension就是用来取整用的
          
       // Allocate sufficient storage for RGBA pixel color data with 
       // the power of 2 sizes specified
       NSMutableData    *imageData = [NSMutableData dataWithLength:
          height * width * 4];  // 4 bytes per RGBA pixel
    
       NSCAssert(nil != imageData, 
          @"Unable to allocate image storage");
       
       // Create a Core Graphics context that draws into the 
       // allocated bytes
       CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
       CGContextRef cgContext = CGBitmapContextCreate( 
          [imageData mutableBytes], width, height, 8, 
          4 * width, colorSpace, 
          kCGImageAlphaPremultipliedLast);
       CGColorSpaceRelease(colorSpace);
       
       // Flip the Core Graphics Y-axis for future drawing
       CGContextTranslateCTM (cgContext, 0, height);
       CGContextScaleCTM (cgContext, 1.0, -1.0);
       
       // Draw the loaded image into the Core Graphics context 
       // resizing as necessary
       CGContextDrawImage(cgContext, CGRectMake(0, 0, width, height),
          cgImage);
       
       CGContextRelease(cgContext);
       
       *widthPtr = width;
       *heightPtr = height;
       
       return imageData;
    }
    

    最后在glk回调中实现真正画图:

    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
    {
       [self.baseEffect prepareToDraw];
       
       // Clear back frame buffer (erase previous drawing)
       [(AGLKContext *)view.context clear:GL_COLOR_BUFFER_BIT];
       
       [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribPosition
          numberOfCoordinates:3
          attribOffset:offsetof(SceneVertex, positionCoords)
          shouldEnable:YES];
       [self.vertexBuffer prepareToDrawWithAttrib:GLKVertexAttribTexCoord0
          numberOfCoordinates:2
          attribOffset:offsetof(SceneVertex, textureCoords)
          shouldEnable:YES];
          
       // Draw triangles using the first three vertices in the 
       // currently bound vertex buffer
       [self.vertexBuffer drawArrayWithMode:GL_TRIANGLES
          startVertexIndex:0
          numberOfVertices:3];
    }
    

    glEnableVertexAttribArray,

    glVertexAttribPointer,

    glDrawArrays

    这里提前熟悉一下这些函数,虽然现在还是对它们不是太理解。

    后面的例子就不一一写了,过程笔记都在代码中,分别涉及到一些概念

    OpenGLES_Ch3_3取样循环等

    OpenGLES_Ch3_4混合片元颜色

    OpenGLES_Ch3_5多重纹理

    OpenGLES_Ch3_6自定义纹理

    相关文章

      网友评论

        本文标题:OpenGLES-纹理的初步认识

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