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

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

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

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

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

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

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

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

ОБНОВЛЕНИЕ

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

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


person KomodoDave    schedule 22.10.2011    source источник


Ответы (1)


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

Чтобы разрешить сенсорное взаимодействие для выбора пикселя, сначала добавьте 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];
person KomodoDave    schedule 23.10.2011
comment
Я закончил весь механизм подбора, и он работает очень здорово :) Ура! - person KomodoDave; 23.10.2011
comment
Выбранный ответ отлично работает, за исключением того, что он также, похоже, выявил значительную ошибку в методе Apple GLKView снимок, которая вызывает большие всплески выделения памяти и приводит к сбоям в моем случае ... если кто-то еще запускал по этому вопросу см. мой ‹a href=stackoverflow.com/questions/17354982/ сообщение ‹/a› по этой теме. - person todd412; 07.07.2013