Выходные данные первых двух фрагментных шейдеров отличаются

В настоящее время я пытаюсь заставить этот шейдер боке работать с GPUImage: http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(обновление)

Вот что у меня есть на данный момент:

precision mediump float;

varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
uniform float inputImageTextureWidth;
uniform float inputImageTextureHeight;

#define PI 3.14159265

float width = inputImageTextureWidth; //texture width
float height = inputImageTextureHeight; //texture height

vec2 texel = vec2(1.0/width,1.0/height);

//uniform variables from external script

uniform float focalDepth;  //focal distance value in meters, but you may use autofocus option below
uniform float focalLength; //focal length in mm
uniform float fstop; //f-stop value
bool showFocus = false; //show debug focus point and focal range (red = focal point, green = focal range)

float znear = 0.1; //camera clipping start
float zfar = 5.0; //camera clipping end

//------------------------------------------
//user variables

int samples = 3; //samples on the first ring
int rings = 3; //ring count

bool manualdof = false; //manual dof calculation
float ndofstart = 1.0; //near dof blur start
float ndofdist = 2.0; //near dof blur falloff distance
float fdofstart = 1.0; //far dof blur start
float fdofdist = 3.0; //far dof blur falloff distance

float CoC = 0.03;//circle of confusion size in mm (35mm film = 0.03mm)

bool vignetting = false; //use optical lens vignetting?
float vignout = 1.3; //vignetting outer border
float vignin = 0.0; //vignetting inner border
float vignfade = 22.0; //f-stops till vignete fades

bool autofocus = false; //use autofocus in shader? disable if you use external focalDepth value
vec2 focus = vec2(0.5, 0.5); // autofocus point on screen (0.0,0.0 - left lower corner, 1.0,1.0 - upper right)
float maxblur = 1.0; //clamp value of max blur (0.0 = no blur,1.0 default)

float threshold = 0.5; //highlight threshold;
float gain = 2.0; //highlight gain;

float bias = 0.5; //bokeh edge bias
float fringe = 0.7; //bokeh chromatic aberration/fringing

bool noise = false; //use noise instead of pattern for sample dithering
float namount = 0.0001; //dither amount

bool depthblur = false; //blur the depth buffer?
float dbsize = 1.25; //depthblursize

/*
 next part is experimental
 not looking good with small sample and ring count
 looks okay starting from samples = 4, rings = 4
 */

bool pentagon = false; //use pentagon as bokeh shape?
float feather = 0.4; //pentagon shape feather

//------------------------------------------


float penta(vec2 coords) //pentagonal shape
{
    float scale = float(rings) - 1.3;
    vec4  HS0 = vec4( 1.0,         0.0,         0.0,  1.0);
    vec4  HS1 = vec4( 0.309016994, 0.951056516, 0.0,  1.0);
    vec4  HS2 = vec4(-0.809016994, 0.587785252, 0.0,  1.0);
    vec4  HS3 = vec4(-0.809016994,-0.587785252, 0.0,  1.0);
    vec4  HS4 = vec4( 0.309016994,-0.951056516, 0.0,  1.0);
    vec4  HS5 = vec4( 0.0        ,0.0         , 1.0,  1.0);

    vec4  one = vec4( 1.0 );

    vec4 P = vec4((coords),vec2(scale, scale));

    vec4 dist = vec4(0.0);
    float inorout = -4.0;

    dist.x = dot( P, HS0 );
    dist.y = dot( P, HS1 );
    dist.z = dot( P, HS2 );
    dist.w = dot( P, HS3 );

    dist = smoothstep( -feather, feather, dist );

    inorout += dot( dist, one );

    dist.x = dot( P, HS4 );
    dist.y = HS5.w - abs( P.z );

    dist = smoothstep( -feather, feather, dist );
    inorout += dist.x;

    return clamp( inorout, 0.0, 1.0 );
}

