iOS 5 + GLKView:如何访问基于颜色的顶点拾取的像素RGB数据?

Kom*_*ave 7 pixel colors picking opengl-es-2.0 ios5

我一直在转换自己的个人OGLES 2.0框架,以利用新的iOS 5框架添加的功能GLKit.

在取悦结果之后,我现在希望实现这里描述的基于颜色的拾取机制.为此,您必须访问后台缓冲区以检索触摸的像素RGBA值,然后将其用作顶点/基元/显示对象的唯一标识符.当然,这需要对所有顶点/基元/显示对象进行临时唯一着色.

我有两个问题,我非常感谢你们的帮助:

  1. 我有权访问GLKViewController,GLKView,CAEAGLLayer(的GLKView)和EAGLContext.我还可以访问所有与OGLES 2.0缓冲区相关的命令.如何组合这些来识别我在屏幕上点击的EAGLContext中像素的颜色?

  2. 鉴于我正在使用顶点缓冲对象进行渲染,是否有一种巧妙的方法来覆盖提供给我的顶点着色器的颜色,首先不涉及修改缓冲的顶点(颜色)属性,其次不涉及添加一个IF语句进入顶点着色器?

我认为(2)的答案是"不",但出于性能和非艰苦的代码改造的原因,我认为与更有经验的人核实是明智的.

我们将非常感激地收到任何建议.感谢您的时间

UPDATE

我现在知道如何使用glReadPixels从活动帧缓冲区读取像素数据.所以我想我只需要对后台缓冲区进行特殊的"独特颜色"渲染,短暂切换到它并读取像素,然后切换回来.这将不可避免地产生视觉闪烁,但我想这是最简单的方法; 肯定比CGImageContextRef从屏幕快照创建一个并分析这种方式更快(也更明智).

尽管如此,关于后缓冲器的任何提示都将非常受欢迎.

Kom*_*ave 12

好吧,我已经尽可能简明扼要地研究了如何做到这一点.下面我解释如何实现这一点并列出所需的所有代码:)

为了允许触摸交互选择像素,首先UITapGestureRecognizerGLKViewController子类中添加一个(假设您需要点击选择像素),在该类中使用以下目标方法.你必须使你的GLKViewController子类a UIGestureRecognizerDelegate:

@interface GLViewController : GLKViewController <GLKViewDelegate, UIGestureRecognizerDelegate>
Run Code Online (Sandbox Code Playgroud)

在实例化手势识别器后,将其添加到view属性(GLKViewController实际上是一个GLKView):

// Inside GLKViewController subclass init/awakeFromNib:
[[self view] addGestureRecognizer:[self tapRecognizer]];
[[self tapRecognizer] setDelegate:self];
Run Code Online (Sandbox Code Playgroud)

设置手势识别器的目标操作; 你可以使用特定的方法创建它,init...但是我使用Storyboard(也就是"Xcode 4.2中的新接口构建器")创建了它并以这种方式连接它.

无论如何,这是我的轻拍手势识别器的目标动作:

-(IBAction)onTapGesture:(UIGestureRecognizer*)recognizer {
    const CGPoint loc = [recognizer locationInView:[self view]];
    [self pickAtX:loc.x Y:loc.y];
}
Run Code Online (Sandbox Code Playgroud)

在那里调用的pick方法是我在GLKViewController子类中定义的方法:

-(void)pickAtX:(GLuint)x Y:(GLuint)y {
    GLKView *glkView = (GLKView*)[self view];
    UIImage *snapshot = [glkView snapshot];
    [snapshot pickPixelAtX:x Y:y];
}
Run Code Online (Sandbox Code Playgroud)

这利用了一种方便的新方法snapshot,Apple通过这种方法从底层GLKView生成一种方法.UIImageEAGLContext

需要注意的重要事项是snapshotAPI文档中的注释,其中指出:

只要您的应用程序明确需要视图的内容,就应该调用此方法; 永远不要尝试使用OpenGL ES函数直接读取底层帧缓冲区的内容.

这给了我一个线索,为什么我之前尝试调用的方法glReadPixels是尝试访问生成的像素数据EXC_BAD_ACCESS,以及指示器向我发送了正确的路径.

你会发现在我的pickAtX:Y:方法定义刚才我所说的pickPixelAtX:Y:UIImage.这是我UIImage在自定义类别中添加的方法:

@interface UIImage (NDBExtensions)
-(void)pickPixelAtX:(NSUInteger)x Y:(NSUInteger)y;
@end
Run Code Online (Sandbox Code Playgroud)

这是实施; 这是所需的最终代码清单.代码来自这个问题,并根据收到的答案进行了修改:

@implementation UIImage (NDBExtensions)

- (void)pickPixelAtX:(NSUInteger)x Y:(NSUInteger)y {

    CGImageRef cgImage = [self CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);

    if ((x < width) && (y < height))
    {
        CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
        CFDataRef bitmapData = CGDataProviderCopyData(provider);
        const UInt8* data = CFDataGetBytePtr(bitmapData);
        size_t offset = ((width * y) + x) * 4;
        UInt8 b = data[offset+0];
        UInt8 g = data[offset+1];
        UInt8 r = data[offset+2];
        UInt8 a = data[offset+3];
        CFRelease(bitmapData);
        NSLog(@"R:%i G:%i B:%i A:%i",r,g,b,a);
    }
}

@end
Run Code Online (Sandbox Code Playgroud)

我最初尝试在Apple API文档中找到的一些相关代码,名为:"从CGImage上下文中获取像素数据",这需要2个方法定义而不是1,但是需要更多代码,并且有类型的数据void *我是无法执行正确的解释.

而已!将此代码添加到项目中,然后在点击像素时,它将以下列形式输出:

R:24 G:46 B:244 A:255
Run Code Online (Sandbox Code Playgroud)

当然,您应该编写一些方法来提取这些RGBA int值(将在0 - 255范围内)并根据需要使用它们.一种方法是UIColor从上面的方法返回一个,实例化如下:

UIColor *color = [UIColor colorWithRed:red/255.0f green:green/255.0f blue:blue/255.0f alpha:alpha/255.0f];
Run Code Online (Sandbox Code Playgroud)