UWP/WinUI3 PixelShaderEffect 實現ThresholdEffect 濾鏡。

来源:https://www.cnblogs.com/chifan/archive/2022/08/06/16556284.html
-Advertisement-
Play Games

在上一遍文章中已經介紹了PixelShaderEffect 用hlsl(著色器) 可以實現各種自定義濾鏡效果了,本文將用 "ThresholdEffect" 來講解如何編寫,編譯hlsl,然後使用PixelShaderEffect製作自定義濾鏡。 效果圖: 一.hlsl幫助程式介紹 在寫hlsl 代 ...


在上一遍文章中已經介紹了PixelShaderEffect 用hlsl(著色器) 可以實現各種自定義濾鏡效果了,本文將用 "ThresholdEffect" 來講解如何編寫,編譯hlsl,然後使用PixelShaderEffect製作自定義濾鏡。

效果圖:

 

 

一.hlsl幫助程式介紹

  在寫hlsl 代碼前需要簡單介紹下 “hlsl幫助程式”.通過學習了 hlsl幫助程式 後我們不需要將hlsl的所有知識都掌握了就可以寫一寫簡單的hlsl代碼了。hlsl幫助程式分為兩部分,巨集定義和函數。

  1.巨集定義

D2D_INPUT_COUNT N 紋理輸入個數。必須定義
D2D_INPUTn_SIMPLE  指定第n個紋理的為簡單採樣,預設為此定義,可選的
D2D_INPUTn_COMPLEX   指定第n個紋理為複雜採樣.可選的
D2D_REQUIRES_SCENE_POSITION 指示著色器函數調用使用場景位置的值的幫助方法(即使用D2DGetScenePosition函數,必須要有該定義)
D2D_CUSTOM_ENTRY   著色器程式入口
#include "d2d1effecthelpers.hlsli" 引入 hlsl幫助程式

  2.函數

D2DGetInput(n) 獲取第n張紋理的當前位置的像素,返回float4,即是rgba顏色。
D2DSampleInput(n,float2(x,y)) (按百分比位置進行採樣)獲取第n張紋理指定位置(xy按照百分比0~1)的像素顏色,返回float4,即rgba顏色。(需要定義紋理為複雜採樣才能使用該函數)
D2DSampleInputAtOffset(n,float2(ox,oy)) (按絕對位置進行偏移採樣)從輸入坐標偏移的偏移量對第n張紋理進行採樣(需要定義紋理為複雜採樣)。例子:比如需要獲取當前像素的左邊像素可以使用該函數 D2DSampleInputAtOffset(0,float(-1,0))來獲取左邊像素的顏色;
D2DSampleInputAtPosition(n,float2(x,y)) (按絕對位置進行採樣) 例子:比如輸入的紋理圖像大小的寬和高都為100,現在需要獲取該紋理位置 50,50位置的像素可以使用該函數D2DSampleInputAtPosition(0,float2(50,50));(需要定義紋理為複雜採樣)
D2DGetInputCoordinate(n) 獲取當前像素在屏幕上的坐標(相對位置0~1)(需要定義紋理為複雜採樣)
D2DGetScenePosition()   獲取當前像素在屏幕上的坐標(絕對位置)(需要定義 D2D_REQUIRES_SCENE_POSITION巨集)
D2D_PS_ENTRY 函數

 一個巨集,用於定義具有給定函數名稱的像素著色器入口點。

  hlsl幫助程式文檔:HLSL 幫助程式 - Win32 apps | Microsoft Docs

  hlsl文檔:高級著色器語言 (HLSL) - Win32 apps | Microsoft Docs

二.編寫hlsl代碼

  在項目程式中右鍵-》添加新建項-》常規-》文本文件 並將文件名改成ThresholdEffect.hlsl

  在代碼中用巨集定義了 輸入一張紋理,並且將紋理的採樣模式定義為簡單模式,通過#include 引入hlsl幫助程式。

