Title Subtitle
Wstęp Wykorzystywanie możliwości GPU Głównie gry Także nowe wersje programów graficznych, video (Adobe Photoshop, Ahead Nero) Będę głównie opowiadał o zastosowaniach w grach (chociaż można niektóre efekty przenieść na np. post-processing filmu)
Różne rodzaje efektów Korekcja koloru Proste filtry Rozbudowane, wieloprzejściowe filtry Efekty działające nie tylko na buforze koloru (z-buffer, bufor normalnych, etc.)
Prosty przykład
Prosty przykład <texture type="image" id="input_image" options="pict0100.jpg"/> <texture type="image" id="noise" options="noisef.png"/> <pass id="image_out" shader="post.fx" entrypoint="passthrough"> <bind semantic="sourcecolor" id="input_image"/> <target id="color_target"/> </pass> <pass id="correction" shader="post.fx" entrypoint="contrast"> <bind semantic="sourcecolor" id="scene_color"/> <target id="color_target"/> <parameter id="contrvalue" semantic="contrast" type="float" value="0.9"/> </pass> <pass id="sepia" shader="post.fx" entrypoint="sepiamode"> <bind semantic="sourcecolor" id="scene_color"/> <target id="color_target"/> </pass> <pass id="age" shader="post.fx" entrypoint="addnoise"> <bind semantic="sourcecolor" id="scene_color"/> <bind semantic="noisetexture" id="noise"/> <target id="color_target"/> </pass> <pass id="vignette" shader="post.fx" entrypoint="vignetting"> <bind semantic="sourcecolor" id="scene_color"/> <target id="color_target"/> </pass>
Implementacja Potrzebujemy odpowiednie bufory obrazu (Render Targets) Jeśli mamy więcej przejść, będziemy musieli je wymieniać ('ping-pong') Wyświetlamy dwa trójkąty pokrywające ekran Prosty VS wystarczy jeden, zmienia się tylko PS
Przykładowy VS struct VSOut { float4 hpos : SV_POSITION; float2 coords : TEXCOORD0; ; struct VSIn { uint vertexid : SV_VertexID; ; VSOut QuadVS(VSIn input ) { uint vid = input.vertexid; VSOut OUT = (VSOut) 0; if(vid ==0) { OUT.hPos = float4(-1.0f,-1.0f,0.0f,1.0f); OUT.coords = float2(0.0f,1.0f); else if(vid ==1) { OUT.hPos = float4(1.0f,-1.0f,0.0f,1.0f); OUT.coords = float2(1.0f,1.0f); else if(vid ==3) { OUT.hPos = float4(1.0f,1.0f,0.0f,1.0f); OUT.coords = float2(1.0f,0.0f); else if(vid==2){ OUT.hPos = float4(-1.0f,1.0f,0.0f,1.0f); OUT.coords = float2(0.0f,0.0f); return OUT;
Najprostsze filtry Passthrough Texture2D SourceColor; SamplerState SceneSampler = sampler_state { AddressU = Clamp; AddressV = Clamp; Filter = MIN_MAG_MIP_POINT; ; float4 PassThrough(in float2 coords :TEXCOORD0) : SV_Target { return SourceColor.Sample(SceneSampler,coords); Desaturacja float3 ToLum = {0.2125, 0.7154, 0.0721; float4 BnW(in float2 coords :TEXCOORD0) : SV_Target { return float4(dot(tolum,sourcecolor.sample(scenesampler,coords).rgb).rrr,1.0f); Zmiana nasycenia float Saturation = -1.5f; float4 AdjustSaturation(in float2 coords :TEXCOORD0) : SV_Target { float3 Color = SourceColor.Sample(SceneSampler,coords).rgb; float bnw = dot(tolum,color); float3 final = lerp(color,bnw.rrr,saturation); return float4(final,1.0f);
Najprostsze filtry Kontrast float Contrast = 1.0f; float4 AdjustContrast(float2 coords :TEXCOORD0) : SV_Target { float3 Color = SourceColor.Sample(SceneSampler,coords).rgb; float3 final = Color; final-=contrast * (Color - 1.0f) * Color *(Color - 0.5f); return float4(final,1.0f); Sepia float3 IntensityConverter1953 ={0.299, 0.587, 0.114; float3 sepiaconvert ={0.191, -0.054,-0.221; float4 Sepia(in float2 coords :TEXCOORD0) : SV_Target { float3 Color = SourceColor.Sample(SceneSampler,coords).rgb; float bnw = dot(intensityconverter1953,color); float3 final = bnw+sepiaconvert; return float4(final,1.0f);
Proste filtry Za pomocą najprostszych operacji na kolorach możemy uzyskiwać wszelkiego rodzaju efekty NightMode float4 NightColor(in float2 coords :TEXCOORD0) : SV_Target { float3 Color = SourceColor.Sample(SceneSampler,coords).rgb; float3 final = Color*float3(0.26f,0.30f,0.4f);//lerp(Color,DestColor,Factor); return float4(final,1.0f);
Proste filtry Vignette float4 Vignette(in float2 coords : TEXCOORD0) : SV_Target { float3 Color =SourceColor.Sample(SceneSampler,coords).rgb; float3 final = Color; final *= saturate(1.05f - (dot(coords.xy-0.5f,coords.xy-0.5f)/0.8f)); return float4(final,1.0f); Szum Texture2D NoiseTexture; SamplerState NoiseSampler= sampler_state { AddressU = Wrap; AddressV =Wrap; Filter = MIN_MAG_MIP_LINEAR; ; float4 Noise(in float2 coords :TEXCOORD0) : COLOR { float NoiseHigh = NoiseTexture.Sample(SceneSampler,coords).r; NoiseHigh = lerp(0.5f,1.0f,noisehigh); float NoiseLow = 1.0f-NoiseTexture.Sample(SceneSampler,coords*0.4f).r; NoiseLow*=0.1f; float3 SceneColor = SourceColor.Sample(SceneSampler,coords).rgb; float3 final = NoiseHigh.rrr*SceneColor + NoiseLow; return float4(final,1.0f);
Proste efekty - animacja Przekształcenie obrazu float Timer : TIME ; float4 Drunken(float2 coords) { float3 final = 0.0f; float2 offset = sin(coords.x*8.0f+timer*0.4f)*10.0f*texelsize; final+=sourcecolor.sample(scenesampler,coords+offset).rgb*0.6f; offset = sin(coords.y*5.0f+timer*0.3f)*10.0f*texelsize; final+=sourcecolor.sample(scenesampler,coords+offset).rgb*0.4f; return float4(final,1.0f); Przejścia kolorów, zmiana intensywności efektów efekty zależne od stanu gry Także: termowizja, nightvision, 'zakrwawienie ekranu', etc.
Złożone efekty Podstawą wielu efektów jest rozmycie obrazu Należy je zatem wykonywać bardzo szybko Wykorzystujemy wspomaganie sprzętowe Przed rozmyciem zmniejszamy obraz (2x czasami 4x) Próbkując później teksturę (przy filtrowaniu liniowym) uzyskujemy dodatkowe rozmycie Wykonujemy też 4x (16x) mniej operacji przy rozmywaniu
Rozmywanie Wykonujemy dwa przejścia pionowe i poziome Przy rozmyciu Gaussa musimy wyliczyć odpowiednie współczynniki Próbkujemy na granicach pikseli GPU pobierze oba i przefiltruje 'za darmo' dwa razy więcej próbek
Rozmywanie - najprostsze <rendertarget id="downsampled" size="half"/> <rendertarget id="downsampled2" size="half"/> <pass id="downsample" shader="post.fx" entrypoint="downsample"> <bind semantic="sourcecolor" id="scene_color"/> <target id="downsampled"/> </pass> <pass id="blurh" shader="post.fx" entrypoint="blurh"> <bind semantic="sourcecolor" id="downsampled"/> <target id="downsampled2"/> </pass> <pass id="blurv" shader="post.fx" entrypoint="blurv"> <bind semantic="sourcecolor" id="downsampled2"/> <target id="downsampled"/> </pass> float4 Downsample(in float2 coords :TEXCOORD0) : SV_Target { float3 final = 0.0f; float2 offset = TexelSize*1.0f; final+=sourcecolor.sample(scenesampler,coords+float2(1.0f,1.0f)*offset).rgb; final+=sourcecolor.sample(scenesampler,coords+float2(-1.0f,1.0f)*offset).rgb; final+=sourcecolor.sample(scenesampler,coords+float2(1.0f,-1.0f)*offset).rgb; final+=sourcecolor.sample(scenesampler,coords+float2(-1.0f,-1.0f)*offset).rgb; final/=4.0f; return float4(final,1.0f); float4 Blur(float2 coords,float2 orientation) { float3 final = 0.0f; float2 offset = TexelSize; for(int i=-2;i<=2;i++) { final+=sourcecolor.sample(scenesampler,coords+i*orientation*offset).rgb; final/=5.0f; return float4(final,1.0f); float4 BlurH_(in float2 coords :TEXCOORD0) : SV_Target { return Blur(coords,float2(1.0f,0.0f));
Depth of Field
HDR Rendering
Wykrywanie krawędzi
Screen Space Ambient Occlusion
Anti-aliasing Kiedyś: Dodatkowe RT nie mogły korzystać z AA Renderowanie do buffora obrazu, potem Idirect3DDevice9::StretchRect/glCopyTexImage DX10: Większość formatów wspiera AA (oraz filtrowanie) PS może pobierać pojedyncze próbki Jest to jednak zbyt kosztowne (kilkakrotnie więcej obliczeń) Musimy zrobić to samo co wcześniej, tylko lepiej na GPU Resolve Your Resolves Ważna jest odpowiednia kolejność! np. mapowanie tonów powinno działać na wszystkich próbkach
Tips & tricks Wiele obliczeń można przenieść do VS Wartości będą interpolowane dla wyższej jakości należy wyświetlać więcej niż dwa trójkąty
Diagram Haupttitel 10 9 8 7 6 5 4 3 2 1 0 Spalte 1 Spalte 2 Spalte 3 Spalte 4 Zeile 1 Zeile 2 Zeile 3