Posts from 2007

Lepszy font

2007-10-16 19:51

Na dzisiaj mam drobną radę, jak poprawić wygląd kodu i wygodę pracy z nim, jeżeli akurat używamy któregoś z IDE (dla jakiegokolwiek języka) pod systemem Windows. Otóż w większości wypadków domyślną czcionką dla kodu, jaka jest w takich programach używana, jest Courier New. Ma ona oczywiście stałą szerokość znaków, ale właściwie na tym jej zalety się kończą… Zwłaszcza programiści pracujący czasem w Linuxie stwierdzić mogą, że fonty tam obecne są zdecydowane ładniejsze.

“Hello World” pisane czcionką ConsolasDlatego do kodu polecam stosunkowo niedawny wynalazek Microsoftu, wprowadzony wraz z nowym Office’em – czcionkę Consolas. Jest ona wbudowana w Vistę, a jeśli używamy Windows XP, możemy się w nią zaopatrzyć na stronie MS. Wówczas do idealnego wyglądu powinniśmy jeszcze włączyć opcję wygładzania czcionek ClearType.
Przyznam, że kiedy kilka miesięcy temu zacząłem używać tej czcionki, dość trudno było mi się do niej przyzwyczaić. Teraz jednak na poczciwego Couriera patrzę już z dużym niesmakiem :) Consolas oprócz niewątpliwie większej wartości estetycznej ma też tę zaletę, że jej znaki są dość wąskie – a mimo to nadal doskonale czytelne. Sprawia to, że na ekranie mieszczą się linijki o znacznie większej długości, co dla programisty jest rzecz jasna bardzo wygodne.

Naturalnie nie twierdzę, że wszystkim akurat ta właśnie czcionka przypadnie do gustu. Zachęcam jednak, aby poeksperymentować z ustawieniami środowiska programistycznego, bo prawie zawsze można sobie choć trochę poprawić komfort kodowania.

Tags: ,
Author: Xion, posted under Programming » 8 comments

Semantyczne unikaty

2007-10-14 22:49

W języku angielskim istnieje bardzo ciekawe słowo ‘serendipity‘. W skrócie, oznacza ono “umiejętność” dokonywania szczęśliwych i ważnych odkryć całkowicie przez przypadek – w szczególności wtedy, gdy tak naprawdę szukaliśmy czegoś zupełnie innego. Najciekawszą cechą tego słowa jest fakt, że bardzo trudno przełożyć je na język inny niż angielski przy pomocy czegoś krótszego niż podana wcześniej definicja (a już na pewno nie poprzez jeden wyraz). Dlatego też w 2004 roku znalazło się ono w czołówce najtrudniejszych do przetłumaczenia słów.

Czy w językach programowania możemy spotkać się z czymś podobny? Istnieją oczywiście tzw. idiomy, nakazujące by określone czynności robić tak, a nie inaczej – jak choćby słynny idom eraseremove z STL. Jednak tutaj chodzi mi raczej o taki element języka, który ułatwia życie albo po prostu jest w jakiś sposób nietypowy i – co najważniejsze – nie jest po prostu cukierkiem składniowym: jego przełożenie na inny język wymagałoby większej ilości kodu lub byłoby po prostu niemożliwe.
Jeżeli odpowiedź to pytanie jest twierdząca, to moimi osobistymi typami są:

  • Pętla for w C++. Można ją rzecz jasna z powodzeniem symulować we wszystkich językach proceduralnych przy pomocy pętli typu while, lecz jej elastyczność jest naprawdę zadziwiająca. Prawie komicznie wygląda pętla, w której wszystkie operacje wykonywane są w nagłówku, a jej zasadnicza treść jest pustym blokiem
  • “Funkcja” list, pozwalająca rozdzielić elementy tablicy pomiędzy ustalone zmienne za pomocą jednej instrukcji. Przydatna choćby przy przetwarzaniu rekordów baz danych czy parametrów GET i POST. Jej odpowiednikiem w innych językach jest po prostu odpowiednia seria przypisań.
    1. $tab = array("one", "two", "three");   // tablica
    2. list($one, $two, $three) = $tab;   // rozdzielenie elementów na zmienne
  • Funkcja Mid z Visual Basica. Zasadniczo jej celem jest wyodrębnienie fragmentu łańcucha znaków i zwrócenie go, lecz może ona występować też po prawej stronie operatora przypisania. Wówczas służy ona do zastąpienia tegoż fragmentu innym tekstem, np.:
    1. Dim str As String
    2. str = "Ala ma kota"
    3. Mid(str, 5, 2) = "nie ma" ' i Ala już nie ma kota :)
  • Sekcje initalization i finalization w modułach Delphi. Zawarty w nich kod jest wykonywany jednokrotnie, odpowiednio: przy załadowaniu modułu oraz przy “sprzątaniu” w trakcie kończenia aplikacji. Nie jest to za bardzo przydatne w kodzie obiektowym – tym bardziej używającym odśmiecacza pamięci – ale podobny mechanizm występuje też w języku Python.
  • I wreszcie mój ulubiony trik, czyli tzw. swizzling z HLSL (a także shaderowego asemblera). Jest to sprytna i efektywna technika dostępu do składowych czteroelementowego wektora, pozwalająca dokonywać kilku operacji odczytu i/lub zapisu naraz. Wygląda to tak, że po kropce zamiast jednej literki oznaczającej składową (x, y, z lub w) podajemy kilka:
    1. v1.xyz = v1.w;  // wpisanie w do x, y i z
    2. v2.wzyx = v3.xyzw;  // "odwrócenie" wektora
    3. v4.xy = v4.zw;  // x = z i y = w

    Na pierwszy rzut oka może to wyglądać nieco zagadkowo, ale warto tej konstrukcji używać, gdyż karty graficzne dokonują swizzlingu albo za darmo, albo bardzo małym kosztem Zatem mamy tutaj elegancję, tajemniczość i efektywność w jednym, czyli to co programiści lubią najbardziej ;)