然後在聲明瞭 三個變數 hreshold,startColor,endColor;

  聲明一個 getGray(in float3 color) 函數將顏色的各個分量相加再除以3 並且返回;

  最後定義了一個 D2D_PS_ENTRY 函數,該函數是著色器程式的入口函數,在函數裡面調用了 D2DGetInput(0)函數獲取第0張紋理的當前像素,然後通過調用getGray 函數將像素的顏色轉換成灰度值後跟 threshold 比較再決定返回那個顏色。

註意:函數的聲明需要寫在調用前,否則編譯的時候會找不到函數而報錯;

//定義輸入紋理數量為 1張
#define D2D_INPUT_COUNT 1
//定義第一張紋理的採樣模式為簡單模式
#define D2D_INPUT0_SIMPLE
//引入 hlsl幫助程式
#include "d2d1effecthelpers.hlsli"

//定義屬性
//閾值
float threshold;
//預設第一個顏色為白色
float4 startColor = float4(1,1,1,1);
//預設第二個顏色為黑色
float4 endColor = float4(0, 0, 0, 1);
//將輸入顏色轉換成灰度 (r+g+b)/3
float getGray(in float3 color)
{
    float gray = (color.r + color.g + color.b) / 3;
    return gray;
}
//程式入口
D2D_PS_ENTRY(main){
    //獲取當前位置的輸入紋理像素顏色
float3 color = D2DGetInput(0).rgb;
float gray = getGray(color);
    if(gray<=threshold){
    return endColor;
    }
    return startColor;
}

三.編譯hlsl

  現在我們來將上面寫好的hlsl 編譯成二進位文件。

  1.記事本方式打開我們報錯好的 ThresholdEffect.hlsl 文件查看它的編碼格式是否是 UTF-8 ,如果不是則需要將編碼改成UTF-8後保存。如果編碼不對的情況下會在編譯時報錯說”找不到任何代碼“.

 

 

 

 

 

  2.在vs工具欄中找到 工具-》命令行-》開發者提示    然後將位置切換到存放 ThresholdEffect.hlsl 文件的目錄下,

然後輸入以下命令進行編譯,如果編譯成功就會在存放目錄下看到會多了一個ThresholdEffect.bin的文件,這個文件就是將hlsl編譯好的二進位文件了:

set INCLUDEPATH="%WindowsSdkDir%\Include\%WindowsSDKVersion%\um"
 fxc ThresholdEffect.hlsl /nologo /T lib_4_0_level_9_3_ps_only /D D2D_FUNCTION /D D2D_ENTRY=main /Fl ThresholdEffect.fxlib /I %INCLUDEPATH%
 fxc ThresholdEffect.hlsl /nologo /T ps_4_0_level_9_3 /D D2D_FULL_SHADER /D D2D_ENTRY=main /E main /setprivate ThresholdEffect.fxlib /Fo:ThresholdEffect.bin /I %INCLUDEPATH%
del ThresholdEffect.fxlib

  編譯命令就不在這裡展開講了感興趣的可以 看文檔:像素陰影效果構造函數 (microsoft.github.io) 裡面有介紹;

 

 

 

 

 

 編譯成功

 

 四.導入編譯好的bin文件,然後右鍵文件->屬性,將複製到輸出目錄改為 始終複製;

 

 

 

 五.xaml界面

  在界面中放置了一個 CanvasControl 控制項用於顯示繪製內容,在下麵添加選擇圖片按鈕,更改閾值的slider,和兩個顏色選擇器;

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <canvas:CanvasControl x:Name="canvas"></canvas:CanvasControl>
        <StackPanel Grid.Row="1">
            <Button Content="選擇圖片" x:Name="selectPicture"></Button>
            <Slider Header="閾值:" Value="0" Maximum="1" StepFrequency="0.01" x:Name="threshold"></Slider>
            <StackPanel Orientation="Horizontal">
                <Button Content="顏色一" >
                    <Button.Flyout>
                        <Flyout>
                            <ColorPicker Color="White" x:Name="cp1"></ColorPicker>
                        </Flyout>
                    </Button.Flyout>
                </Button>
                <Rectangle Width="50" Fill="{Binding ElementName=cp1,Path=Color,Mode=TwoWay,Converter={StaticResource colorToBrush}}"></Rectangle>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Content="顏色二" >
                    <Button.Flyout>
                        <Flyout>
                            <ColorPicker Color="Black" x:Name="cp2"></ColorPicker>
                        </Flyout>
                    </Button.Flyout>
                </Button>
                <Rectangle  Width="50" Fill="{Binding ElementName=cp2,Path=Color,Mode=TwoWay,Converter={StaticResource colorToBrush}}"></Rectangle>
            </StackPanel>
        </StackPanel>
    </Grid>
