You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止WPF ShaderEffect的SamplerProperty将纹理缩放到控件尺寸

WPF ShaderEffect中阻止纹理自动缩放的解决方法

我在WPF中实现了一个包含两个SamplerProperty的ShaderEffect:

  • 寄存器S0是隐式输入,绑定到目标Rectangle的Fill(另一个ImageBrush)
  • 寄存器S1是TestTexture,通过ImageBrush传入一张PNG图片
    同时向HLSL着色器传入了绑定到Rectangle实际宽高(已取整)的ViewportWidth和ViewportHeight常量。

相关代码

XAML代码

<TextBlock Grid.Row="0">
    Texture: 1009 x 753, Control: <Run Text="{Binding Path=ActualWidth, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}"/> x <Run Text="{Binding Path=ActualHeight, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}"/>
</TextBlock>
<Rectangle x:Name="TheRectangle" Grid.Row="1" Margin="20" RenderOptions.BitmapScalingMode="NearestNeighbor" >
    <Rectangle.Fill>
        <ImageBrush ImageSource="D:\NoEffect.png"/>
    </Rectangle.Fill>
    <Rectangle.Effect>
        <l:MyEffect1
            x:Name="MyEffect"
            ViewportWidth="{Binding Path=ActualWidth, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}"
            ViewportHeight="{Binding Path=ActualHeight, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}">
            <l:MyEffect1.TestTexture>
                <ImageBrush x:Name="Texture" ImageSource="D:\MinecraftSkeletonTexture.png"/>
            </l:MyEffect1.TestTexture>
        </l:MyEffect1>
    </Rectangle.Effect>
</Rectangle>

HLSL着色器代码

sampler2D ImplicitTexture : register(S0);
sampler2D TestTexture : register(S1);

float ViewportWidth : register(C0);
float ViewportHeight : register(C1);

float4 main(float2 UV : TEXCOORD) : SV_Target
{
    // 测试单独采样两个纹理均正常
    // return tex2D(ImplicitTexture, UV);
    // return tex2D(TestTexture, UV);

    // 将UV转换为像素坐标
    float2 ScreenPixel = UV * float2(ViewportWidth, ViewportHeight);
    // 硬编码TestTexture的原始像素尺寸
    float2 TestTextureSourceSize = float2(1009, 753);
    // 计算TestTexture上的采样位置(像素坐标)
    float2 SamplePositionInPixels = ScreenPixel;
    // 转换为TestTexture的UV坐标
    float2 TestTextureRelativeUv = SamplePositionInPixels / TestTextureSourceSize;
    // 采样TestTexture
    return tex2D(TestTexture, TestTextureRelativeUv);
}

问题现象

逻辑本身可行,但缩小窗口时(Rectangle尺寸变小),无法访问TestTexture的完整像素数据,出现扭曲块状失真。推测WPF会自动将TestTexture缩放到Rectangle的尺寸,而我需要用该纹理传递原始像素数据,缩放插值会破坏数据。已尝试设置RenderOptions.BitmapScalingMode="NearestNeighbor",仅能改变缩放方式,无法阻止缩放行为。

解决方法

1. 预渲染原始尺寸纹理

通过RenderTargetBitmap将原始图片渲染为与原图同尺寸的位图,再将其作为ImageBrush的源传入ShaderEffect,避免WPF自动缩放纹理:

// 加载原始图片
var bitmap = new BitmapImage(new Uri(@"D:\MinecraftSkeletonTexture.png"));
// 创建与原图同尺寸的RenderTargetBitmap
var rtb = new RenderTargetBitmap(
    (int)bitmap.PixelWidth, 
    (int)bitmap.PixelHeight, 
    bitmap.DpiX, 
    bitmap.DpiY, 
    PixelFormats.Pbgra32);

// 渲染原始图片到RTB
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
    dc.DrawImage(bitmap, new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
}
rtb.Render(drawingVisual);

// 将RTB赋值给TestTexture的ImageBrush
Texture.ImageSource = rtb;

2. 设置SamplerProperty的采样模式

在自定义ShaderEffect注册TestTexture属性时,指定采样模式为NearestNeighbor,确保采样时不进行插值:

public static readonly DependencyProperty TestTextureProperty = 
    ShaderEffect.RegisterPixelShaderSamplerProperty(
        nameof(TestTexture), 
        typeof(MyEffect1), 
        1, 
        SamplingMode.NearestNeighbor);

public Brush TestTexture
{
    get => (Brush)GetValue(TestTextureProperty);
    set => SetValue(TestTextureProperty, value);
}

3. 传入原始纹理尺寸到着色器

避免硬编码纹理尺寸,通过额外的常量参数传入原始宽高,保证UV计算准确:

修改HLSL代码

sampler2D ImplicitTexture : register(S0);
sampler2D TestTexture : register(S1);

float ViewportWidth : register(C0);
float ViewportHeight : register(C1);
float TestTextureWidth : register(C2);
float TestTextureHeight : register(C3);

float4 main(float2 UV : TEXCOORD) : SV_Target
{
    float2 ScreenPixel = UV * float2(ViewportWidth, ViewportHeight);
    float2 TestTextureSourceSize = float2(TestTextureWidth, TestTextureHeight);
    float2 SamplePositionInPixels = ScreenPixel;
    float2 TestTextureRelativeUv = SamplePositionInPixels / TestTextureSourceSize;
    return tex2D(TestTexture, TestTextureRelativeUv);
}

修改XAML绑定

<l:MyEffect1
    x:Name="MyEffect"
    ViewportWidth="{Binding Path=ActualWidth, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}"
    ViewportHeight="{Binding Path=ActualHeight, ElementName=TheRectangle, Mode=OneWay, Converter={StaticResource ResourceKey=Rounder}}"
    TestTextureWidth="{Binding PixelWidth, ElementName=OriginalTextureImage}"
    TestTextureHeight="{Binding PixelHeight, ElementName=OriginalTextureImage}">
    <l:MyEffect1.TestTexture>
        <ImageBrush x:Name="Texture" ImageSource="{Binding Source={StaticResource OriginalRtb}}"/>
    </l:MyEffect1.TestTexture>
</l:MyEffect1>
<!-- 可隐藏原始图片用于绑定尺寸 -->
<Image x:Name="OriginalTextureImage" Source="D:\MinecraftSkeletonTexture.png" Visibility="Collapsed"/>

通过上述步骤,可确保传入着色器的是原始分辨率的纹理,不会因窗口缩放导致像素丢失或失真,完整保留纹理中的原始数据。

内容的提问来源于stack exchange,提问作者Niko O

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 22:34:59