Grafika 3D
 
  Zarejestruj się
::  Newsy  ::  Pliki  ::  Twoje Konto  ::  Forum  ::
Menu
· Strona główna
· Forum
· Linki
· Lista u?ytkowników
· O nas...
· Pliki
· Statystyki
· Twoje Konto
Tutoriale
· API
· Matematyka
· Teoria
· Direct3D
· OpenGL
· Techniki
Kto Jest Online
Aktualnie jest 48 gość(ci) i 0 użytkownik(ów) online.

Jesteś anonimowym użytkownikiem. Możesz się zarejestrować za darmo klikając tutaj
Tutoriale - OpenGL - Prymitywy

Witam w kolejnej lekcji poświęconej programowaniu grafiki za pomocą biblioteki OpenGL. Dzisiaj zapoznamy się bliżej z wierzchołkami, narysujemy więc wreszcie coś konkretnego na ekranie a także poznamy trochę uboższy odpowiednik biblioteki D3DX w OpenGL czyli glUtils.
Ponieważ OpenGL, jak wiadomo powszechnie i czemu nie da się zaprzeczyć w żaden sposób, jest biblioteką, którą w pewnych aspektach jest o wiele łatwiej się posługiwać niż Direct3D, więc odbija się to na kodzie programu. Jak wiemy, jest ona napisana tak, aby można było kompilować programy napisane w czystym języku C, a co bardziej interesujące, napisany kod jest w pełni przenośny na inne systemy operacyjne a nawet platformy sprzętowe. Jest to niewątpliwie ogromna zaleta OpenGL-a, która jednak przy próbie bardziej zaawansowanych operacji może nam czasem wyjść bokiem, ale o tym będziemy jeszcze mieli okazję przekonać się nie raz. W naszym dzisiejszym przykładzie, jak zaraz będzie widać, nowego kodu będzie wręcz śmiesznie mało, tak że na początku nawet trochę się obawiałem czy lekcja nie zmieści mi się na jednej stronie ;-). No ale może będzie co poczytać a co ważniejsze nawet, będzie można się nauczyć coś więcej niż tylko tworzyć wierzchołki.

Zacznijmy może nie od samej geometrii, ale od tego, co będzie nam pomagać, czyli glUtils. Jak sama nazwa wskazuje, będzie to biblioteka narzędzi, które będą nas wspomagać w zmaganiach z czystym OpenGL-em i skutecznie ułatwią pewne operacje, tak samo jak w przypadku Direct3D i jego osławionej już przeze mnie biblioteki D3DX. Tak więc OpenGL Utility (bo tak brzmi pełna nazwa tej biblioteki) zawiera kilka grup funkcji, które zawierają w sobie z kolei "czyste" funkcje OpenGL wykonujące pewne, nieraz żmudne operacje. Ponieważ funkcje z "utilsów" są oparte na czystym kodzie OpenGL, więc oczywiste wydaje się, że na każdej platformie, na której da się skompilować program OpenGL, biblioteka glUtils także będzie obecna i możliwa do wykorzystania. Wszystkie funkcje w bibliotece narzędziowej noszą, w przeciwieństwie do "czystych" funkcji OpenGL-a, przedrostki "glu" zamiast "gl", co będzie od razu widać w przykładowych programach. Opis ich wszystkich znajdziecie w różnorakich dokumentacjach, wielu z nich na pewno nie użyjemy, ale te, których będzie okazja użyć, opiszę na pewno Wam bardzo dokładnie, jak to już mamy w zwyczaju. Funkcje te będą miały najróżniejsze zastosowania począwszy od tworzenia konkretnych macierzy do tworzenia powierzchni NURBS, teslacji obiektów i innych niebanalnych operacji ;-). Ale zaczniemy oczywiście od podstaw.