xaml

六.後臺代碼

  1.在 canvas的CreateResource 事件中讀取bin文件中的位元組數組並創建一個 PixelShaderEffect 對象;

  2.添加選擇圖片事件用於選擇一張輸入的源圖;

  3.繪製圖像,effect 對象通過 Properties 鍵值對的形式對著色器裡面的屬性進行賦值。這裡需要註意 著色器裡面的顏色範圍是 0~1 並且是float類型,所以需要將c#裡面的顏色的每個分量都除以255,轉換成Vector4結構;

  4.監聽Slider 和顏色選擇器 的更改並重新繪製顯示圖像;

public sealed partial class ThresholdEffectPage : Page
    {
        PixelShaderEffect effect;
        CanvasBitmap bitmap;
        public ThresholdEffectPage()
        {
            this.InitializeComponent();
            Init();
        }

        void Init()
        {
            canvas.CreateResources += async (s, e) =>
             {
                 //獲取著色器二進位文件
                 StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Shaders/ThresholdEffect.bin"));
                 IBuffer buffer = await FileIO.ReadBufferAsync(file);
                 //轉換成位元組數組
                 var bytes = buffer.ToArray();
                 //用 位元組數組 初始化一個 PixelShaderEffect 對象;
                 effect = new PixelShaderEffect(bytes);

             };

            //選擇圖片
            selectPicture.Click += async (s, e) =>
            {
                var file = await Ulit.SelectFileAsync(new List<string> { ".png", ".jpg" });
                if (file == null)
                    bitmap = null;
                else
                    bitmap = await CanvasBitmap.LoadAsync(canvas.Device, await file.OpenAsync(FileAccessMode.Read));
                canvas.Invalidate();
            };

            canvas.Draw += (s, e) =>
            {
                //繪製黑白網格
                Win2dUlit.DrawGridGraphics(e.DrawingSession, 100);
                //判斷effect 和點陣圖是否為空
                if (effect == null || bitmap == null)
                    return;
                var element = (FrameworkElement)s;
                float effectWidth = (float)element.ActualWidth;
                float effectHeight = (float)element.ActualHeight * 0.7f;
                float previewWidth = effectWidth;
                float previewHeight = (float)element.ActualHeight * 0.3f;
                //繪製原圖
                var previewTran = Win2dUlit.CalcutateImageCenteredTransform(previewWidth, previewHeight, bitmap.Size.Width, bitmap.Size.Height);
                previewTran.Source = bitmap;
                e.DrawingSession.DrawImage(previewTran, 0, effectHeight);

                //顏色將0~255 轉換成0~1 因為hlsl裡面的顏色是0~1範圍的並且是float類型,所以這裡需要將每個顏色的分量除以255
                Vector4 startColor = new Vector4(cp1.Color.R / 255f, cp1.Color.G / 255f, cp1.Color.B / 255f, 1f);
                Vector4 endColor = new Vector4(cp2.Color.R / 255f, cp2.Color.G / 255f, cp2.Color.B / 255f, 1f);
                //通過鍵值對的形式設置屬性傳遞到著色器裡面
                effect.Properties["threshold"] = (float)threshold.Value;
                effect.Properties["startColor"] = startColor;
                effect.Properties["endColor"] = endColor;
                effect.Source1 = bitmap;

                //繪製效果圖
                var effectTran = Win2dUlit.CalcutateImageCenteredTransform(effectWidth, effectHeight, bitmap.Size.Width, bitmap.Size.Height);
                effectTran.Source = effect;
                e.DrawingSession.DrawImage(effectTran);
            };

            threshold.ValueChanged += (s, e) => canvas.Invalidate();
            //這裡遇到問題了 說找不到 WinRT.ObjectReferenceValue ABI.Windows.Foundation.TypedEventHandler`2.CreateMarshaler2(Windows.Foundation.TypedEventHandler`2<!0,!1>)'。”
            //cp1.ColorChanged += (s, e) =>
            //{
            //    //if (canvas.IsLoaded)
            //    //    canvas.Invalidate();
            //};
            //cp2.ColorChanged += (s, e) =>
            //{
            //    //if (canvas.IsLoaded)
            //    //    canvas.Invalidate();
            //};

        }
    }