float bdepth(vec2 coords) //blurring depth
{
    float d = 0.0;
    float kernel[9];
    vec2 offset[9];

    vec2 wh = vec2(texel.x, texel.y) * dbsize;

    offset[0] = vec2(-wh.x,-wh.y);
    offset[1] = vec2( 0.0, -wh.y);
    offset[2] = vec2( wh.x -wh.y);

    offset[3] = vec2(-wh.x,  0.0);
    offset[4] = vec2( 0.0,   0.0);
    offset[5] = vec2( wh.x,  0.0);

    offset[6] = vec2(-wh.x, wh.y);
    offset[7] = vec2( 0.0,  wh.y);
    offset[8] = vec2( wh.x, wh.y);

    kernel[0] = 1.0/16.0;   kernel[1] = 2.0/16.0;   kernel[2] = 1.0/16.0;
    kernel[3] = 2.0/16.0;   kernel[4] = 4.0/16.0;   kernel[5] = 2.0/16.0;
    kernel[6] = 1.0/16.0;   kernel[7] = 2.0/16.0;   kernel[8] = 1.0/16.0;


    for( int i=0; i<9; i++ )
    {
        float tmp = texture2D(inputImageTexture2, coords + offset[i]).r;
        d += tmp * kernel[i];
    }

    return d;
}


vec3 color(vec2 coords,float blur) //processing the sample
{
    vec3 col = vec3(0.0);

    col.r = texture2D(inputImageTexture, coords + vec2(0.0,1.0)*texel*fringe*blur).r;
    col.g = texture2D(inputImageTexture, coords + vec2(-0.866,-0.5)*texel*fringe*blur).g;
    col.b = texture2D(inputImageTexture, coords + vec2(0.866,-0.5)*texel*fringe*blur).b;

    vec3 lumcoeff = vec3(0.299,0.587,0.114);
    float lum = dot(col.rgb, lumcoeff);
    float thresh = max((lum-threshold)*gain, 0.0);
    return col+mix(vec3(0.0),col,thresh*blur);
}

vec2 rand(vec2 coord) //generating noise/pattern texture for dithering
{
    float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0;
    float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0;

    if (noise)
    {
        noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0;
        noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0;
    }
    return vec2(noiseX,noiseY);
}

vec3 debugFocus(vec3 col, float blur, float depth)
{
    float edge = 0.002*depth; //distance based edge smoothing
    float m = clamp(smoothstep(0.0,edge,blur),0.0,1.0);
    float e = clamp(smoothstep(1.0-edge,1.0,blur),0.0,1.0);

    col = mix(col,vec3(1.0,1.0,0.0),(1.0-m)*0.6);
    col = mix(col,vec3(0.0,1.0,1.0),((1.0-e)-(1.0-m))*0.2);

    return col;
}

float linearize(float depth)
{
    return -zfar * znear / (depth * (zfar - znear) - zfar);
}

float vignette()
{
    float dist = distance(textureCoordinate.xy, vec2(0.5,0.5));
    dist = smoothstep(vignout+(fstop/vignfade), vignin+(fstop/vignfade), dist);
    return clamp(dist,0.0,1.0);
}