Następna sprawa będzie już trochę bliższa rysowaniu i naszemu światu 3D. Chodzi mianowicie o kamerę a dokładniej o macierze. OpenGL różni się nieco w swojej filozofii od DirectX. Nie chodzi tutaj o sposób, bo ten wszędzie jest taki sam - reprezentacja przekształceń odbywa się zawsze za pomocą macierzy. W DirectX mamy wszystko czarno na białym, trzy macierze reprezentujące trzy niezbędne przekształcenia - świata, widoku i rzutowania. Ktoś pewnie zada pytanie, dlaczego przy wierzchołkach rozpatruję sprawę macierzy - przecież na to mamy jeszcze czas. Otóż muszę powiedzieć, że nie do końca. OpenGL z samego swojego założenia służy do tworzenia i obsługi grafiki 3D. O efektywnych operacjach 2D, z jakimi doczynienia mieliśmy na przykład w DirectDraw, można sobie tylko pomarzyć. Oczywiście OpenGL ma funkcje do obróbki 2D, jednak powiem szczerze od razu na początku, że są one dla nas zupełnie dzisiaj bezużyteczne ze względu na swoją szybkość. W naszej działalności oczywiście największy nacisk kładziemy na szybkość obliczeń a z tym nie jest najlepiej jeśli chodzi o 2D w OpenGL. Dlatego też, chcąc zaprezentować najprostszy przykład z udziałem wierzchołków, będę musiał od razu w OpenGL ustawić macierz, co nie było konieczne w przypadku DirectX - tam nie ustawialiśmy żadnych macierzy a współrzędne wierzchołków odpowiadały po prostu współrzędnym punktów w oknie. W OpenGL niestety żadne zmienne przy starcie nie mają ustalonych wartości, więc o wszystko musimy zadbać sami. Ale spokojnie, do wszystkiego dojdziemy po kolei.
Zanim jednak przystąpimy do szaleństw tworzenia trójkątów, jeszcze jedna sprawa. Chodzi o rzutowanie brył podczas rysowania. Ponieważ nasz przykład zaprezentuje nam płaskie trójkąty na ekranie a ja nie chcę Wam od razu mieszać z perspektywą i tym podobnymi rzeczami, więc udamy się w naszym przykładzie po pomoc do biblioteki narzędziowej glUtils. Za jej pomocą będziemy mogli ustawić sobie na scenie tzw. rzutowanie ortogonalne. Co to znaczy? Ano w naszych obliczeniach nie będzie uwzględniana perspektywa - wszystko co narysujemy, bez względu na odległość, zawsze będzie miało ten sam wymiar współrzędnej z. Jednocześnie zachowamy możliwość przykrywania niewidocznych powierzchni, które leżą za innymi. To będzie coś na kształt rysunku technicznego - takie dobrze nam znane rzutowanie prostokątne ino, że na monitorze komputera.

No dobrze, skoro wiemy już co nas czeka, możemy powoli przystąpić do prób narysowania czegoś na ekranie. Zanim oczywiście przystąpimy do rysowania, musimy ustawić format pikseli dla naszego okna, co mamy już dokładnie opisane w poprzedniej lekcji. Umówmy się od razu na początku, że aby nie wałkować wiele razy tego samego materiału, omówione już fragmenty kodu, sztuczki itp. będziemy po prostu omijać z daleka - mnie pójdzie wszystko o wiele szybciej a Wam oszczędzi nudy i wodolejstwa. A wracając do naszych rozważań...
Załóżmy, że format ustawiony, okno zainicjalizowane i wyświetlone. Logicznie nasuwającą się rzeczą byłoby teraz narysowanie czegoś na ekranie. Zanim jednak przystąpimy do wielkiego malowania trzeba zrobić jeszcze to, o czym mówiłem wcześniej - zainicjalizować naszą macierz, która umożliwi uwidocznienie czegokolwiek na ekranie a przy okazji ustawić jeszcze kilka rzeczy.
void Render()
{
    glViewport( 0, 0, 300, 300 );
    
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluOrtho2D(-2.0f, 2.0f,-2.0f, 2.0f );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glBegin( GL_TRIANGLES );
        glColor3f( 1.0f, 0.0f, 0.0f );
        glVertex3f(-1.0f,-1.0f, 0.0f );
        glColor3f( 0.0f, 1.0f, 0.0f );
        glVertex3f( 1.0f,-1.0f, 0.0f );
        glColor3f( 0.0f, 0.0f, 1.0f );
        glVertex3f( 0.0f, 1.0f, 0.0f );
    glEnd();
    glFlush();
    SwapBuffers( hDC );
}
Przyjrzyjmy się powyższej funkcji. Nie da się ukryć - wszystko zaczyna się od "gl" :-), więc zaczynamy naprawdę dobrą zabawę. Jak się okaże już za moment, będziemy mieli do czynienia także po raz pierwszy z biblioteką glUtils, która użyczy nam swojej pomocy nie po raz ostatni. Ale żeby już dłużej nie marudzić spójrzmy, co my tu mamy.