ThresholdEffectPage

結語:

  現在已經將PixelShaderEffect 的整個使用過程都講解完了。下一篇講解 "ReplaceColorEffect";


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題背景 大家看看這個頁面,有沒有發現什麼問題? 主頁:http://www.javastack.cn/ 是的,頁面 CSS 樣式全丟失了,導致頁面混亂。。 這個頁面是我人為刪除了樣式(為了演示),真正出現問題是另外一個頁面,最近棧長髮現有個頁面時不時就會出現樣式錯亂的問題,很詭異!! 於是這篇就記 ...
  • JProfiler 是一個功能強大的工具,您可以使用它以動態方式分析基於 Java 的應用程式,並使您能夠分析它們以優化性能。當您配置文件時,您需要最強大的工具。同時,您不想花時間學習如何使用該工具。JProfiler 就是這樣:既簡單又強大。 Mac版詳情:JProfiler 13 for Mac ...
  • 雙向鏈表與數據結構 引言 在上小節中 我們分析了ArrayList的底層實現, 知道了ArrayList底層是基於數組實現的,因此具有查找修改快而插入、刪除慢的特點 本章我們介紹的LinkedList是List介面的另一種實現 它的底層是基於雙向鏈表實現的 因此它具有插入、刪除快而查找修改慢的特點 ...
  • 大家好,我是Mic,一個工作了14年的Java程式員。 最近很多小伙伴私信我,讓我說一些線程池相關的問題。 線程池這個方向考察的點還挺多的,如果只是靠刷面試題 面試官很容易就能識別出來,我隨便舉幾個。 線程池是如何實現線程的回收的 核心線程是否能夠回收 當調用線程池的shutdown方法,會發生什麼 ...
  • 前言 最近複習操作系統,看到了lru演算法,就去網上搜索下,因此發現了GeeCache,順手寫了一遍。研究下lru演算法的實現。 正文: lru使用map+鏈表實現。map裡面存儲了key以及其對應的鏈表節點。當我們根據某個key訪問緩存值的時候,可以經過map快速定位到該鏈表節點。從而獲取值 下麵我們 ...
  • 什麼是商城系統?商城系統又稱線上商城系統,是一個功能完善的線上購物系統,主要為線上銷售和線上購物服務。 一般的商城系統運營模式有B2C單商戶商城系統,B2B2C多商戶商城系統以及SAAS運營版。但是搭建一個商城系統過程很麻煩,這時候我們這些現成的來幫忙啦!likeshop是一個100%開源免費商用而 ...
  • 繼 Tabby、Warp 後,今天再來給大家推薦一款終端神器——WindTerm,完全開源,在 GitHub 上已經收穫 6.6k 的 star。 https://github.com/kingToolbox/WindTerm 作者還拿 WindTerm 和 Putty、xterm、Windows ...
  • 一、前言 我們在實際開發中肯定會遇到後端的時間傳到前端是這個樣子的:2022-08-02T15:43:50 這個時候前後端就開始踢皮球了,!! 後端說:前端來做就可! 前端說:後端來做就可! 作為一名有責任感的後端,這種事情怎麼能讓前端來搞呢! 還有就是Long類型的返回到前端可能會損失精度,這個情 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...