void main()
{
    //scene depth calculation

    float depth = linearize(texture2D(inputImageTexture2, textureCoordinate2.xy).x);

    if (depthblur)
    {
        depth = linearize(bdepth(textureCoordinate2.xy));
    }

    //focal plane calculation

    float fDepth = focalDepth;

    if (autofocus)
    {
        fDepth = linearize(texture2D(inputImageTexture2, focus).x);
    }

    //dof blur factor calculation

    float blur = 0.0;

    if (manualdof)
    {
        float a = depth-fDepth; //focal plane
        float b = (a-fdofstart)/fdofdist; //far DoF
        float c = (-a-ndofstart)/ndofdist; //near Dof
        blur = (a>0.0)?b:c;
    }

    else
    {
        float f = focalLength; //focal length in mm
        float d = fDepth*1000.0; //focal plane in mm
        float o = depth*1000.0; //depth in mm

        float a = (o*f)/(o-f);
        float b = (d*f)/(d-f);
        float c = (d-f)/(d*fstop*CoC);

        blur = abs(a-b)*c;
    }

    blur = clamp(blur,0.0,1.0);

    // calculation of pattern for ditering

    vec2 noise = rand(textureCoordinate.xy)*namount*blur;

    // getting blur x and y step factor

    float w = (1.0/width)*blur*maxblur+noise.x;
    float h = (1.0/height)*blur*maxblur+noise.y;

    // calculation of final color

    vec3 col = vec3(0.0);

    if(blur < 0.05) //some optimization thingy
    {
        col = texture2D(inputImageTexture, textureCoordinate.xy).rgb;
    }

    else
    {
        col = texture2D(inputImageTexture, textureCoordinate.xy).rgb;
        float s = 1.0;
        int ringsamples;

        for (int i = 1; i <= rings; i += 1)
        {
            ringsamples = i * samples;

            for (int j = 0 ; j < ringsamples ; j += 1)
            {
                float step = PI*2.0 / float(ringsamples);
                float pw = (cos(float(j)*step)*float(i));
                float ph = (sin(float(j)*step)*float(i));
                float p = 1.0;
                if (pentagon)
                {
                    p = penta(vec2(pw,ph));
                }
                col += color(textureCoordinate.xy + vec2(pw*w,ph*h),blur)*mix(1.0,(float(i))/(float(rings)),bias)*p;
                s += 1.0*mix(1.0,(float(i))/(float(rings)),bias)*p;
            }
        }
        col /= s; //divide by sample count
    }

    if (showFocus)
    {
        col = debugFocus(col, blur, depth);
    }

    if (vignetting)
    {
        col *= vignette();
    }

    gl_FragColor.rgb = col;
    gl_FragColor.a = 1.0;
}

Это мой фильтр боке, подкласс GPUImageTwoInputFilter:

@implementation GPUImageBokehFilter

- (id)init;
{
    NSString *fragmentShaderPathname = [[NSBundle mainBundle] pathForResource:@"BokehShader" ofType:@"fsh"];
    NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPathname encoding:NSUTF8StringEncoding error:nil];

    if (!(self = [super initWithFragmentShaderFromString:fragmentShaderString]))
    {
        return nil;
    }

    focalDepthUniform = [filterProgram uniformIndex:@"focalDepth"];
    focalLengthUniform = [filterProgram uniformIndex:@"focalLength"];
    fStopUniform = [filterProgram uniformIndex:@"fstop"];

    [self setFocalDepth:1.0];
    [self setFocalLength:35.0];
    [self setFStop:2.2];

    return self;
}


#pragma mark -
#pragma mark Accessors

- (void)setFocalDepth:(float)focalDepth {
    _focalDepth = focalDepth;
    [self setFloat:_focalDepth forUniform:focalDepthUniform program:filterProgram];
}

- (void)setFocalLength:(float)focalLength {
    _focalLength = focalLength;
    [self setFloat:_focalLength forUniform:focalLengthUniform program:filterProgram];
}

- (void)setFStop:(CGFloat)fStop {
    _fStop = fStop;
    [self setFloat:_fStop forUniform:fStopUniform program:filterProgram];
}

@end

И, наконец, вот как я использую этот фильтр:

@implementation ViewController {
    GPUImageBokehFilter *bokehFilter;
    GPUImagePicture *bokehMap;
    UIImage *inputImage;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    inputImage = [UIImage imageNamed:@"stones"];
    bokehMap = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:@"bokehmask"]];
    _backgroundImage.image = inputImage;

    bokehFilter = [[GPUImageBokehFilter alloc] init];

    [self processImage];
}
- (IBAction)dataInputUpdated:(id)sender {
    [self processImage];
}
- (void *)processImage {
    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        GPUImagePicture *gpuPicture = [[GPUImagePicture alloc] initWithImage:inputImage];
        [gpuPicture addTarget:bokehFilter];
        [gpuPicture processImage];
        [bokehMap addTarget:bokehFilter];
        [bokehMap processImage];
        [bokehFilter useNextFrameForImageCapture];
        [bokehFilter setFloat:inputImage.size.width forUniformName:@"inputImageTextureWidth"];
        [bokehFilter setFloat:inputImage.size.height forUniformName:@"inputImageTextureHeight"];

        UIImage *blurredImage = [bokehFilter imageFromCurrentFramebuffer];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self displayNewImage:blurredImage];
        });
    });
}
- (void)displayNewImage:(UIImage*)newImage {
    [UIView transitionWithView:_backgroundImage
                      duration:.6f
                       options:UIViewAnimationOptionTransitionCrossDissolve
                    animations:^{
                        _backgroundImage.image = newImage;
                    } completion:nil];
}

