为什么我无法在hlsl像素着色器中访问矩阵?

时间:2019-07-29 14:40:29

标签: wpf for-loop hlsl pixel-shader

我正在学习如何使用hlsl为wpf创建效果。 我目前正在尝试制作一种简单的效果来标记图像中的边缘。 我也想为此使用Sobel运算符,因此我在hlsl代码中设置了一个公共float2x3,但似乎无法访问该矩阵中的元素。

我尝试手动输入正确的值,它可以工作,但在使用循环时无效。

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);
}

此方法不是很好,我想替换它。

1 个答案:

答案 0 :(得分:0)

我不是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;
}

您可以看到代码声明了一个纹理采样器,一个float和一个float4(具有4个float的向量)作为输入。然后,您可以像这样通过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参数,可让您指定要将值绑定到的寄存器的索引。

编辑:好的,所以我已经阅读了更多有关此主题的内容,使用了您的代码并使其正常工作。以下是我未按特定顺序进行的更改:

  • 我读到您应该将数组声明为静态const。在着色器执行期间,它不会更改,并且显然“可能”允许一些较小的编译器优化。
  • 我已将float2x3更改为float3x3以适合Laplace内核-因为我们希望边缘从图像的所有侧面开始工作。
  • 我已更改了灰度功能,以将权重用于发光度方法。

以下是相关代码:

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。我希望这会有所帮助。