glViewport( 0, 0, 300, 300 );

Jak na razie czysta funkcja gl-owa. Sądząc po nazwie, ma ona coś wspólnego z jakimś widokiem. Jeśli zajrzeć do dokumentacji, to dowiemy się, że funkcja ta dokonuje afinicznej transformacji ze znormalizowanych współrzędnych urządzenia na współrzędne okna - brrr! Co to w ogóle znaczy? Na nasz prosty język jest to nic innego, jak tylko ustalenie w naszym oknie, dopiero co stworzonym pewnego obszaru, który nie musi się pokrywać całkiem z obszarem klienta, w którym my będziemy sobie wyświetlać nasz obraz. Możemy sobie na przykład wymyślić, że wyrenderujemy naszą scenę tylko w jednej ćwiartce naszego okna a resztę poświęcimy na jakieś informacje albo zostawimy w ogóle w spokoju. Tym samym ustalamy po prostu rozmiar naszego widoku, który w rzeczywistości najczęściej będzie się pokrywał z obszarem klienta, aby rendering odbywał się w całym oknie.

glMatrixMode( GL_PROJECTION );

Następna funkcja "czysto gl-owa". Tutaj trzeba wspomnieć trochę o zasadzie działania OpenGL-a, która w pewnych aspektach jest trochę inna od DirectX. Weźmy dla przykładu macierze. W DirectX tworzyliśmy zmienną odpowiedzialną za macierz, ustawialiśmy jej jakieś wartości i taką przekazywaliśmy urządzeniu do zastosowania odpowiedniej transformacji. W OpenGL jest inaczej. Macierze, które zmieniamy, przynajmniej jeśli chodzi o te podstawowe, niejako istnieją w programie od początku bez naszej wiedzy. Są wewnętrznie przechowywane przez bibliotekę, choć my możemy je oczywiście modyfikować. Aby to jednak zrobić musimy powiedzieć programowi, którą z macierzy chcemy akurat zmienić. Do tego właśnie służy funkcja glMatrixMode(). Jako swój parametr oczekuje ona oznaczenia macierzy, którą my pragniemy sobie zmienić. Tak naprawdę OpenGL przetwarzając program, posiada pewien zestaw stałych definiujących w określonym momencie zachowanie się biblioteki - inaczej mówiąc OpenGL jest tak zwaną "maszyną stanu". Krótko mówiąc maszyna ta pracuje sobie cały czas a my zmieniając coś w jej konfiguracji powodujemy odpowiednie jej zachowanie. Do jednego z takich parametrów należy bieżąca macierz, która w OpenGL może być tylko jedna. Funkcja powyższa powoduje to, że ustawiamy dla naszej maszyny jako bieżącą macierz określoną jako jej parametr - w naszym przypadku będzie to macierz odpowiedzialna za projekcję (rzutowanie) wierzchołków na scenie. Kiedy wywołamy tę funkcję, biblioteka niejako nastawi nam tę macierz i każdą operację jaką wykonamy za pomocą funkcji do operacji na macierzach, będzie dotyczyła tej właśnie macierzy.

glLoadIdentity();

Pierwszą z takich funkcji jest właśnie ta. Zastępuje ona bieżącą macierz (u nas jest to w tej chwili macierz odpowiedzialna za projekcję) macierzą jednostkową, czyli tak naprawdę po prostu ją czyści. Przyzwyczajmy się do tego od samego początku, że w większości przypadków przed przystąpieniem do ustalenia parametrów macierzy najlepiej na początku wyczyścić ją dla pozbycia się kłopotów.

gluOrtho2D(-2.0f, 2.0f,-2.0f, 2.0f );