...

Первое изображение — это то, которое я пытаюсь размыть, второе — это случайный градиент для проверки карты глубины шейдера:

камнибокемаска

Когда я запускаю приложение на своем iPhone, я получаю следующее:

Странный результат шейдера 1

После перемещения ползунка (который запускает метод dataInputChanged) я получаю следующее:

Странный результат шейдера 2, с диагональной зашумленной линией

Хотя это, по общему признанию, выглядит намного лучше, чем первое изображение, у меня все еще есть некоторые проблемы с этим:

  1. Есть диагональная зашумленная линия (внутри красных линий, которые я нанес на картинку), которая кажется неразмытой.
  2. Верхний левый угол изображения размыт, хотя этого быть не должно.

Почему у меня такое странное поведение? Разве вывод шейдера не должен быть одинаковым каждый раз? Кроме того, как мне заставить его уважать карту глубины? Мои познания в шейдерах GLSL очень ограничены, так что наберитесь терпения.


person Peter W.    schedule 25.04.2014    source источник
comment
Первое, что бросается в глаза, это то, что [bokehMap processImage]; и [bokehFilter useNextFrameForImageCapture]; стоят в неправильном порядке. Их нужно перевернуть, чтобы вы сообщили фильтру, что вы будете захватывать их, прежде чем запускать обработку, которая обновляет выходные данные фильтра. Попробуйте переключить их и посмотрите, что произойдет. Кроме того, это ужасно дорогой шейдер, и мне интересно, будет ли лучше работать замена его фильтром с тремя входами, который также принимает размытую по Гауссу версию изображения для микширования (как это делает сдвиг наклона).   -  person Brad Larson    schedule 25.04.2014
comment
Я перевернул два вызова метода только для того, чтобы получить те же результаты. Кроме того, я знаю, что этот шейдер дорог, но я недостаточно знаю о шейдерах, чтобы упростить его. Это чудо, что мне удалось добраться так далеко без посторонней помощи! :D Было бы здорово, если бы кто-то, кто знает, что делает, взял это, очистил и сделал «официальной» частью GPUImage.   -  person Peter W.    schedule 26.04.2014
comment
(Кроме того, продолжайте в том же духе! GPUImage заставил меня изучить основы фрагментных шейдеров, что я слишком долго откладывал)   -  person Peter W.    schedule 26.04.2014


Ответы (4)


Диагональный артефакт, по-видимому, вызван вашим тестовым градиентом. Вы можете видеть, что это происходит примерно в том же месте, где ваш градиент становится полностью белым. Попробуйте распределить градиент так, чтобы он достигал 1,0 или 0,0 только в самых углах изображения.

