Makale Özeti

Bu makalemizde Silverlight 3.0 Beta ile beraber gelen Pixel Shader kullanımına göz atıyoruz.

Makale

Pixel Shader efektleri genelde oyun programcılarının duyduğu fakat web veya windows programcılarının pek de haşır neşir olmadıkları bir alandır. Tabi bunun birçok nedeni var; Pixel Shader efektleri HLSL (High Level Shading Language) denilen  farklı bir dil ile yazılarak DirectX'in SDK'sı ile beraber gelen bir compiler ile kullanılabilir hale geliyor. Her ne kadar HLSL C tabanlı dillere benzese de özünde esas problem bu efektlerin kullanılacağı ortamların azlığıydı.

Bundan yaklaşık 9 ay önce WPF'e SP1 ile beraber Pixel Shader efektlerinin gelmesi sonrasında sizlerle bu konuda bir yazı paylaşmıştım. Bugün ise konumuz Silverlight içerisinde Pixel Shader efeklerinin kullanımı. Silverlight 3.0 ile beraber artık istediğiniz bir Pixel Shader efektini herhangi bir UIElement yani kontrole rahatlıkla uygulayabiliyorsunuz. Hatta bu konuda Runtime ile beraber gelen hazır iki efekt bulunuyor; DropShadow ve Blur.

[XAML]

        <Button

           Content="TIKLA"

           Height="100"

           Width="100">

            <Button.Effect>

                <BlurEffect

                   Radius="10" />

            </Button.Effect>

        </Button>

Yukarıdaki XAML kodu içerisinde gördüğünüz üzere basit bir Button nesnesine Blur efekti vermek aslında gerçekten çok kolay. Blur efektinin piksellere uygulanırken ne kadar uygulanacağı ile ilgili bir de yarıçap verebiliyoruz. Her efektin kendine göre farklı parametreleri olabiliyor. Verilen bu efektler tamamen gerçek zamanlı olarak uygulandığı için gerektiğinde kod ile yaratılabilir ve kontrol edilebilirler.

[XAML]

        <StackPanel>

            <Button

               Content="TIKLA"

               Height="100"

               Width="100">

                <Button.Effect>

                    <BlurEffect

                       x:Name="btnBlur"

                       Radius="10" />

                </Button.Effect>

            </Button>

            <Slider

               Minimum="0"

               Margin="10"

               Value="{Binding Radius, ElementName=btnBlur, Mode=TwoWay}" />

        </StackPanel>

Hali hazırda herhangi bir efektin farklı özelliklerine animasyonlar uygulayabileceğiniz gibi isterseniz bu özellikleri yine Silverlight 3.0 ile beraber yeni gelen ElementBinding sistemini kullanarak editlenebilir hale de getirebilirsiniz. Yukarıdaki örneğimizde özellikle gidip BlurEffect nesnemize btnBlur ismini veriyoruz. Böylece artık bu efektin özelliklerine hem programatik olarak hem de farklı Binding'lerde rahatlıkla ulaşabiliriz.

[XAML]

        <Storyboard

           x:Name="BlurOL">

            <DoubleAnimation

               Duration="00:00:01.000"

               From="0"

               To="10"

               Storyboard.TargetName="btnBlur"

               Storyboard.TargetProperty="(BlurEffect.Radius)" />

        </Storyboard>

Örneğimizdeki BlurEffect nesnesinin Radius özelliğini hedef alıp anime eden bir animasyonun XAML kodunu yukarıda inceleyebilirsiniz. Söz konusu animasyon çalıştırıldığında düğmemiz bir saniye içerisinde yavaşça netliğini kaybediyor.

Peki hepsi bu kadar mı?

DropShadow ve Blur efektleri sadece hazır gelenler. Oysa esas güzellik bizim de kendi Pixel Shader efektlerimizi yazabiliyor olmamız. Tabi bunun için öncesinde bilgisayarınıza DirectX SDK'sını yüklemeniz ve SDK içerisindeki fxc aracını kullanabiliyor olmanız şart. Kabaca aşağıdaki komutlar ile yazmış olduğunuz bir PixelShader FX dosyasını PS dosyasına compile edebilirsiniz.

fxc /T ps_2_0 /Fo DaronEfekt.ps DaronEfekt.fx

Eğer bilgisayarınıza DirectX SDK'sını yüklemek istemiyorsanız aşağıdaki adresteki Silverlight uygulamasını kullanarak PS dosyaları hazırlayabilirsiniz. Sayfa açıldığında karşınıza çıkan ilk kutucuğa almak istediğiniz PS dosyasının adını, ikinci kutucuğa ise HLSL kodunu koymanız yeterli olacaktır. Sistem otomatik olarak PS dosyasını compile edip download linki sunacaktır.

