Почему я не могу получить доступ к матрицам в пиксельном шейдере hlsl?

Я учусь создавать эффекты для wpf с помощью hlsl. В настоящее время я пытаюсь создать простой эффект, который отмечает края изображения. Я также хочу использовать для этого оператор Собеля, поэтому я установил общедоступный float2x3 в своем коде hlsl, но я не могу получить доступ к элементам в этой матрице.

Я пробовал вручную вводить правильные значения, и это работает, но не тогда, когда я использую цикл.

sampler2D imageSampler : register(s0);
float imageWidth : register(c0);
float imageHeight : register(c1);
float threshold : register(c2);

float2x3 op =
{
    1.0f, 2.0f, 1.0f,
    -1.0f, -2.0f, -1.0f
};


float grayScale(float3 color)
{
    return (color.r + color.g + color.b) / 3;
}

float4 GetEdgeLoop(float2 coord, float2 pixelSize)
{
    float2 current;
    float avrg = 0;

    float holder;
    float gsHolder;

    current.x = coord.x - pixelSize.x;
    for (int x = 0; x < 2; x++)
    {
        current.y = coord.y - pixelSize.y;
        for (int y = 0; y < 3; y++)
        {
            holder = op[x][y];
            gsHolder = grayScale(tex2D(imageSampler, current).rgb);
            avrg += gsHolder * holder;

            current.y += pixelSize.y;
        }
        current.x += pixelSize.x * 2;
    }

    avrg = abs(avrg / 8);

    if (avrg > threshold)
        return float4(1, 0, 0, 1);

    return tex2D(imageSampler, coord);
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float2 pixelSize = (1 / imageWidth, 1 / imageHeight);

    return GetEdgeLoop(uv, pixelSize);
}

Этот метод должен возвращать красный цвет для достаточно сильных краев. Иногда это возвращает красный цвет, но явно не для краев.

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

float4 GetEdge(float2 coord, float2 pixelSize)
{
    float avrg = 0;

    avrg += grayScale(tex2D(imageSampler, float2(coord.x - pixelSize.x, coord.y - pixelSize.y)).rgb) * 1;
    avrg += grayScale(tex2D(imageSampler, float2(coord.x - pixelSize.x, coord.y)).rgb) * 2;
    avrg += grayScale(tex2D(imageSampler, float2(coord.x - pixelSize.x, coord.y + pixelSize.y)).rgb) * 1;

    avrg += grayScale(tex2D(imageSampler, float2(coord.x + pixelSize.x, coord.y - pixelSize.y)).rgb) * (-1);
    avrg += grayScale(tex2D(imageSampler, float2(coord.x + pixelSize.x, coord.y)).rgb) * (-2);
    avrg += grayScale(tex2D(imageSampler, float2(coord.x + pixelSize.x, coord.y + pixelSize.y)).rgb) * (-1);

    avrg = abs(avrg / 8);

    if (avrg > threshold)
        return float4(1, 0, 0, 1);

    return tex2D(imageSampler, coord);
}

Этот метод не очень элегантный, и я хочу его заменить.


person Rotem Steiner    schedule 29.07.2019    source источник
comment
Можете ли вы опубликовать полный исходный код вашего шейдера.   -  person Colin Smith    schedule 29.07.2019


Ответы (1)


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

sampler2D implicitInputSampler : register(S0);
float opacity : register(C0);

(Первое определение — сэмплер текстуры, второе — регистр с плавающей точкой.)

Вам нужно «объявить» объекты, которые будут переданы в функцию шейдера. В руководстве Microsoft по написанию шейдерных эффектов есть хороший пример по адресу https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.effects.shadereffect?view=netframework-4.8. Вот код для потомков:

// Threshold shader 
// Object Declarations