person Anna Dickinson    schedule 08.05.2014
comment
Это противоречит цели создания карты глубины. - person Peter W.; 08.05.2014
comment
Что ты имеешь в виду? Я говорю, что вы по-прежнему используете градиент, просто не ограничивайте его до 1,0 или 0,0 в середине изображения. Попробуйте наложить свой градиент (в фотошопе или что-то в этом роде) на изображение; вы увидите, что я имею в виду. - person Anna Dickinson; 09.05.2014
comment
Мне нужны эти сплошные белые/черные области, потому что они сообщают шейдеру, что нужно полностью держать в фокусе, а что размыть. Если я последую вашему совету, шейдер размоет все, кроме очень небольшой части в углу изображения, где градиент достигает 1,0. - person Peter W.; 09.05.2014
comment
Ох, хорошо. Я недостаточно хорошо разбираюсь в шейдере, чтобы точно знать, почему, но форма градиента определенно является причиной артефакта — есть край, когда градиент приближается к 0,0 или 1,0. Если вы думаете об этом как об обработке сигнала, края — это высокочастотные изменения. Шейдер должен отфильтровать их; шейдер должен больше сэмплировать вокруг каждой точки. Чтобы избежать этого изменения, вы можете попробовать плавный градиент (вместо линейного); градиент S-образной кривой (сейчас я забыл, как это называется). - person Anna Dickinson; 09.05.2014
comment
Вы можете использовать карту глубины RGB и получить 16777216 градаций вместо 256. - person badweasel; 10.05.2014
comment
Я скажу. Я провожу дни, работая над подобными вещами для приложения. Попытка найти виновного, когда что-то идет не так. Я думаю, Анна что-то задумала. Я не могу сказать, какая часть изображения размыта, а какая нет. Я бы увеличил сумму, чтобы вы действительно могли сказать. Или используйте наложение цвета вместо размытия, чтобы увидеть, что ваш код буфера глубины даже прав. Вы написали весь этот код или нашли его где-то? Потому что мне кажется, что формула неверна или, может быть, точная подсказка отключена. - person badweasel; 10.05.2014
comment
Оказывается, за артефакт отвечает функция linearize(). Я удалил его, и артефакт исчез. - person Peter W.; 11.05.2014

Это довольно большой вопрос, и я не могу дать полный ответ, потому что мне действительно нужно проверить это.

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

Еще меня беспокоит ваш комментарий //some optimization thingy. Это то, что будет отвечать за некрасивую линию в вашем окончательном выводе. Сказать, что у вас не будет размытия под blur < 0.05, это не обязательно то, что вы можете сделать! Я бы ожидал неприятного артефакта, когда шейдер переходит от шейдера размытия к «оптимизированной» части.

Надеюсь, это прольет свет, и удачи!

person AudioGL    schedule 10.05.2014
comment
Оптимизация на самом деле очень полезна, поскольку она в основном не позволяет шейдеру обрабатывать части изображения, которые не предназначены для обработки. Помогает то, что переход от размытия 0,05% к размытию 0% практически незаметен. - person Peter W.; 11.05.2014

Вы пытались включить showFocus? Это должно показать фокальную точку красным цветом, а фокальный диапазон — зеленым, что должно помочь при отладке. Вы также можете попробовать включить autofocus, чтобы убедиться, что центр изображения находится в фокусе, потому что на данный момент не очевидно, какое расстояние должно быть в фокусе, из-за того, что функция linearize меняет системы координат. После этого попробуйте настроить fstop, чтобы получить желаемое размытие. Вы, вероятно, также обнаружите, что вам потребуется большее значение, чем samples = 3 и rings = 3, чтобы получить плавный эффект боке.

person user3615737    schedule 08.05.2014

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

Некрасивая диагональная линия была вызвана методом linearize(), поэтому я ее убрал и заставил шейдер использовать значения RGB (точнее: только значение R) из карты глубины без предварительной их обработки.

Голубоватое изображение, которое я получил от шейдера, было вызвано моей собственной некомпетентностью. Эти две строки должны быть помещены перед вызовами processImage:

[bokehFilter setFloat:inputImage.size.width forUniformName:@"inputImageTextureWidth"];
[bokehFilter setFloat:inputImage.size.height forUniformName:@"inputImageTextureHeight"];

Оглядываясь назад, становится очевидно, почему я получил результаты только во второй раз, когда использовал шейдер. После исправления этих ошибок я немного оптимизировал его, чтобы максимально сократить время выполнения, и теперь я могу сказать ему рендерить 8 сэмплов/4 кольца, и он делает это менее чем за секунду. Вот как это выглядит:

Как должен выглядеть вывод шейдера

Спасибо всем за ответы, без вас я бы, наверное, не исправил эти ошибки.

person Peter W.    schedule 11.05.2014