Ta lista na pewno jest o wiele za krótka. Jestem pewien, że programiści innych języków – tych co bardziej egzotycznych – mogliby dodać mnóstwo własnych typów.

Tags: , , , ,
Author: Xion, posted under Programming » 1 comment

Laptopy są sexy

2007-10-12 17:59
Carrie Bradshaw z laptopem
Carrie Bradshaw (Sarah Jessica Parker),
Seks w wielkim mieście

Komputery maleją. To pewnie nic odkrywczego, ale trudno by było temu zaprzeczyć. Już od dłuższego czasu poczciwe blaszaki z dużą prostokątna skrzynką są wręcz wypierane przez te mniejsze – o wiele szczuplejsze i, przede wszystkim, bardziej mobilne.
Właściwie to ostatnio laptop stał się prawie synonimem komputera. Wystarczy obejrzeć dowolny film, serial czy nawet reklamę, by stwierdzić, że zdecydowana większość PC-tów, jakie tam występują to właśnie komputery przenośne. I to nawet wówczas, gdy nigdy nie opuszczają one przytulnych czterech ścian.

Sam od kilku już miesięcy zastanawiałem się nad kupnem komputera przenośnego, a ostatnio rozważania te stały się nawet intensywniejsze :) Posiadanie laptopa ma sporo zalet, jak choćby to, że:

  • Można go zabrać ze sobą na uczelnię/do szkoły, co pozwala nie tracić czasu w trakcie (zbyt) licznych “okienek”. Dodatkowo jeśli jeszcze możemy skorzystać z lokalnego punktu dostępu do Internetu (standard w większości uczelni), to już nic więcej do szczęścia nie potrzeba. Ewentualnie poza nudnym wykładem, na którym można usiąść w jednym z tylnych rzędów ;]
  • Laptop przydaje się też bardzo podczas przeróżnych wyjazdów, jak choćby konferencja IGK. W końcu trzy dni bez własnego komputera to może być stanowczo zbyt długo :)
  • Wreszcie, nawet w domu notebook to przydatna rzecz. Trudno chyba wyobrazić sobie przyjemniejszą pozycję do programowania, grania, itp. niż pozycja półleżąca ;P

Toshiba Satellite A200Mając na uwadze powyższe argumenty (i pewnie nie tylko te) ostatecznie zdecydowałem się wczoraj na ten jakże ‘niezbędny’ zakup. I chociaż wybrany przeze mnie model nie jest może najwyższej jakości cudem techniki, ale do codziennej pracy czy nawet grania w niezbyt wymagające tytuły powinien w zupełności wystarczyć. Zresztą najważniejsze jest głównie to, aby bez problemów uruchomiło się na nim Visual Studio – z czym nie ma akurat najmniejszych problemów :)

Tags:
Author: Xion, posted under Computer Science & IT, Life » 8 comments

Bolączki C++ #5 – Szablonowe typedef

2007-10-10 21:32