http://www.voxpeeps.com/slpixelshadercompiler/

Pixel Shader efekti nasıl yazılır?

Bu makalenin amacı Pixel Shader nasıl yazılır sorusuna cevap vermek olmadığı gibi ben de kişisel anlamda bir Pixel Shader uzmanı sayılmam. O nedenle olabildiğince basit bir şekillde kabaca konunun üzerinden geçeceğiz.

[HLSL]

sampler2D input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR

{

    float4 Color;

    Color = tex2D( input , uv.xy);

    Color.r=1;

    return Color;

}

Yukarıda gördüğünüz Pixel Shader efektinin yaptığı tek şey kendisine verilen piksellerdeki kırmızı renk değerini maksimuma çekmek. input parametresi üzerinden piksel bilgisi, yani bir anlamda boyama fırçası gelirken uv parametresi ise offset koordinatları içeriyor. X ve Y koordinatı olarak gelen bu bilgiler 0 ile 1 arasında oluyor. Yani özünde tüm koordinatlar 0,0 ile 1,1 arasında diyebiliriz. Tüm bu bilgiler üzerinden fırçamızdaki uygun koordinatlardaki rengi almak için ise text2D metodunu kullanabiliyoruz. Elimize geçen renk değişkeni ARGB değerleri olan klasik bir Color nesnesi olarak geliyor. Böylece biz hem renk hem de koordinatlar üzerinden rahatlıkla oynama yapabiliyoruz.

Yukarıdaki efekt kendi içinde bir değer alarak kırmızı rengini 1 olarak eşitliyor. Oysa biz bu değerin yeri geldiğinde bu efekte parametrik olarak gönderilmesini de isteyebiliriz. Böyle durumlarda PixelShader kodumuzda söz konusu parametreyi de tanımlamamız şart.

[HLSL]

sampler2D input : register(s0);

int kirmizilik : register(c0);

 

float4 main(float2 uv : TEXCOORD) : COLOR

{

    float4 Color;

    Color = tex2D( input , uv.xy);

 

    Color.r=kirmizilik;

    return Color;

}

Değişkenimizi tanımlarken register ettiğimiz c0 noktasına dikkat. Bu nokta üzerinden söz konusu parametreye Silverlight tarafından da ulaşabileceğiz. Eğer farklı parametreler tanımlayaracak c1, c2 şeklinde devam edebilirsiniz. Gelin daha detalara girip makalemizi bir HLSL makalesine çevirmeden PixelShader efektimizin Silverlight tarafındaki kullanım şekline göz atalım.

PS dosyamızı Silverlight ile nasıl kullanırız?

PS dosyasını ilk olarak Silverlight projenize sağ tıklayarak gelen menüden "Add / Existing Item" diyerek eklemeniz gerekiyor. Sonrasında söz konusu PS dosyasını Solution Explorer içerisinde seçili tutup Visual Studio'nun Properties paneline geçerseniz Build Action adında bir özellik göreceksiniz. Söz konusu özelliğe "Content" değerini vererek PS dosyasının XAP içerisine alınmasını sağlayabilirsiniz. Eğer bu şekilde bir ayarlama yapmazsanız PS dosyası projede kalır fakat XAP dosyasına eklenmez ve uygulamanız da söz konusu Shader efektini kullanamaz.

[VB]

Public Class DaronEfekti

    Inherits Effects.ShaderEffect

 

    Public Sub New()

        Dim YeniEfekt As New Effects.PixelShader

        YeniEfekt.UriSource = New Uri("DaronEfekt.ps", UriKind.Relative)

        Me.PixelShader = YeniEfekt

    End Sub

 

End Class

