如何阻止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
相关产品推荐
相关产品推荐