Szablony to bardzo potężna część języka C++. Można przynajmniej powiedzieć, że spośród podobnych mechanizmów obecnych w wielu innych językach (jak np. typy generyczne z C# czy parametryzowane z Javy), daje on zdecydowanie największe możliwości. Ale, jak wiadomo, wszędzie można zawsze coś poprawić :)

Jednym z takich mankamentów jest choćby to, że póki co szablonowe – czyli opatrzone frazą template <...> – mogą być tylko funkcje i klasy (pomijając dość specyficzny przypadek szablonowych deklaracji przyjaźni). To może się wydawać absolutnie wystarczające, ale istnieje jeszcze jeden potencjalny kandydat na “uszablonowienie”. Chodzi tu o deklarację typedef.
Przydatność takiego ‘szablonowego typedef‘ ukazuje się choćby w tym – przyznam że generalnie niezbyt mądrym – przykładzie tablicy dwuwymiarowej:

  1. vector<vector<int> > mtxInts;
  2. vector<vector<float> > mtxFloats;

Nieco bardziej życiowy przypadek to zdefiniowanie własnego alokatora STL i chęć użycia go w standardowych kontenerach:

  1. list<int, my_alloc<int> > lstInts;
  2. list<string, my_alloc<int> > lstString;

Widać, że w obu sytuacjach pewne fragmenty nazw typów powtarzają się, co zresztą sprawia, że nazwy stają się dość długie. Są to więc doskonali kandydaci do aliasowania poprzez typedef, lecz jest tu jeden feler: w takim aliasie musimy dokładnie wyspecjalizować typ, któremu nadajemy nową nazwę. Nie można zatem zostawić sobie pewnej swobody w sposób, jaki się od razu narzuca:

  1. // źle!
  2. template <typename T>
  3.    typedef list<T, my_alloc<T> > my_list;
  4. // użycie: my_list<int> lstInts;

Po prostu w aktualnej wersji C++ nie ma szablonowego typedef. Obecnie można tylko zastosować pewien substytut, posługując się zwykłą definicją szablonu klasy:

  1. template <typename _T> struct my_list
  2. {
  3.    typedef list<_T my_alloc<_T> > T;
  4. };

która sprawi, że np. my_list::T będzie naszą listą intów z niestandardowym alokatorem.
Niezbyt to zgrabne, ale na razie musi wystarczyć. Poprawy życia pod tym względem należy spodziewać wraz z C++0x, acz proponowana składnia “szablonowego typedef” jest nieco inna od tej, którą zaprezentowałem wyżej.

Tags: , ,
Author: Xion, posted under Programming » 4 comments

Rzucamy cień

2007-10-09 15:55

W naturze gdy jakiś obiekt jest oświetlony, automatycznie powstaje za nim obszar cienia (a czasem i półcienia) tylko z tego powodu, że promienie świetlne są zatrzymywane przez ów przedmiot. Jest to w gruncie rzeczy bardzo proste zjawisko, lecz opiera się ono na śledzeniu torów promieni (raytracing) i dlatego wszelkie jego symulacje są kosztowne obliczeniowo. Oczywiście nie dotyczy to prawdziwych fotonów, bo one “same się śledzą” :)

Jeżeli więc chcemy mieć na scenie 3D coś, co przynajmniej stara się wyglądać jak prawdziwe cienie, musimy sami o nie zadbać. Sposobów na osiągnięcie tego celu jest oczywiście mnóstwo; różnią się one zarówno stopniem skomplikowania, jak i kosztem obliczeniowym czy “wspieralnością” przez różne karty graficzne.
Prawdopodobnie dwie najprostsze (i najszerzej obsługiwane) to:

  • Shadow maps, czyli mapy cieni. W tej metodzie najpierw renderujemy scenę patrząc na nią od strony światła i w ten sposób – przy pomocy bufora głębokości – określamy, które piksele są oświetlone. Następnie rysujemy już normalnie (z pozycji kamery) i przy pomocy jednego sprytnego porównania możemy wówczas określić, czy dany piksel jest w cieniu, czy nie.
  • Shadow volumes (“bryły cieni”) wymagają z kolei wyznaczenia figury przestrzennej określonej przez obiekt rzucający cień, promienie i płaszczyznę rzutowania cienia. Potrzebne są więc dodatkowe informacje o geometrii sceny. Zaletą jest fakt, że dla N świateł wymaganych jest tylko N+1 przebiegów renderowania.

