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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...