Задача: создать 3D-часы. Пример инициализации окна DirectX под Windows NT. Задача состоит в описании простой 3D-модели и успешном её рендеринге. В качестве модели решено было сделать часы, кроме того добавлен глобальный цикл с анимацией "ходьбы" часов. И в заключение -- переход в/из полноэкранный режим. Примерная структура программы такова: 1. Инициализируем окно Windows. Настраиваем его для дальнейшего преобразования в DirectX-совместимое. Для работы exe-шника вам понадобится DirectX 9, а так же видеокарта с поддержкой сглаживания 4х. Кроме того может понадобится Visual C++ 2008 Redistributable Package. // Подключаем необходимые библиотеки #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #pragma comment(lib, "winmm.lib") #include <windows.h> #include <d3d9.h> #include <d3dx9.h> #include <MMSystem.h> HWND hInstance1; LPDIRECT3D9 pDirect3D = NULL; // интерфейс Direct3D LPDIRECT3DDEVICE9 pDirect3DDevice = NULL; // DirectX device LPDIRECT3DVERTEXBUFFER9 pBufferVershin = NULL; // буфер вершин для кольца LPDIRECT3DVERTEXBUFFER9 pBufferVershinArrows = NULL; // для стрелок LPDIRECT3DVERTEXBUFFER9 pBufferVershin2 = NULL; // для ещё одного кольца bool FULLSCREEN = false; // полноэкранный режим, получаем это значение из аргументов, передаваемых с ехе-шником int WINDOW_WIDTH = 500; // ширина окна int WINDOW_HEIGHT = 500; // высота окна bool check3000 = true; // контролирует был ли уже проведёт рандомный поворот матрицы int randCheck = 0; // вспомогательная переменная, random -- true или false. Зря она в глобальных объявлена. // Вершина struct CUSTOMVERTEX { FLOAT X, Y, Z; DWORD color; }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE) // задаем тип вершины #define CIRCLE_RES 60 // встроенной функции для рисования кольца нет // поэтому придётся рисовать его вручную. Эта константа задаёт "разрешение" // кольца -- из скольки секторов он будет состоять. /*class MyArrow { public: CUSTOMVERTEX points[6]; float x, y, fi, ro; MyArrow(float xT, float yT, float roT); void SetAngle(float fiT); }; class MyClock { public: float r; MyArrow hour, minute, second; };*/ // Крутим, вращаем наши часы VOID Matrix() { // Задаём матрица Мира, Вида и Проекции D3DXMATRIX MatrixWorld, MatrixWorldX, MatrixWorldY, MatrixWorld2; D3DXMATRIX MatrixView; D3DXMATRIX MatrixProjection; // MatrixWorld UINT Time = timeGetTime() % 24000; UINT Time2 = timeGetTime() % 60000; FLOAT Angle = (sin((2.0f * D3DX_PI)*(Time2 / 60000.0f)))/4; //(2.0f * D3DX_PI)*(Time / 25000.0f); FLOAT Angle1 = (cos((2.0f * D3DX_PI)*(Time2 / 60000.0f)))/4 + D3DX_PI; float Angle2=0; if(check3000 && Time >= 15000) { randCheck = Time % 2; check3000 = false; } else if(!check3000 && Time >= 3000 && Time<15000) { randCheck = Time % 2; check3000 = true; } // Если пришло время сменить направление движения матрицы, то меняем соответствующие углы if(randCheck) Angle2 = (sin((2.0f * D3DX_PI)*(Time / 24000.0f)))/4; else Angle2 = (cos((2.0f * D3DX_PI)*(Time / 24000.0f)))/4; D3DXMatrixRotationX(&MatrixWorldX, Angle); // Поворачиваем матрицу по Х на угол D3DXMatrixRotationY(&MatrixWorldY, Angle1); // по Y D3DXMatrixRotationY(&MatrixWorld2, Angle2); D3DXMatrixMultiply(&MatrixWorld, &MatrixWorldX, &MatrixWorldY); D3DXMatrixMultiply(&MatrixWorld, &MatrixWorld, &MatrixWorld2); pDirect3DDevice->SetTransform(D3DTS_WORLD, &MatrixWorld); // Трансформируем все вершины по глобальной матрице // MatrixView D3DXMatrixLookAtLH(&MatrixView, &D3DXVECTOR3(0.0f, 0.0f, -8.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); pDirect3DDevice->SetTransform(D3DTS_VIEW, &MatrixView); // MatrixProjection int winH, winW; if(FULLSCREEN) { winH = GetSystemMetrics(SM_CYSCREEN); winW = GetSystemMetrics(SM_CXSCREEN); } else { winH = WINDOW_HEIGHT; winW = WINDOW_WIDTH; } D3DXMatrixPerspectiveFovLH(&MatrixProjection, D3DX_PI/4, (float)winW/(float)winH, 1.0f, 100.0f); pDirect3DDevice->SetTransform(D3DTS_PROJECTION, &MatrixProjection); } // инициализируем буфер вершин. Фактически строим фигуры HRESULT InitialBufferVershin() { // Создаём массив для вершин круга CUSTOMVERTEX Vershin[CIRCLE_RES*4+2]; const float dFi = (2*D3DX_PI)/CIRCLE_RES; // размер сектора float Fi = 0; int i,j; for(i=0; i<CIRCLE_RES*2+2; i+=2) // пишем вершины главной окружности и окружности более глубокой, кот. будет нарисована для 3D-эффекта { Vershin[i].color = 0x00ff0000;//0x00d2691e; Vershin[i+1].color = 0x00440000; Vershin[i].Z = -0.1f; Vershin[i+1].Z = -0.3f; Vershin[i].X = cos( Fi )*2.2f; Vershin[i+1].X = Vershin[i].X*1.05f; Vershin[i].Y = sin( Fi )*2.2f; Vershin[i+1].Y = Vershin[i].Y*1.05f; Fi += dFi; } // создаём штриховки на циферблате. Отталкиваемся от уже созданных вершин круга Vershin[CIRCLE_RES*2].X = Vershin[0].X; Vershin[CIRCLE_RES*2].Y = Vershin[0].Y; Vershin[CIRCLE_RES*2+1].X = Vershin[1].X; Vershin[CIRCLE_RES*2+1].Y = Vershin[1].Y; for(i=0, j=CIRCLE_RES*2+2; i<CIRCLE_RES*2; i+=2, j+=2) { Vershin[j].color = 0x00ff0000;//0x00d2691e; Vershin[j].Z = -0.095f; Vershin[j+1].color = 0x00d2691e; Vershin[j+1].Z = -0.095f; if( i % (CIRCLE_RES / 12) == 0) // часовое деление более длинное { Vershin[j].X = 0.8f * Vershin[i].X; Vershin[j].Y = 0.8f * Vershin[i].Y; Vershin[j+1].X = 0.9f * Vershin[i].X; Vershin[j+1].Y = 0.9f * Vershin[i].Y; } else // обычные деления, короткие { Vershin[j].X = 0.86f * Vershin[i].X; Vershin[j].Y = 0.86f * Vershin[i].Y; Vershin[j+1].X = 0.89f * Vershin[i].X; Vershin[j+1].Y = 0.89f * Vershin[i].Y; } } // создаём буфер вершин из массива объектов if( FAILED( pDirect3DDevice -> CreateVertexBuffer( (CIRCLE_RES*4+2)*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, // длина, использование, тип вершин D3DPOOL_DEFAULT, &pBufferVershin, NULL))) // размешение в памяти, целевой объект буфер вершин return E_FAIL; VOID* pBV; // нетиризированный указатель if( FAILED( pBufferVershin -> Lock(0, sizeof(Vershin), (void**)&pBV, 0))) // блокируем буфер вершин return E_FAIL; memcpy(pBV, Vershin, sizeof(Vershin)); // копируем данные из массива в созданный выше буфер вершин pBufferVershin->Unlock(); // разблокируем буфер // Создаём буфер вершин для всего круга, затем мы его полностью зальём каким-то цветом и получим круг // (выше создавали буфер вершин для окружности -- ободка часов). // Действия те же, что и выше. CUSTOMVERTEX Vershin2[CIRCLE_RES+2]; Fi = 0; for(i=1; i<CIRCLE_RES+2; i++) { Vershin2[i].color = 0x00230000; Vershin2[i].Z = -0.1f; Vershin2[i].X = cos( Fi )*2.2f; Vershin2[i].Y = sin( Fi )*2.2f; Fi += dFi; } Vershin2[0].X = 0.0f; Vershin2[0].Y = 0.0f; Vershin2[0].color = 0x00230000; Vershin2[0].Z = -0.11f; Vershin2[CIRCLE_RES+1].X = Vershin2[1].X; Vershin2[CIRCLE_RES+1].Y = Vershin2[1].Y; if( FAILED( pDirect3DDevice -> CreateVertexBuffer( (CIRCLE_RES+2)*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &pBufferVershin2, NULL))) return E_FAIL; VOID* pBV2; if( FAILED( pBufferVershin2 -> Lock(0, sizeof(Vershin2), (void**)&pBV2, 0))) return E_FAIL; memcpy(pBV2, Vershin2, sizeof(Vershin2)); pBufferVershin2->Unlock(); return S_OK; } // Производим перерасчёт и инициализируем буфер вершин для стрелок HRESULT InitialBufferVershinArrows() { if( pBufferVershinArrows != NULL) // очищаем буфер pBufferVershinArrows -> Release(); SYSTEMTIME* Time = new SYSTEMTIME; // получаем системное время GetLocalTime(Time); // переводим в локальное // Забиваем в массив координаты часовой, минутной и секундной стрелок CUSTOMVERTEX VershinArrows[6]; VershinArrows[4].X = 1.3f * cos(D3DX_PI*2*(((Time->wHour%12)*60+Time->wMinute)/720.0f)+D3DX_PI/2); VershinArrows[4].Y = 1.3f * sin(D3DX_PI*2*(((Time->wHour%12)*60+Time->wMinute)/720.0f)+D3DX_PI/2); VershinArrows[4].color = 0x000f00ff; VershinArrows[4].Z = 0; VershinArrows[5].X = 0; VershinArrows[5].Y = 0; VershinArrows[5].color = 0x000f7fff; VershinArrows[5].Z = 0; VershinArrows[2].X = 1.8f * cos(D3DX_PI*2*((Time->wMinute*60+Time->wSecond)/3600.0f)+D3DX_PI/2); VershinArrows[2].Y = 1.8f * sin(D3DX_PI*2*((Time->wMinute*60+Time->wSecond)/3600.0f)+D3DX_PI/2); VershinArrows[2].color = 0x000f00ff; VershinArrows[2].Z = 0.01f; VershinArrows[3].X = 0; VershinArrows[3].Y = 0; VershinArrows[3].color = 0x000f7fff; VershinArrows[3].Z = 0.01f; VershinArrows[0].X = 1.8f * cos(D3DX_PI*2*(Time->wSecond/60.0f)+D3DX_PI/2); VershinArrows[0].Y = 1.8f * sin(D3DX_PI*2*(Time->wSecond/60.0f)+D3DX_PI/2); VershinArrows[0].color = 0x008b3a3a; VershinArrows[0].Z = 0.02f; VershinArrows[1].X = -VershinArrows[0].X * 0.2f; VershinArrows[1].Y = -VershinArrows[0].Y * 0.2f; VershinArrows[1].color = 0x00cd5555; VershinArrows[1].Z = 0.02f; // создаём буфер вершин, хранящий стрелки if( FAILED( pDirect3DDevice -> CreateVertexBuffer( 6*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &pBufferVershinArrows, NULL))) return E_FAIL; VOID* pBV; if( FAILED( pBufferVershinArrows -> Lock(0, sizeof(VershinArrows), (void**)&pBV, 0))) return E_FAIL; memcpy(pBV, VershinArrows, sizeof(VershinArrows)); pBufferVershinArrows->Unlock(); return S_OK; } // Инициализируем Direct3D HRESULT InitialDirect3D(HWND hwnd) { if( NULL == (pDirect3D = Direct3DCreate9(D3D_SDK_VERSION))) // создаём указатель на интерфейс return E_FAIL; D3DDISPLAYMODE Display; // будет использовано при создании заднего буфера if( FAILED( pDirect3D -> GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &Display))) return E_FAIL; D3DPRESENT_PARAMETERS Direct3DParametr; // параметры, кот. будут использованы при создании DirectX девайса (окна Windows с поддержкой DirectX) ZeroMemory(&Direct3DParametr, sizeof(Direct3DParametr)); if(FULLSCREEN) // если приложение запущено в полноэкранном режиме, делаем соответствующие установки { Direct3DParametr.BackBufferHeight = Display.Height; Direct3DParametr.BackBufferWidth = Display.Width; Direct3DParametr.BackBufferCount = 3; Direct3DParametr.FullScreen_RefreshRateInHz = Display.RefreshRate; } Direct3DParametr.EnableAutoDepthStencil = TRUE; Direct3DParametr.AutoDepthStencilFormat = D3DFMT_D16; Direct3DParametr.Windowed = !FULLSCREEN; // оконный или полноэкранный режим Direct3DParametr.SwapEffect = D3DSWAPEFFECT_DISCARD; // задний и текущий буферы будут меняться только если рендеринг в задний буфер был полностью завершён Direct3DParametr.BackBufferFormat = Display.Format; // формат заднего буфера Direct3DParametr.MultiSampleType = D3DMULTISAMPLE_4_SAMPLES; // мультисэмплинг, измените под свою видеокарту if( FAILED( pDirect3D -> CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, // видеокарта, аппаратное ускорение, дескриптор главного окна D3DCREATE_HARDWARE_VERTEXPROCESSING, &Direct3DParametr, &pDirect3DDevice))) // обработка вершин, параметры выше, целевой указатель интерфейса return E_FAIL; // указываем различные параметры рендеринга, такие как отсутствие Z-буфера, свет, мультисэмплинг и антиальясинг pDirect3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); pDirect3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); pDirect3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); pDirect3DDevice->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, TRUE); pDirect3DDevice->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, TRUE); return S_OK; } VOID RenderingDirect3D() { if( pDirect3DDevice == NULL) // если интерфейс создан, то можно приступать к рендерингу return; // чистим экран (точнее задний буфер) pDirect3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(39, 64, 139), 1.0f, 0); //(23, 95, 54) // Непосредственно само рисование и создание сцены проходит между pDirect3DDevice->BeginScene() и pDirect3DDevice->EndScene() pDirect3DDevice->BeginScene(); // Здесь рисуем Matrix(); // поворачиваем глобальную матрицу // Рисуем цилиндр по краям часов, уходящий вглубь экрана, который придаст объём часам // указываем источник для рисования фигуры pDirect3DDevice->SetStreamSource(0, pBufferVershin, 0, sizeof(CUSTOMVERTEX)); // указываем тип вершин pDirect3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX); // рисуем "примитив", в данном случае треугольник, начиная с 0-й вершины, рисуем CIRCLE_RES*2 фигур (треугольников) pDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, CIRCLE_RES*2); // Рисуем сам циферблат, круг pDirect3DDevice->SetStreamSource(0, pBufferVershin2, 0, sizeof(CUSTOMVERTEX)); pDirect3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX); pDirect3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, CIRCLE_RES); // Рисуем минутные штриховки на циферблате pDirect3DDevice->SetStreamSource(0, pBufferVershin, 0, sizeof(CUSTOMVERTEX)); pDirect3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX); pDirect3DDevice->DrawPrimitive(D3DPT_LINELIST, CIRCLE_RES*2+2, CIRCLE_RES); // рисуем стрелки pDirect3DDevice->SetStreamSource(0, pBufferVershinArrows, 0, sizeof(CUSTOMVERTEX)); pDirect3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX); pDirect3DDevice->DrawPrimitive(D3DPT_LINELIST, 0, 3); // - - - - - - - pDirect3DDevice->EndScene(); pDirect3DDevice->Present(NULL, NULL, NULL, NULL); // Выводим задний буфер на экран } // освобождение ресурсов VOID DeleteDirect3D() { if( pBufferVershinArrows != NULL) pBufferVershinArrows -> Release(); if( pBufferVershin != NULL) pBufferVershin -> Release(); if( pDirect3DDevice != NULL) pDirect3DDevice -> Release(); if( pDirect3D != NULL) pDirect3D -> Release(); } LRESULT CALLBACK MainWinProc(HWND hwnd, // дескриптор окна UINT msg, // идентификатор сообщения WPARAM wparam, // дополнительная информация LPARAM lparam) // дополнительная информация { switch(msg) { /*case WM_PAINT: { RenderingDirect3D(); ValidateRect(hwnd, NULL); } break; */ case WM_DESTROY: // если получили сообщение о выходе, то уничтожаем ресурсы директ икс и выходим { DeleteDirect3D(); PostQuitMessage(0); return(0); } break; case WM_KEYDOWN: { if(wparam == VK_RETURN) // при нажатии на "Enter" { // получаем текущий список аргументов. В принципе эти 3 строки можно убрать. LPWSTR *szArglist; int nArgs; szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); // если сейчас приложение запущено в оконом режиме, запускаем его в полноэкранном if(!FULLSCREEN) ShellExecute(hInstance1,L"open",szArglist[0], L"-f", NULL, SW_SHOWNORMAL); else // и наоборот ShellExecute(hInstance1,L"open",szArglist[0], NULL, NULL, SW_SHOWNORMAL); // в эту милисекунду у нас работает 2 программы. PostQuitMessage(0); // но на практике это не заметно вообще, так как мы сразу // после запуска новой копии, выходим из старой. } else if(wparam == VK_ESCAPE) // если нажат эскейп, выходим PostQuitMessage(0); return(0); } break; } return (DefWindowProc(hwnd, msg, wparam, lparam)); } int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { // получаем список переданных с командной строкой аргументов // если есть аргумент "-f", то запускаем в полноэкранном режиме LPWSTR *szArglist1; int nArgs; LPWSTR temp = L"-f"; szArglist1 = CommandLineToArgvW(GetCommandLineW(), &nArgs); if(szArglist1 != NULL && nArgs==2) FULLSCREEN = true; WNDCLASSEX windowsclass; // создаём класс HWND hwnd; // создаём дескриптор окна MSG msg; // идентификатор сообщения // определим класс окна WNDCLASSEX windowsclass.cbSize = sizeof(WNDCLASSEX); windowsclass.style = CS_DBLCLKS|CS_OWNDC|CS_HREDRAW|CS_VREDRAW; windowsclass.lpfnWndProc = MainWinProc; windowsclass.cbClsExtra = 0; windowsclass.cbWndExtra = 0; windowsclass.hInstance = hinstance; windowsclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); windowsclass.hCursor = LoadCursor(NULL, IDC_ARROW); windowsclass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); windowsclass.lpszMenuName = NULL; windowsclass.lpszClassName = L"WINDOWSCLASS"; windowsclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // зарегистрируем класс if(!RegisterClassEx(&windowsclass)) return 0; // можно создать окно if(!(hwnd = CreateWindowEx(NULL, L"WINDOWSCLASS", L"Часы на DirectX9 by RazeR", // стиль окна, класс, название окна (WS_OVERLAPPEDWINDOW|WS_VISIBLE)-(/*WS_MINIMIZEBOX | */WS_MAXIMIZEBOX | WS_THICKFRAME), (GetSystemMetrics(SM_CXSCREEN)-WINDOW_WIDTH)/2, // левый верхний (GetSystemMetrics(SM_CYSCREEN)-WINDOW_HEIGHT)/2, // угол WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hinstance, NULL))) // ширина, высота, дескриптор родительского окна, дескриптор меню, дескриптор приложения, указатель на данные окна return 0; if(FULLSCREEN) ShowCursor(false); // прячем курсор if( SUCCEEDED( InitialDirect3D(hwnd))) // инициализируем Директ 3Д { if( SUCCEEDED( InitialBufferVershin())) // инициализируем буфер вершин { ShowWindow(hwnd, SW_SHOWDEFAULT); // рисуем окно UpdateWindow(hwnd); // обновим окно ZeroMemory(&msg, sizeof(msg)); while( msg.message != WM_QUIT) // главный цикл, здесь читаем входящие сообщения от пользователя { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else if( SUCCEEDED( InitialBufferVershinArrows())) RenderingDirect3D(); } } } return 0; } /* MyArrow::MyArrow(float xT, float yT, float roT) { for(int i=0; i<6; i++) { points[i].X = 0; points[i].Y = 0; points[i].Z = 0.5; points[i].rhw = 1.0; points[i].color = 0x00000fff; } x = xT; y = yT; ro = roT; fi = 0; }*/
|
|||||||