No i pierwsza funkcja z naszej biblioteki narzędziowej. Wspominałem już wyżej o sposobie rzutowania wierzchołków, jakie zastosujemy sobie dzisiaj (czyli o rzutowaniu prostokątnym). Oczywiście moglibyśmy ręcznie sobie wypełnić macierz projekcji potrzebnymi wartościami, uprzednio projektując taką macierz, która określiła by nam takie, a nie inne rzutowanie. Ale przecież po to wymyślono biblioteki, żeby z nich korzystać, prawda? Zamiast więc babrać się w jakieś chore obliczenia, my po prostu sobie sięgniemy i gotowe ;-). Funkcja gluOrtho2D() ustawia projekcję prostopadłą, jak już powiedzieliśmy chyba z dziesięć razy, a jako swoje parametry przyjmuje współrzędne płaszczyzn obcinających (definiujących bryłę widzenia) kolejno od lewej strony, prawej, dołu i góry. Ponieważ rzutowanie będziemy mieli prostopadłe, więc i płaszczyzny obcinające będą w takim stosunku do powierzchni ekranu, dlatego wystarczy podać po prostu ich jedną współrzędną. Dla płaszczyzn prawej i lewej będzie to ich położenie na osi X, natomiast dla górnej i dolnej, jak łatwo wywnioskować, na osi Y. Ponieważ jako aktualną macierz mamy ustawioną w dalszym ciągu projekcję, więc wyliczone wartości wędrują oczywiście do tej właśnie macierzy - a o to nam właśnie chodziło!

glMatrixMode( GL_MODELVIEW );

Po raz kolejny wywołujemy funkcję do zmiany stanu naszego urządzenia. Tym razem do zmiany przywołujemy sobie znowu macierz, ale z innym parametrem - tym razem będzie to macierz odpowiedzialna za widok i transformacje naszego modelu. Ustawiamy ją jako bieżącą i teraz z kolei wszystko, co zrobimy funkcjami dotyczącymi macierzy, wpłynie na tę właśnie macierz widoku.

glLoadIdentity();

A ponieważ my tak naprawdę z naszym obiektem nie będziemy robić nic, bo ten będzie sobie spokojnie stał w miejscu, a nie szalał po scenie, więc po prostu naszą macierz wyczyścimy, aby żadne transformacje się go nie imały i nie wprowadzały nam na scenę zamieszania.

glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );

Po ustawieniu wszystkich macierzy jak należy, możemy przystąpić do malowania. Zawsze to lepiej, przed przystąpieniem do tej jakże fascynującej czynności, wyczyścić sobie tło na którym się maluje i to jeszcze jakimś fajnym kolorem ;-). Jak pamiętamy maszyna OpenGL pracuje już pełną parą, więc można ustawić jej kolejne parametry - tym razem będą to składowe koloru, jakimi zostaną wypełnione bufory, odpowiedzialne za przechowywanie koloru na scenie. Po wywołaniu tej funkcji OpenGL zapamięta sobie gdzieś wewnątrz te trzy składowe i tylko będzie czekał na wywołanie funkcji:

glClear( GL_COLOR_BUFFER_BIT );

Widząc to OpenGL przystąpi oczywiście do działania. Zacznie czyścić bufory, które są wyspecyfikowane jako parametry funkcji - w naszym przypadku będzie to tylko bufor koloru, bo inne do niczego nie są nam teraz potrzebne. Wykorzysta oczywiście wcześniej ustawione wartości koloru, aby bufor koloru zawierał tylko takie, które po narysowaniu sceny dadzą konkretny efekt - ustawiony kolor.
glBegin( GL_TRIANGLES );
    glColor3f( 1.0f, 0.0f, 0.0f );
    glVertex3f(-1.0f,-1.0f, 0.0f );
    glColor3f( 0.0f, 1.0f, 0.0f );
    glVertex3f( 1.0f,-1.0f, 0.0f );
    glColor3f( 0.0f, 0.0f, 1.0f );
    glVertex3f( 0.0f, 1.0f, 0.0f );