[C#]

    public class DaronEfekti : System.Windows.Media.Effects.ShaderEffect

    {

        public DaronEfekti()

        {

            System.Windows.Media.Effects.PixelShader YeniEfekt = new System.Windows.Media.Effects.PixelShader();

            YeniEfekt.UriSource = new Uri("DaronEfekt.ps", UriKind.Relative);

            this.PixelShader = YeniEfekt;

        }

    }

Yukarıdaki kodlar içerisinde de görebileceğiniz üzere kendi Shader efektimizi yaratırken Silverlight ile beraber gelen ShaderEffect sınıfını inherit ediyoruz. Bu sınıfın kendi içerisinde zaten mevcut bir PixelShader Property'si var. Söz konusu Property'ye biz de kendi PixelShader efektimizi yaratarak veriyoruz. Kendi PixelShader efektimizi yaratırken de UriSource olarak Silverlight projemize eklediğimiz PS dosyasının relatif yolunu vermeyi unutmuyoruz. Aslında basit bir şekilde Silvelright için PixelShader efektimizi yaratma işlemini bitirdik fakat unutmayın ki bizim efektin bir de ekstra parametresi vardı. İşte bu parametre için Silverlight tarafında da bir Dependancy Property tanımlamamız gerekiyor. Özellikle Dependancy Property yaratmamızın nedeni aslında bu Property üzerinden gerektiğinde StoryBoard'ların da animasyon yapabilmesini sağlamak.

[VB]

    Public Property Kirmizilik() As Double

        Get

            Return Me.GetValue(KirmiziProperty)

        End Get

        Set(ByVal value As Double)

            Me.SetValue(KirmiziProperty, value)

        End Set

    End Property

 

    Public Shared ReadOnly KirmiziProperty As DependencyProperty = _

    DependencyProperty.Register("Kirmizilik", GetType(Double), _

                                GetType(DaronEfekti), _

                                New PropertyMetadata(0.0, PixelShaderConstantCallback(0)))

[C#]

        public double Kirmizilik

        {

            get { return (double)GetValue(KirmiziProperty); }

            set { SetValue(KirmiziProperty, value); }

        }

 

        public static readonly DependencyProperty KirmiziProperty =

            DependencyProperty.Register("Kirmizilik", typeof(double), typeof(DaronEfekti), new PropertyMetadata(0.0, PixelShaderConstantCallback(0)));

Yukarıdaki kodların normal bir Dependancy Property'den tek farkı Metadata kısmı! İşte tam da bu noktada bir PixelShaderConstantCallback görüyoruz. Söz konusu Callback nesnesine parametre olarak PixelShader HLSL kodumuzu yazarken parametremize verdiğimiz c0'ın sayısal kısmını veriyoruz :) Artık herşey hazır. Sıra geldi bu efektimizi XAML içerisinde kullanmaya.

Efektimizi tepe tepe kullanalım :)

Efektimizi kullanabilmek için ilk aşamada XAML tarafında uygulamamızın assemblysini bir XML namespace olarak tanımlıyoruz. Sonrasında söz konusu namespace üzerinden rahatlıkla efektimizi herhangi bir nesneye atayabiliyoruz. Tabi bu işlemleri C# veya VB kodu ile de yapabiliriz.

[XAML]

<UserControl

   x:Class="SilverlightApplication21.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:daron="clr-namespace:SilverlightApplication21"

   Width="400"

   Height="300">

    <UserControl.Resources>

        <Storyboard

           x:Name="BlurOL">

            <DoubleAnimation

               Duration="00:00:01.000"

               From="0"

               To="1"

               Storyboard.TargetName="Renk"

               Storyboard.TargetProperty="(DaronEfekti.Kirmizilik)" />

        </Storyboard>

    </UserControl.Resources>

    <Grid

       x:Name="LayoutRoot"

       Background="White">

        <StackPanel>

            <Button x:Name="btnTikla"

               Content="TIKLA"

               Height="100"

               Width="100">

                <Button.Effect>

                    <daron:DaronEfekti x:Name="Renk"></daron:DaronEfekti>

                </Button.Effect>

            </Button>

            <Slider

               Minimum="0"

               Maximum="1"

               Value="{Binding Kirmizilik, ElementName=Renk, Mode=TwoWay}" />

        </StackPanel>

    </Grid>

</UserControl>

Yukarıdaki XAML kodunda da inceleyebileceğiniz üzere hazırladığımız efektin özelliklerine bir Slider aracılığı ile erişebildiğimiz gibi gerekirse doğrudan söz konusu efektin bir özelliğine animasyon da verebiliyoruz. Örneğimizdeki animasyon Button kontrolünün görselliğindeki tüm kırmızılık renk değerlerini en düşük seviyesinden en yükseğe doğru anime edecektir.

Hazırı yok mu bunların?

Kendi Pixel Shader'larınızı yazmak her zaman doğru seçim olmayabilir :) Bazen başkaları yazsa da biz hemen kullansak hissiyatı kapılmamak elde değil. Esasen bu hissiyatın nedeni HLSL'in çoğumuza yabancı bir uzmanlık alanı olması. Bu çerçevede tabi ki konunun uzmanları "Özgür Yazılım" çerçevesinde :) codeplex'te açık kaynak kodları ile Silverlight için birçok Pixel Shader kütüphanesini paylaşmış durumdalar. Zaten bu kütüphanelerin neredeyse hepsi daha önce de WPF için downloada sunulmuştur.

http://wpffx.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25285

Yukarıdaki adresten Silverlight 3.0 ile uyumlu Pixel Shader kütüphanelerini indirebilirsiniz.

Hepinize kolay gelsin.