Decyzja, którą z tych technik wybrać (czy może posłużyć się jeszcze jakąś inną), ma wbrew pozorom znacznie dalej idące konsekwencje niż tylko sam wygląd cieni czy szybkość ich generowania. W każdym przypadku inaczej wygląda bowiem ‘współpraca’ cieni z różnymi cechami materiałów, jak choćby półprzezroczystością czy mapowaniem nierówności.
Chwilowo bardziej skłaniam się ku shadow mapom z tegoż to ambitnego powodu, iż są prostsze w implementacji :) Wypadałoby jednak wpierw zrobić krótkie rozpoznanie ewentualnych problemów, jakie mogą z tego później wyniknąć…

Magiczna interpolacja

2007-10-07 16:36

Shaderami ostatni raz zajmowałem się (dość teoretycznie) parę lat temu, więc od tego czasu minęła już cała epoka – albo i kilka. W ramach przypomnienia oraz nauki HLSLa (bardzo prostego i przyjemnego języka swoją drogą) przeglądam więc implementację różnych technik graficznych, głównie oświetlenia i cieniowania.
Przy okazji natrafiłem tam na pewnego rodzaju shaderową sztuczkę. Bardziej zaawansowanym programistom grafiki pewnie nie będzie się ona wydawała niczym nadzwyczajnym. Dla mnie jednak jej pomysłowość i prostota była – że użyję modnego ostatnio słowa – porażająca :)

Otóż, jak powszechnie wiadomo, programowalny potok grafiki to przede wszystkim dwie części: vertex i pixel shader. W tym pierwszym transformujemy wierzchołki i wyliczamy inne ich dane, które potem trafiają do shadera pikseli. Ten zaś z racji częstości wykonywania musi być jak najlżejszy i dlatego jak najwięcej operacji trzeba wykonywać per vertex. Z drugiej strony, aby cokolwiek ładnie wyglądało (oświetlenie, nierówności, itd.) musi być liczone per pixel.
I tutaj pomaga część potoku włączająca się między shaderami, czyli interpolator. Jego głównym zadaniem jest interpolacja wartości koloru rozproszenia i współrzędnych tekstur na poszczególnych pikselach. To dzięki niemu piksel w środku trójkąta może mieć właściwy kolor rozproszenia i mieć przypisany odpowiedni teksel.

Interpolacja normalnychTrik polega na tym, że w interpolować możemy też inne dane. Typowy przykład to choćby pozycja w przestrzeni 3D – już przetransformowana, ale jeszcze nie zrzutowana na płaszczyznę projekcji. Podobnie może być z wektorami normalnymi. Już te dwie dane (plus kierunek światła) wystarczają, by otrzymać proste oświetlenie per pixel, które wygląda realistycznie, jeżeli tylko powierzchnia nie wymaga mapowania nierówności.
Żeby to wszystko było możliwe, wystarczy nieco oszukać sprzęt i oznaczyć określone pole wyjścia vertex shadera – zawierające np. wspomnianą normalną czy pozycję – jako… współrzędne tekstury. Wtedy zostaną poddane interpolacji i wyliczone w ten sposób wartości będą dostępne w pixel shaderze. A tam już wcale nie musimy ich traktować jako koordynaty tekstury i znów mogą być pozycją lub normalną.

W sumie wspomniane proste oświetlenie per pixel to kwestia użycia takich dwóch shaderów:

  1. float4 vLightPos; // pozycja światła
  2.  
  3. // macierze przekształceń
  4. float4x4 mtxRotation;  // obrót (dla normalnych)
  5. float4x4 mtxTranslationAndScaling; // translacja i obrót
  6. float4x4 mtxViewProjection; // widok i projekcja
  7.  
  8. // wyjście VS i wejście PS
  9. struct Vertex2Pixel
  10. {
  11.    // zwykłe dane wierzchołka
  12.    float4 Pos   : POSITION;
  13.    float4 Diffuse : COLOR0;
  14.    float2 Tex0   : TEXCOORD0;
  15.    
  16.    // dane dla oświetlenia per pixel
  17.    float3 Normal : TEXCOORD1;
  18.    float3 Pos3D : TEXCOORD2;
  19. };
  20.  
  21.  
  22. // vertex shader
  23. Vertex2Pixel VS(float4 inPos : POSITION, float4 inDiffuse : COLOR0,
  24. float4 inNormal : NORMAL, float2 inTex0 : TEXCOORD0)
  25. {
  26.    Vertex2Pixel v2p = (Vertex2Pixel)0;
  27.  
  28.    // liczenie macierzy transformacji (preshader)
  29.    float4x4 mtxWorld = mul(mtxRotation, mtxTranslationAndScaling);
  30.    float4x4 mtxWVP = mul(mtxWorld, mtxViewProjection);
  31.  
  32.    // transformacja wierzchołka
  33.    v2p.Pos = mul(inPos, mtxWVP);
  34.    v2p.Pos3D = v2p.Pos;   // dla interpolacji
  35.  
  36.    // transformacja (obrót) normalnych
  37.    v2p.Normal = mul(inNormal, mtxRotation); // też będzie interpolowane
  38.  
  39.    // reszta
  40.    v2p.Tex0 = inTex0;
  41.    v2p.Diffuse = inDiffuse;
  42.    return v2p;
  43. }
  44.  
  45. // pixel shader
  46. sampler Tex;
  47. float4 PS(Vertex2Pixel inV2P) : COLOR0
  48. {
  49.    Pixel px = (Pixel)0;
  50.  
  51.    // obliczenie oświetlenia dla piksela
  52.    // (korzystamy z interpolowanej pozycji piksela i z interpolowanej normalnej)
  53.    float3 vPixel2Light = normalize(vLightPos - inV2P.Pos3D);
  54.    float fLightFactor = dot(vPixel2Light, inV2P.Normal);
  55.  
  56.    // liczymy kolor (tekstura + kolor rozproszenia + oświetlenie)
  57.    float4 clDiffuse = tex2D(Tex, inV2P.Tex0) * inV2P.Diffuse * fLightFactor;
  58.    return clDiffuse;
  59. }