glEnd();
Po wyczyszczeniu sobie naszego tła rysunkowego nie pozostanie nam już nic innego, jak w końcu dobrać się do tych wszystkich wierzchołków, ścian, tekstur i shaderów. No ale bez szaleństw, może na początek i zajmijmy się podstawą wszystkiego, czyli wierzchołkami. W rysowaniu sceny zarówno OpenGL i jak Direct3D są bardzo do siebie podobne. Podejrzewać należy oczywiście, że z racji swojego wieku to Direct3D przejął od swojego konkurenta sposób malowania, ale roztrząsanie tutaj takich kwestii nie jest naszym zadaniem. Nas interesuje tylko jedno - jak? Malowanie odbywa się pomiędzy wywołaniem pary funkcji - glBegin() i glEnd(). Wszystko, co znajdzie się pomiędzy nimi, powinno służyć tylko i wyłącznie do narysowania czegoś na ekranie i do zmiany sposobu tego rysowania. Jednak patrząc na przykładowe najprostsze programy z wykorzystaniem jednej i drugiej biblioteki da się zauważyć kilka dosyć istotnych różnic. Po pierwsze przyjrzyjmy się bliżej funkcji glBegin(). Jej odpowiednik u konkurencji nie posiada żadnego parametru, tutaj mamy, jak łatwo się domyśleć, specyfikację rodzaju prymitywu, jaki będziemy rysować do momentu wywołania siostrzanej funkcji glEnd(). Rodzaje prymitywów są dokładnie takie same wszędzie, więc i różnic w sposobie ich rysowania także darmo szukać - oczywiście po ich rodzaje można sobie zaglądnąć do dokumentacji, choć zapewne doskonale je znacie.

glColor3f( 1.0f, 0.0f, 0.0f );
glVertex3f(-1.0f,-1.0f, 0.0f );

Następnie przychodzi czas na właściwe rysowanie, czyli definicję geometrii. Ponieważ na pierwszy raz nie będziemy od razu wprowadzać zamieszania z rozszerzeniami i listami wyświetlania, (na to przyjdzie czas) więc do tego czasu przyjdzie nam ręcznie dla naszych potrzeb klepać poszczególne wierzchołki. Ponieważ nie mamy do dyspozycji jak na razie rzeczy o których mowa przed momentem, (tak naprawdę to mamy, tylko ich nie potrafimy jeszcze wykorzystać, ;-) więc trzeba pomiędzy parą glBegin() i glEnd() stworzyć całą geometrię, którą one rysują. Każdy wierzchołek w OpenGL może być stworzony na wiele sposobów, choć zapewne w większości dokumentacji znajdziemy tylko odnośnik do funkcji glVertex(), która jednak potem nam się rozwinie w koszmarnie długą listę wszelakich modyfikacji tejże właśnie. Dostaniemy glVertex2d, glVertex2f, glVertex2i... i tak to poleci poprzez wszystkie 2, potem 3 a na końcu 4 - po co to wszystko? Ano, ponieważ nie zawsze będziemy potrzebowali wszystkich współrzędnych wierzchołka, dla przykładu w naszej aplikacji znaczenie będą miały tylko dwie: x i y, więc projektanci biblioteki wymyślili sobie, że dadzą nam do ręki wszystkie możliwe kombinacje, żebyśmy mogli sobie z nich skorzystać. Możemy sobie zatem definiować wierzchołki o dwóch, trzech a nawet czterech współrzędnych, gdyby ktoś potrzebował ;-). Dla naszego przykładu, ponieważ wszystko i tak jest prostopadłe, więc dwie współrzędne wystarczą. Kogoś mogą jeszcze zastanawiać te dziwne literki na końcu nazwy każdej funkcji. Ponieważ współrzędne wierzchołków możemy sobie zachcieć podać jako różnorakie liczby różnych typów, więc i tutaj, dla każdego typu mamy adekwatną funkcję, przyjmującą określony typ. Czym jest to podyktowane, powiem Wam szczerze, że nie mam pojęcia, choć podejrzenia są oczywiście skierowane w stronę maksymalnej optymalizacji kodu, aby bez zbędnych późniejszych operacji można było korzystać z danych. Ponieważ jednak my przy obiektach najczęściej korzystać będziemy ze współrzędnych typu float, więc umówmy się na początek, że używać będziemy funkcji z końcówką "f".
Tak samo, jak w przypadku wierzchołków, dokumentacja zaskoczy nas jeśli chodzi o funkcję glColor(). Dostaniemy całe multum różnych nazw złożonych z nazwy właściwej i adekwatnych końcówek, często nie mówiących zbyt wiele. W tym przypadku zamiast współrzędnych podawać będziemy oczywiście składowe koloru a w przypadku funkcji z końcówką liczbową "4", jeszcze wartość składowej "alfa", która posłuży nam do definicji przezroczystości wierzchołków. Pewien niepokój mogą wzbudzać także wartości poszczególnych składowych, zwłaszcza podawanych jako liczby typu float, zwłaszcza że do tej pory mieliśmy raczej do czynienia z wartościami z przedziału 0 - 255. Z jednej strony taka postać jest dobra, z drugiej nie bardzo. Dobra jest dlatego, ponieważ jako liczba float nasz kolor może być podany w zasadzie o dowolnej wartości, na przykład 0.5432 - co z tym zrobi OpenGL? Najprawdopodobniej zaokrągli tę wartość do najbliższej możliwej, wynikającej z ustawionego aktualnie trybu koloru, z tym że zaznaczyć od razu trzeba ważną sprawę - wartość 1.0 odpowiada maksymalnej wartość konkretnej składowej koloru - jeśli podamy więcej, nie wpłynie to w jakimś większym znaczeniu na kolor niż gdybyśmy podali wartość 1.0, 0.0 to oczywiście wartość najniższa.
A co jeśli chcielibyśmy i uparli się, aby podawać kolor w znanym i bardziej przyjaznych wartościach? Ano nic trudnego, trzeba tylko zmienić dwie literki w nazwie funkcji i gotowe - zaznajomionych już z funkcjami GDI na pewno ucieszy na przykład funkcja o nazwie glColor3ub().