sampler2D implicitInput : register(s0);
float threshold : register(c0);
float4 blankColor : register(c1);
//------------------------------------------------------------------------------------
// Pixel Shader
//------------------------------------------------------------------------------------
float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color = tex2D(implicitInput, uv);
    float intensity = (color.r + color.g + color.b) / 3;

    float4 result;
    if (intensity > threshold)
    {
        result = color;
    }
    else
    {
        result = blankColor;
    }

    return result;
}

Вы можете видеть, что код объявляет сэмплер текстуры, число с плавающей запятой и число с плавающей запятой4 (вектор с 4 числами с плавающей запятой) в качестве входных данных. Затем вы можете привязаться к ним через DependencyProperties следующим образом:

    #region Input dependency property

    public Brush Input
    {
        get { return (Brush)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }

    public static readonly DependencyProperty InputProperty =
        ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(ThresholdEffect), 0);

    #endregion

    ///////////////////////////////////////////////////////////////////////
    #region Threshold dependency property

    public double Threshold
    {
        get { return (double)GetValue(ThresholdProperty); }
        set { SetValue(ThresholdProperty, value); }
    }

    public static readonly DependencyProperty ThresholdProperty =
        DependencyProperty.Register("Threshold", typeof(double), typeof(ThresholdEffect),
                new UIPropertyMetadata(0.5, PixelShaderConstantCallback(0)));

    #endregion

    ///////////////////////////////////////////////////////////////////////
    #region BlankColor dependency property

    public Color BlankColor
    {
        get { return (Color)GetValue(BlankColorProperty); }
        set { SetValue(BlankColorProperty, value); }
    }

    public static readonly DependencyProperty BlankColorProperty =
        DependencyProperty.Register("BlankColor", typeof(Color), typeof(ThresholdEffect),
                new UIPropertyMetadata(Colors.Transparent, PixelShaderConstantCallback(1)));

    #endregion

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

Редактировать: Хорошо, я прочитал еще кое-что по этой теме, поиграл с вашим кодом и заставил его работать. Вот изменения, которые я сделал в произвольном порядке:

  • Я читал, что вы должны объявить свой массив как static const. Он не меняется во время выполнения шейдера и, по-видимому, «может» позволить некоторые незначительные оптимизации компилятора.
  • Я изменил float2x3 на float3x3, чтобы соответствовать ядру Лапласа, поскольку мы хотим, чтобы края работали со всех сторон изображения.
  • Я изменил функцию оттенков серого, чтобы использовать веса для подхода яркости.

Вот соответствующий код:

static const float3x3 laplace =
{
    -1.0f, -1.0f, -1.0f,
    -1.0f, 8.0f, -1.0f,
    -1.0f, -1.0f, -1.0f,
};

float grayScaleByLumino(float3 color)
{
    return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b);
}

float4 GetEdgeGeorge(float2 coord, float2 pixelSize)
{
    float2 current = coord;
    float avrg = 0;

    float kernelValue;
    float4 currentColor;
    float grayScale;

    float4 result;

    current.x = coord.x - pixelSize.x;
    for (int x = 0; x < 3; x++)
    {
        current.y = coord.y - pixelSize.y;
        for (int y = 0; y < 3; y++)
        {
            kernelValue = laplace[x][y];
            grayScale = grayScaleByLumino(tex2D(imageSampler, current).rgb);
            avrg += grayScale * kernelValue;

            current.y += pixelSize.y;
        }
        current.x += pixelSize.x;
    }

    avrg = abs(avrg / 8);

    if (avrg > threshold)
    {
        result = float4(1, 0, 0, 1);
    }
    else
    {
        result = tex2D(imageSampler, coord);
    }

    return result;
}

Все решение, которое я создал, доступно здесь: https://github.com/georgethejournalist/WPFShaders . Надеюсь, это поможет.

person George H.    schedule 30.07.2019
comment
Я упустил некоторую информацию при написании вопроса, поэтому я понимаю, если это было не очень ясно. Спасибо за попытку помочь, я исправил вопрос. - person Rotem Steiner; 31.07.2019