iOS 5 + GLKView: как получить доступ к пиксельным данным RGB для выбора вершин на основе цвета? - PullRequest
7 голосов
/ 22 октября 2011

Я конвертировал свою собственную платформу OGLES 2.0, чтобы воспользоваться функциональностью, добавленной новой платформой iOS 5 GLKit.

После получения приятных результатов я теперь хочу реализовать механизм выбора на основе цвета, описанный здесь . Для этого вам необходимо получить доступ к заднему буферу, чтобы получить значение RGBA касанного пикселя, которое затем используется в качестве уникального идентификатора для вершины / примитива / экранного объекта. Конечно, для этого требуется временная уникальная раскраска всех вершин / примитивов / экранных объектов.

У меня есть два вопроса, и я был бы очень признателен за помощь в любом из них:

  1. У меня есть доступ к GLKViewController, GLKView, CAEAGLLayer (из GLKView) и EAGLContext. У меня также есть доступ ко всем OGLES 2.0 команды, связанные с буфером. Как мне объединить их, чтобы определить цвет пикселя в тексте EAGLC, который я нажимаю на экране?

  2. Учитывая, что я использую Vertex Buffer Objects для рендеринга, есть ли удобный способ переопределить цвет, предоставленный моему вершинному шейдеру который, во-первых, не включает изменение буферизованной вершины (цвета) атрибуты, а во-вторых, не включает в себя добавление IF оператор в вершинный шейдер?

Я предполагаю, что ответом на (2) является "нет", но по соображениям производительности и несложного изменения кода я посчитал целесообразным проверить кого-то более опытного.

Любые предложения будут с благодарностью приняты. Спасибо за ваше время

UPDATE

Ну, теперь я знаю, как читать данные пикселей из активного буфера кадров, используя glReadPixels . Так что, я думаю, мне просто нужно сделать специальные «уникальные цвета» для рендеринга в задний буфер, кратко переключиться на него и прочитать пиксели, затем переключиться обратно. Это неизбежно создаст визуальное мерцание, но я думаю, что это самый простой способ; конечно, быстрее (и разумнее), чем создавать CGImageContextRef из снимка экрана и анализировать таким образом.

Тем не менее, любые советы по поводу заднего буфера будут очень благодарны.

1 Ответ

12 голосов
/ 23 октября 2011

Ну, я точно определил, как сделать это максимально лаконично. Ниже я объясню, как этого добиться, и перечислю весь необходимый код:)

Чтобы позволить сенсорному взаимодействию выбрать пиксель, сначала добавьте UITapGestureRecognizer к своему подклассу GLKViewController (при условии, что вы хотите нажать на пиксель для выбора) со следующим целевым методом внутри этого класса. Вы должны сделать свой GLKViewController подкласс UIGestureRecognizerDelegate:

@interface GLViewController : GLKViewController <GLKViewDelegate, UIGestureRecognizerDelegate>

После создания экземпляра распознавателя жестов добавьте его в свойство view (которое в GLKViewController на самом деле является GLKView):

// Inside GLKViewController subclass init/awakeFromNib:
[[self view] addGestureRecognizer:[self tapRecognizer]];
[[self tapRecognizer] setDelegate:self];

Установите целевое действие для вашего распознавателя жестов; Вы можете сделать это при создании с использованием определенного init..., однако я создал свой с использованием Storyboard (он же «новый Интерфейсный Разработчик в XCode 4.2») и подключил его таким образом.

В любом случае, вот мое целевое действие для распознавателя жестов касания:

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

Вызван метод выбора, который я определил в своем подклассе GLKViewController:

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

Для этого используется новый удобный метод snapshot, который Apple любезно включил в GLKView для получения UIImage из базового EAGLContext.

Важно отметить комментарий в документации snapshot API, который гласит:

Этот метод должен вызываться всякий раз, когда ваше приложение явно нуждается в содержании представления; никогда не пытайтесь непосредственно прочитать содержимое базового кадрового буфера с использованием функций OpenGL ES.

Это дало мне ключ к пониманию того, почему мои более ранние попытки вызвать glReadPixels при попытках доступа к пиксельным данным сгенерировали EXC_BAD_ACCESS и индикатор, который вместо этого направил меня по правильному пути.

Вы заметите, что в моем pickAtX:Y: методе, определенном минуту назад, я вызываю pickPixelAtX:Y: для UIImage. Это метод, который я добавил в UIImage в пользовательской категории:

@interface UIImage (NDBExtensions)
-(void)pickPixelAtX:(NSUInteger)x Y:(NSUInteger)y;
@end

Вот реализация; это окончательный список кодов требуется. Код пришел от этого вопроса и был изменен в соответствии с полученным ответом:

@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

Первоначально я попробовал некоторый связанный код, найденный в документе Apple API под названием: «Получение пиксельных данных из контекста CGImage», для которого требовалось 2 определения метода вместо этого 1, но требуется гораздо больше кода и есть данные типа void *, за которые я не смог реализовать правильную интерпретацию.

Вот и все! Добавьте этот код в свой проект, затем, нажав на пиксель, он выведет его в виде:

R:24 G:46 B:244 A:255

Конечно, вы должны написать некоторые средства для извлечения этих значений RGBA int (которые будут в диапазоне 0 - 255) и использования их по своему усмотрению. Один из подходов состоит в том, чтобы вернуть UIColor из вышеописанного метода, например, так:

UIColor *color = [UIColor colorWithRed:red/255.0f green:green/255.0f blue:blue/255.0f alpha:alpha/255.0f];
...