No i to jak na razie wszystko, jeśli chodzi o wierzchołki. Aby go narysować najpierw należy ustalić mu kolor jedną z dziesiątek funkcji z rodziny glColor() a następnie po prostu zdefiniować mu współrzędne. Tutaj także od razu mała uwaga. Ponieważ mamy rzutowanie prostokątne, więc kwestia zakresu współrzędnych wychodzi natychmiast. Ponieważ współrzędne bryły obcinania, jak widać w kodzie, wynoszą odpowiednio -2 do 2 (funkcja gluOrth2D()), więc i współrzędne wierzchołków jeśli będą się mieścić w tym zakresie, to będą na pewno na scenie widoczne. Jeśli ze współrzędnymi pójdziemy gdzieś w las, to oczywiście już ich nie będzie. Tą technikę na pewno jeszcze wykorzystamy, bawiąc się w 2D, żeby udowodnić, że jednak OpenGL się do tego nadaje ;-).

Po zdefiniowaniu wszystkich wierzchołków i zamknięciu ich w parze glBegin() i glEnd(), czas wywołać kilka ostatnich funkcji, które już w pełni pozwolą nam się rozkoszować widzialnymi efektami naszej działalności.

glFlush();

Funkcja ta powoduje opróżnienie wszystkich buforów OpenGL i przeniesienie ich zawartości na ekran, czyli po prostu ich narysowanie. Każda operacja rysunkowa w OpenGL odbywa się w jakiś tam buforze. I rysowanie kolorowych wierzchołków, sprawdzanie współrzędnej z, operacje w buforze szablonu (Stencil Buffer) i mnóstwo innych. Wynik tych operacji nie jest od razu przekazywany ekran, bo mielibyśmy straszną kaszanę i miganie, ale jest przechowywany w specjalnych buforach, do czasu kiedy uznamy, że to co w nich jest nadaje się do wyrzucenia na ekran. Jeśli uważamy, że się nada, wtedy przedstawiamy światu naszą twórczość a bufory stają w gotowości na przyjęcie nowej dawki danych.

SwapBuffers( hDC );

O tej metodzie zdaje się, że już wspominaliśmy - to nasze rozszerzenie systemowe, ponieważ piszemy pod Windows, więc czemu mamy sobie nie skorzystać. Funkcja powoduje wymianę tylnego i przedniego bufora w celu wyświetlenia naszej mini-animki, która tak naprawdę jest statycznym obrazem. Dzięki podwójnemu buforowaniu obraz nam nie miga i wszystko ładnie i pięknie się prezentuje, tak jak na rysunku poniżej.



Kod źródłowy

©Copyright by Robal   



Tutoriale - OpenGL
Nasze newsy s� w RSS: backend.php
PHP-Nuke Copyright © 2005 by Francisco Burzi. This is free software, and you may redistribute it under the GPL. PHP-Nuke comes with absolutely no warranty, for details, see the license.
Tworzenie strony: 0.05 sekund

:: Layout strony został stworzony przez www.nukemods.com w oparciu o styl phpbb2 Helius, którego autorem jest Cyberalien ::