Oświetlenie per pixel
Różnica między oświetleniem wierzchołkowym a pikselowym
Źródło

Oczywiście nie ma tutaj wygaszania ani kontroli kształtu światła, ale to i tak długi przykład ;) Widać jednak, że to co kosztowne – przekształcenia macierzowe – są wykonywane dla wierzchołków, a nie pikseli. W pixel shaderze liczymy tylko oświetlenie, a to – jak wiemy i widzimy powyżej – tylko kwestia odpowiedniego iloczynu skalarnego. Możemy go obliczyć, bo w wyniku interpolacji mamy zarówno pozycję piksela w przestrzeni, jak i jego normalną.

A efekty wyglądają mniej więcej tak:

Screen prostego oświetlenia per pixel Screen prostego oświetlenia per pixel

Świadomość wyboru

2007-10-06 12:34

Każdy programista posługuje się zestawem narzędzi służącym mu do pracy: językiem programowania, środowiskiem developerskim, kompilatorem, debugerem, itd. Są to takie same atrybuty jak ołówek dla rysownika czy hebel dla stolarza. Jako podobne do takich właśnie przedmiotów produkty, mogą być one oceniane pod względem różnych kryteriów, w tym najważniejszego – użyteczności.

Narzędzia, jakimi się posługujemy jako koderzy, powinny więc być przede wszystkim adekwatne do sytuacji, w której się znajdujemy. Zawsze bowiem tworzymy coś przeznaczonego do działania w określonym kontekście – środowisku, platformie sprzętowej czy we współpracy z innymi programami. Jednocześnie nakładamy też pewne wymagania na produkt wynikowy czy też na sam proces jego tworzenia.
Możemy na przykład chcieć, by był on maksymalnie efektywny albo zajmował jak najmniej pamięci operacyjnej. Innym wymaganiem może być szybkość realizacji projektu – co zwykle oznacza, że pisząc nasz program, chcemy namęczyć się jak najmniej i skorzystać z jak największej ilości istniejącego już kodu. Wreszcie może nas interesować też elegancja wynikowego kodu źródłowego – chociaż na nią największy wpływ mamy sami.

W teorii właśnie takimi przesłankami powinniśmy kierować się, gdy przychodzi nam wybrać narzędzie (na przykład język programowania) pomocne w tworzeniu. Oczywiście możemy do nich dodać własne – łącznie z tym dla niektórych najważniejszym: czy dane narzędzie znam wystarczająco dobrze i/lub czy chcę się go (pod/na)uczyć. Grunt żebyśmy byli świadomi powodów, dla których decydujemy na taki a nie inny wybór.
Dotyczy to nawet tych mniej racjonalnych powodów w rodzaju: “bo ‘wszyscy’ tego używają”, “bo dany język/biblioteka/itp. po prostu mi się podoba”, “bo przyjemnie mi się w tym pisało”, itp. Nie muszą one wcale być gorsze od tych solidnie ufundowanych i sprawdzonych argumentów. Jeżeli bowiem żadne zewnętrzne i niezależne od nas okoliczności nas nie ograniczają, powinniśmy dążyć do jak największej satysfakcji z tworzenia – zarówno z samego procesu, jak i z osiąganych rezultatów.

Tags: ,
Author: Xion, posted under Applications, Programming, Thoughts » 9 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.