Дата публикации статьи: 06.05.2004 18:47

Кудицкий Дмитрий
Быстрое создание графических приложений с использованием Win32 GDI и Direct3D

  • Введение

  • Создание библиотек и тестового приложения

  • Работа с графическими API функциями

  • Пример офисного программирования

  • DirectX

  • Оконные приложения DirectX

  • Пример тренажера технического процесса

  • Работа с файлами трехмерной графики *.Х

  • Пример построения трехмерного технического объекта

  • Заключение

  • Исходный код к статье

  • Введение

        Считается, что возможности Visual Basic, связанные с построением визуального интерфейса, обработкой баз данных (при помощью DAO/ADO) и офисным программированием, являются выдающимися по простоте и скорости разработки, а для работы с графикой VB не приспособлен. Такое мнение сложилось, возможно, потому, что VB не имеет встроенного объекта для работы с графикой, инкапсулирующего GDI32. Однако VB получает с помощью команды Declare клиентские возможности по отношению к Win32 (следовательно, и к GDI32.DLL), а такие объекты VB как Form и PictureBox предоставляют доступ к контексту устройства (Form1.hDC Picture1.hDC), значит все возможности, связанные с программированием графики доступны разработчикам, использующим VB. Естественно VB является в первую очередь клиентом COM и такой графический API как DirectX, являющийся сервером COM, так же предоставляет свои интерфейсы разработчикам VB. Оба API физически находятся в библиотеках динамической компоновки gdi32.dll и dx7vb.dll (DirectX версии 7). Самой Windows не важно, на каком языке Вы обращаетесь к ее сервисам (размещенным, как правило, в DLL) главное, чтобы запросы (например, типы в параметрах функций) соответствовали тому, что Windows ожидает получить. Можно привести очень простой пример смешанного программирования VB/VC++ с использованием DLL. Для написания DLL используется С++ и мастер MFC DLL из последней версии Visual C++.NET (логотип .NET пусть не вводит в заблуждение – это обычный С++ стандарта ANSI, просто используется последняя версия MFC).

    Создание библиотек и тестового приложения

    Выполните пошаговые инструкции:

    1. Создайте новый проект. File/New/Project/Visual C++ Projects/MFC DLL
    2. В поле Name введите имя проекта - VCDLLVB
    3. Нажмите кнопку ОК (рис1)


    Рис. 1

    4. В появившемся окне выберите пункт Regular DLL with MFC statically linked (рис.2).


    Рис. 2

    5. Следующим шагом будет добавление функциональности в заготовку для DLL, а именно ввод в проект экспортируемых функций. Добавте в проект файлы MyFunction.h и MyFunction.cpp с помощью меню Project/AddNewItem. Появится окно в котором выберите файл (.h) , а затем (.cpp). Присвойте обоим файлам имена MyFunction. Добавление в проект файлов, с одновременным их созданием, производится кнопкой Open (рис.3).

    Примечание!
    Классы С++ (порожденные в том числе от CObject) можно использовать только внутри проекта. В файле “VCDLLVB.H” присутствует объявление следующего класса – “class CVCDLLVBApp : public CwinApp{…}”, а в фале “VCDLLVB.CPP” создается глобальный объект этого класса “CVCDLLVBApp theApp;”. По сути это минимальное по функциональности приложение MFC.


    Рис. 3

    6. После всех манипуляций окно Solution Explorer должно иметь следующий вид (рис.4).


    Рис. 4

    Для редактирования произведите двойной щелчок мышью на именах функций. Введите следующий текст в файлы MyFunction.h и MyFunction.ccp:

    // #include "afx.h" MFC не используется
    ////////////////////////////MyFunction.h////////////////////////
    extern "C" LONG PASCAL EXPORT AddLong(LPLONG a,LPLONG b);
    extern "C" void PASCAL EXPORT DrawCircl(HWND hOkna);
    
    ////////////////////////////MyFunction.ccp/////////////////////
    #include "StdAfx.h" //Необходимые директивы препроцессора
    #include "MyFunction.h"
    extern "C" LONG PASCAL EXPORT AddLong(LPLONG a,LPLONG b)
    {
    return (*a + *b);
    }
    extern "C" void PASCAL EXPORT DrawCircl(HWND hOkna)
    {
    ::HDC hdc = ::GetDC(hOkna);
    ::Ellipse(hdc,10,10,50,50);
    ::ReleaseDC(hOkna, hdc);
    }

    Редактированию необходимо подвергнуть и файл VCDLLVB.def, в нем хранятся имена экспортируемых функций. Так должен выглядеть этот файл:

    ; VCDLLVB.def : Declares the module parameters for the DLL.
    
    LIBRARY "VCDLLVB"
    
    EXPORTS
    ; Explicit exports can go here
    AddLong
    DrawCircl

    7. Измените режим компиляции с Debug на Release и выполните команду Build/Build VCDLLVB. В результате (если этап компиляции пройдет успешно) в папке “Release” проекта появится файл VCDLLVB.DLL. (файлы проекта в архиве исходного кода Graphics1). Теперь эту библиотеку можно протестировать в VB.

    8. Для создания тестового приложения запустите VB и создайте проект Standard EXE.
    Нарисуйте форму примерно как на рис. 5.

    Примечание!
    Во всех проектах создаваемых в VB необходимо устанавливать свойства формы и элемента управления PictureBox следующим образом: AutoRedraw – False, ScaleMode-Pixel.


    Рис. 5

    Введите следующий код:

    Option Explicit
    Private Declare Function Ellipse& Lib "gdi32" (ByVal hDC As Long, ByVal X1 As Long, _
    ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long)
    Private Declare Function AddLong Lib "VCDLLVB.DLL" (ByRef v1 As Long, _
    ByRef v2 As Long) As Long
    Private Declare Sub DrawCircl Lib "VCDLLVB.DLL" (ByVal hWND As Long)
    
    Private Sub Form_Load()
    Dim L As Long
    Dim L1 As Long
    Dim L2 As Long
    L1 = 2
    L2 = 1
    Text1.Text = AddLong(L2, L1)
    End Sub
    
    Private Sub Command1_Click()
    DrawCircl Form1.hWND
    End Sub
    
    Private Sub Command2_Click()
    Ellipse Form1.hDC, 10, 50, 50, 90
    End Sub
    

    9. Сохраните проект. Поместите файл VCDLLVB.DLL в папку проекта (файлы проекта см. Graphics1). Если запустить приложение на выполнение и нажать в появившейся форме кнопки, его вид должен соответствовать рис. 6.


    Рис. 6

    10. Оба круга нарисованы одной и той же функцией Win32 API, а число три получилось сложением внутри DLL параметров, переданных по ссылке из VB.

        В прототипе функции для сложения AddLong явно указано, с помощью ключевого слова ByRef, что мы передаем значение по ссылке. Единственный параметр функции для рисования круга в DrawCircl объявлен как ByVal, то есть параметр передается по значению. В прототипе API функции Ellipse первый параметр имеет вид ByVal hDC, значит он тоже передается по значению. Оба параметра являются описателями – первый окна(формы), а второй контекста устройства. Использование описателей находится под контролем ядра Windows, а в параметрах функций API они передаются как обычные 32-разрядные числа, поэтому и применяется ByVal. Для программирования с помощью команды Declare нужно только разобраться с передачей параметров из VB в API-функции.

        Рассмотрим простой пример работы с графическими функциями API в среде Visual Basic (файлы проекта см.  Graphics2). Создайте проект типа Standard EXE. Поместите в форму четыре кнопки. Введите в раздел General после Option Explicit следующий код:

    Option Explicit
    Dim Lim As Long
    Dim BackVal As Long
    Dim RecordDC As Long
    Dim DCWin As Long
    Dim WindHandle As Long
    Dim DCPicture As Long
    Private Type POINTAPI
    X As Long
    Y As Long
    End Type
    Private ArrayPoint() As POINTAPI
    Private Declare Function CreateSolidBrush& Lib "gdi32" (ByVal crColor As Long)
    Private Declare Function SelectObject& Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long)
    Private Declare Function DeleteObject& Lib "gdi32" (ByVal hObject As Long)
    Private Declare Function SaveDC& Lib "gdi32" (ByVal hDC As Long)
    Private Declare Function RestoreDC& Lib "gdi32" (ByVal hDC As Long, ByVal nSavedDC As Long)
    Private Declare Function SetPixel& Lib "gdi32" (ByVal hDC As Long, ByVal X As Long, _
    ByVal Y As Long, ByVal crColor As Long)
    Private Declare Function GetWindowDC& Lib "user32" (ByVal hwnd As Long)
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) _
    As Long
    Private Declare Function Rectangle& Lib "gdi32" (ByVal hDC As Long, ByVal X1 As Long, _
    ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long)
    Private Declare Function Polygon& Lib "gdi32" (ByVal hDC As Long, lpPoint As _
    POINTAPI, ByVal nCount As Long)

        Обратите внимание на пользовательский тип POINTAPI. Так как объявление Type POINTAPI можно считать синонимом структуры в языке C, значит Private ArrayPoint() As POINTAPI – это массив структур в которых будут хранятся координаты точек для функции API Polygon, которая принимает по ссылке (по умолчанию в VB параметры передаются по ссылке) в качестве параметра указатель(адрес) на массив структур POINTAPI. Аналогичная функция на C выглядит следующим образом - Polygon(HDC hdc, CONST POINT*ipPoints, int nCount), а массив структур POINT, указатель на который ей надо передать, можно создать так - POINT points[5]={0x,0y,1x,1y,2x,2y,3x,3y,4x,4y}. Переменная int nCount указывает на число используемых вершин в многоугольнике. В обработчики событий формы введите следующий код:

    Private Sub Form_Load()
    Lim = 0
    WindHandle = Form1.hwnd
    DCPicture = Form1.hDC
    End Sub
    Private Sub Form_Paint()
    Dim oldBrush As Long
    Dim BrushBlue As Long
    Dim iCount As Long
    Dim iCountX As Long
    Dim iCountY As Long
    BrushBlue = CreateSolidBrush(QBColor(11))
    oldBrush = SelectObject&(DCPicture, BrushBlue)
    For iCount = 0 To 15 Step 1
    iCountX = iCountX + Lim
    For iCountX = iCountX To iCountX + 5 Step 1
    SetPixel DCPicture, iCountX, 25, QBColor(iCount)
    For iCountY = 26 To 38 Step 1
    SetPixel DCPicture, iCountX, iCountY, QBColor(iCount)
    Next iCountY
    Next iCountX
    Next iCount
    Rectangle Form1.hDC, 3, 3, 290, 20
    BrushBlue = SelectObject&(DCPicture, oldBrush)
    BackVal = DeleteObject&(BrushBlue)
    End Sub
    
    Private Sub Command1_Click()
    DCWin = GetWindowDC(WindHandle)
    Rectangle DCWin, 3, 3, 290, 20
    ReleaseDC WindHandle, DCWin
    End Sub
    
    Private Sub Command2_Click()
    Lim = Lim + 1
    Form1.Refresh
    End Sub
    
    Private Sub Command3_Click()
    Lim = Lim - 1
    Form1.Refresh
    End Sub
    
    Private Sub Command4_Click()
    Dim oldBrush As Long
    Dim BrushRed As Long
    ReDim Preserve ArrayPoint(4)
    ArrayPoint(0).X = 3
    ArrayPoint(0).Y = 89
    '''''
    ArrayPoint(1).X = 151
    ArrayPoint(1).Y = 101
    '''''
    ArrayPoint(2).X = 295
    ArrayPoint(2).Y = 84
    '''''
    ArrayPoint(3).X = 301
    ArrayPoint(3).Y = 130
    '''''
    ArrayPoint(4).X = 25
    ArrayPoint(4).Y = 115
    BrushRed = CreateSolidBrush(QBColor(12))
    oldBrush = SelectObject&(DCPicture, BrushRed)
    Polygon DCPicture, ArrayPoint(0), 5
    BrushRed = SelectObject&(DCPicture, oldBrush)
    BackVal = DeleteObject&(BrushRed)
    End Sub

        Обратите внимание на код в обработчике события Private Sub Command4_Click(). В нем происходит передача массива структур функции Polygon. Контекст устройства хранится в глобальной переменной DCPicture, ее инициализация происходит в событии Private Sub Form_Load.
        Функция рисования многоугольника обрамлена следующими вызовами BrushRed = CreateSolidBrush(QBColor(12)) oldBrush = SelectObject&(DCPicture, BrushRed) и BrushRed = SelectObject&(DCPicture, oldBrush) BackVal = DeleteObject&(BrushRed). Все, что будет заключено между этими функциями и имеет замкнутый контур(например многоугольник) будет закрашено красным цветом.
        В событии Private Sub Command1_Click() происходит рисование прямоугольника на заголовке формы, но контекст устройства для поверхности вне рабочей(клиентской) области окна автоматически не предоставляется формой. Этот контекст необходимо получить с помощью функции DCWin = GetWindowDC(WindHandle). Теперь в переменной DCWin хранится идентификатор контекст для рисования за пределами клиентской области и его можно передавать любым графическим функциям API.
        Самым главным событием является Private Sub Form_Paint(), так как Windows перерисовывает только то, что находится внутри этого события. Проверить это можно если запустить форму на выполнение.
    Запустите приложение. Нажмите кнопку “+” несколько раз, кнопка “Многоугольник” должна быть нажата последней, иначе красный многоугольник будет исчезать при нажатии на “+”. Вид формы приведен на рис. 7.
     


    Рис. 7

        Если теперь передвинуть форму за левый край экрана, а потом вернуть восстановятся только те графические элементы, которые были помещены в событии Private Sub Form_Paint() (рис.8)

    .
    Рис. 8

        Цветные столбики нарисованы в тройном вложенном цикле и выводятся по одной точке(пикселю) функцией SetPixel DCPicture, iCountX, iCountY, QBColor(iCount).
     

    Примечание!
    Все примеры запускались на машине со следующей конфигурацией WinMe/XP Celeron466/RAM-128Мб/NVIDIAVanta-8Мб.

        Найдите в примере строку Form1.Refresh – это значит, что событие Form_Paint применяется ко всей форме. Если форма будет с большим количеством графики чем сейчас Вы заметите нежелательное дрожание(которое и сейчас заметно). Для перерисовки только ограниченной части формы (например прямоугольной области вокруг цветных столбиков) воспользуйтесь функцией Private Declare Function InvalidateRect& Lib "user32" (ByVal hwnd As Long, lpRect As RECT, ByVal bErase As Long). После этого красный многоугольник перестанет исчезать при нажатии на кнопку “Плюс”, а дрожание всей формы исчезнет.

    Пример офисного программирования

        В завершении темы GDI32 и Visual Basic приводится пример офисного программирования с использованием очень простой базы данных созданной в Microsoft Access 97. Данные из БД согласуются с картой небольшого города (например Соснового Бора Ленинградской области с населением около 70 тыс. человек).
     

    Примечание!
    Если захотите открыть БД в Access Вам будет необходим пароль, который присваивается в событии Private Sub Form_Load() через объект Dim MyBaza As Database методом OpenDatabase.

        В папку WINDOWS/FONTS необходимо поместить файл с шрифтом TC05002T, так как при создании рисунка в формате WMF(из которого состоит карта) в некоторых надписях был применен этот шрифт.
    В связи с тем, что код примера достаточно большой просмотрите его сами в среде VB6. Вид интерфейса БД/Карта в работе представлен на рис. 9 и 10 (файлы проекта см. Graphics3).


    Рис. 9, Рис. 10

    Приведу только некоторые пояснения:

    1. С помощью увеличительного стекла можно уменьшать и увеличивать изображение, а с помощью стрелок можно перемещаться по карте.
    2. На рис. 9 показано как на увеличенной части карты нанесен треугольник. Координаты треугольника будут занесены в БД. В реальном примере лучше помещать координаты в файл, а не БД (с другой стороны одна другая тысяча записей в БД для современного компьютера не проблема, к тому же Microsoft собирается в будущих версиях ОС превратить файловую систему в БД). С координатами треугольника будет связана фамилия, телефон, адрес и все что угодно по Вашему усмотрению. Когда в БД появится много записей Вы можете воспользоваться поиском. Поиск производится по средством SQL запроса вида "SELECT * FROM Таблица1 WHERE ФИО LIKE '" & Isk & "*' " '".
    3. С API связанны следующие функции:

    Public Declare Function SetMapMode& Lib "gdi32" (ByVal hDC As Long, ByVal nMapMode As Long)
    Public Declare Function SetWindowExtEx& Lib "gdi32" (ByVal hDC As Long, ByVal nX As _ 
    Long, ByVal nY As Long, lpSize As SIZE)
    Public Declare Function SetWindowOrgEx& Lib "gdi32" (ByVal hDC As Long, ByVal nX As _ 
    Long, ByVal nY As Long, lpPoint As POINTAPI)
    Public Declare Function SetViewportExtEx& Lib "gdi32" (ByVal hDC As Long, ByVal nX _ 
    As Long, ByVal nY As Long, lpSize As SIZE)
    Public Declare Function SetViewportOrgEx& Lib "gdi32" (ByVal hDC As Long, ByVal nX _ 
    As Long, ByVal nY As Long, lpPoint As POINTAPI)
    Public Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex _ 
    As Long) As Long


        Единственная задача для них вступать в работу когда Вы пользуетесь увеличительным стеклом, приводя в соответствие с коэффициентом масштабирования треугольник, показывающий местожительство выбранного из БД человека. Последняя функция предназначена для предотвращения запуска приложения при режиме разрешения экрана отличном от 96 dpi. В утилите свойства экрана нажмите кнопку “Дополнительно” и измените размер шрифта на значение “Крупный шрифт”(120 dpi). После перегрузки при попытке запустить приложение должно появиться предупреждение.
        В данном примере объединены такие возможности VB, как быстрое построение визуального интерфейса, подключение баз данных и смешение графических функций VB и API GDI.

    DirectX

        В API GDI32 предусмотрены функции для работы с изображениями на основе массива пикселей, но для быстрой работы с битовой графикой Microsoft предлагает API DirectX, основанный на технологии COM. Хорошую основу для изучения DirectX7 в среде VB закладывают статьи Сергея Никифорова представленные на сайте http://www.vbstreets.ru.В седьмой версии DirectX за работу с битовой графикой отвечает интерфейс DirectDraw. Гнаться за последней версией DirectX(версии 8, 9 и выше) совсем не обязательно, так как имеет большое значение аппаратная поддержка (последней версии шейдеров) этой технологии видео картами, установленными в компьютеры пользователей, использующих Ваши программы.

    Примечание!
    Если Вы работаете в WinXP - DirectX уже установлен, если в Win9X необходимо установить версию 7 или выше (как правило последнюю версию можно найти на CD с играми, SDK не нужен). С появлением платформы .NET, которая приходит на смену COM возможно вместо DirectX появится пространство имен примерно такого вида System.Drawing.Drawing3D. На платформе .NET не реализованы(на данный момент) собственные интерфейсы для работы с трехмерной графикой, но воспользоваться интерфейсами реализованными в COM можно через класс-оболочку, который построит утилита tlbimp.

        Для работы с DirectX часто достаточно только интерфейса отвечающего за битовую графику, а именно DirectDraw. Инициализация DirectDraw требует ряда поэтапных стандартных действий, описанных в приведенной ниже простой программе.


    1. Создайте проект Standard EXE. Подключите к проекту библиотеку типов DirectX:
    Project/References/DirectX7 for VB Type Library (Рис. 11)



    Рис. 11

    2. Введите в раздел General после Option Explicit следующий код:

    Option Explicit
    Dim dx As New DirectX7
    Dim dd As DirectDraw7
    Dim di As DirectInput
    '''''''''''''''''''''
    Dim diDev As DirectInputDevice
    Dim diState As DIKEYBOARDSTATE
    '''''''''''''''''''''
    Dim Prim As DirectDrawSurface7
    Dim PrimBufer As DirectDrawSurface7
    Dim OsnovaBMP As DirectDrawSurface7
    Dim SpriteBMP As DirectDrawSurface7
    '''''''''''''''''''''
    Dim ddsdPrim As DDSURFACEDESC2
    Dim ddsdPrimBufer As DDSURFACEDESC2
    Dim ddsdOsnovaBMP As DDSURFACEDESC2
    Dim ddsdSpriteBMP As DDSURFACEDESC2
    Dim ddsdcapsPrimBufer As DDSCAPS2
    ''''''''''''''''''''
    Dim Key As DDCOLORKEY

        В переменной dx хранится ссылка на “верхний” объект – собственно на всю библиотеку DirectX7. Объектная переменная dd нужна для получения доступа к сервисам предоставляемым интерфейсом DirectDraw, а переменная di для доступа к DirectInput – интерфейсу обеспечивающему работу мыши и клавиатуры.

    3. Введите следующий код в событие формы Private Sub Form_Load():

    Private Sub Form_Load()
    Set dd = dx.DirectDrawCreate("")
    Set di = dx.DirectInputCreate()
    Set diDev = di.CreateDevice("GUID_SysKeyboard")
    ''''''''''''''''''''''
    dd.SetCooperativeLevel Me.hWnd, DDSCL_FULLSCREEN _
    Or DDSCL_ALLOWMODEX Or DDSCL_EXCLUSIVE
    dd.SetDisplayMode 800, 600, 16, 0, DDSDM_DEFAULT
    ''''''''''''''''''''''
    diDev.SetCommonDataFormat (DIFORMAT_KEYBOARD)
    diDev.SetCooperativeLevel Form1.hWnd, DISCL_NONEXCLUSIVE _
    Or DISCL_BACKGROUND
    diDev.Acquire
    ''''''''''''''''''''''
    ddsdPrim.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT
    ddsdPrim.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE _
    Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX
    ddsdPrim.lBackBufferCount = 1
    Set Prim = dd.CreateSurface(ddsdPrim)
    ''''''''''''''''''''''
    ddsdcapsPrimBufer.lCaps = DDSCAPS_BACKBUFFER
    Set PrimBufer = Prim.GetAttachedSurface(ddsdcapsPrimBufer)
    PrimBufer.GetSurfaceDesc ddsdPrimBufer
    ''''''''''''''''''''''
    ddsdOsnovaBMP.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
    ddsdOsnovaBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
    ddsdOsnovaBMP.lWidth = 800 'ddsdPrimBufer.lWidth
    ddsdOsnovaBMP.lHeight = 600 'ddsdPrimBufer.lWidth
    
    Set OsnovaBMP = dd.CreateSurfaceFromFile(App.Path & "\Osnova.bmp", ddsdOsnovaBMP)
    ''''''''''''''''''''''
    ddsdSpriteBMP.lFlags = DDSD_CAPS
    ddsdSpriteBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
    Set SpriteBMP = dd.CreateSurfaceFromFile(App.Path & "\Fase.bmp", ddsdSpriteBMP)
    ''''''''''''''''''''''
    Key.low = 0
    Key.high = 0
    SpriteBMP.SetColorKey DDCKEY_SRCBLT, Key
    Dim Kvadrat As RECT
    Dim Kvadrat1 As RECT
    Kvadrat.Top = 0
    Kvadrat.Left = 0
    Kvadrat.Bottom = 600 'ddsdPrimBufer.lHeight
    Kvadrat.Right = 800 'ddsdPrimBufer.lWidth
    Kvadrat1.Top = 0
    Kvadrat1.Left = 0
    Kvadrat1.Bottom = ddsdSpriteBMP.lHeight
    Kvadrat1.Right = ddsdSpriteBMP.lWidth
    PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
    'PrimBufer.BltFast 50, 50, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
    PrimBufer.BltFast 0, 0, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
    Prim.Flip Nothing, DDFLIP_WAIT
    End Sub

        В первой части кода приведенного в событии Form_Load производится инициализация объектных переменных dd,di и diDev. Интерфейс доступ к которому предоставляет переменная diDev отвечает в данном случае за клавиатуру (флаг GUID_SysKeyboard). От di можно так же “родить” мышь и джойстик которые являются устройствами (DEVICE) поддерживаемыми технологией DirectX(тип - Dim diDev As DirectInputDevice ).

    Примечание!
    В первой версии DirectX которая называлась Game SDK работа с мышью и клавиатурой производилась через стандартные оконные сообщения Windows, а DirectInput нужен, чтобы обойти сообщения окну. В VB для работы в форме можно использовать стандартные события мыши и клавиатуры. Хотя они и медленней, но если Вы не пишите высокоскоростную игру стандартные сообщения вполне подойдут.

    В функции SetCooperativeLevel устанавливается разрешение экрана 800/600 пикселей и цветовой режим 16 бит на пиксель.

    Примечание!
    Режим 265 цветов (работа с палитрами) в данной статье не рассматривается, хотя все файлы формата “ВМР” в примерах являются палитровыми. Просто если установить режим 16б бит/пиксель все преобразования по правильному отображению палитры за Вас сделает Windows.

        Найдите функции Set OsnovaBMP = dd.CreateSurfaceFromFile(App.Path & "\Osnova.bmp", ddsdOsnovaBMP) и Set SpriteBMP = dd.CreateSurfaceFromFile(App.Path & "\Fase.bmp", ddsdSpriteBMP), прежде чем ими воспользоваться и загрузить массивы пикселей в память из файлов, необходимо создать поверхности с которыми может работать DirectDraw (тип - DirectDrawSurface7). В данном примере будет четыре поверхности: Dim Prim As DirectDrawSurface7-первичная поверхность; Dim PrimBufer As DirectDrawSurface7- буфер прикрепленный к первичной поверхности на котором в памяти строится картинка перед выводом; Dim OsnovaBMP As DirectDrawSurface7 - картинка для фона созданная из файла (Osnova.bmp); Dim SpriteBMP As DirectDrawSurface7 - картинка для лица человечка (Fase.bmp).
        Создают поверхности функции описанные в интерфейсе DirectDraw – CreateSurface(для первичной поверхности и ее буфера) и CreateSurfaceFromFile(поверхности на основе файлов), которым в качестве параметров передаются структуры с описанием поверхностей(тип – DDSURFACEDESC2).
        Найдите в коде примера две функции BltFast – здесь происходит загрузка картинок(спрайтов) в первичный буфер. В функции Flip буфер “выстреливается” на свою первичную поверхность. Если этот процесс “зациклить” меняя при этом координаты вывода спрайтов в функциях BltFast - получится анимация. Изменение координат в функциях BltFast может инициализировать нажатие клавиш, мыши или событие таймера.

    4. Добавьте на форму элемент “Timer” со следующим обработчиком события:

    Private Sub Timer1_Timer()
    Dim iKeyCounter As Integer
    Dim Kvadrat As RECT
    Dim Kvadrat1 As RECT
    Kvadrat.Top = 0
    Kvadrat.Left = 0
    Kvadrat.Bottom = 600 'ddsdPrimBufer.lHeight
    Kvadrat.Right = 800 'ddsdPrimBufer.lWidth
    Kvadrat1.Top = 0
    Kvadrat1.Left = 0
    Kvadrat1.Bottom = ddsdSpriteBMP.lHeight
    Kvadrat1.Right = ddsdSpriteBMP.lWidth
    diDev.GetDeviceStateKeyboard diState
    If diState.Key(2) Then '1'<> 0 Then
    PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
    PrimBufer.BltFast 100, 100, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
    Prim.Flip Nothing, DDFLIP_WAIT
    End If
    If diState.Key(3) Then '1'<> 0 Then
    PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
    PrimBufer.BltFast 0, 0, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
    Prim.Flip Nothing, DDFLIP_WAIT
    End If
    End Sub

        Нажимая на клавиши “1” и “2” лицо человечка будет перемещаться. Обратите внимание, что один глаз у человечка “закрыт”. Происходит это потому, что прозрачными будут только черные пиксели файла из файла Fase.bmp. Отвечает за прозрачность черного цвета следующий код:

    Key.low = 0
    Key.high = 0
    SpriteBMP.SetColorKey DDCKEY_SRCBLT, Key

    5. Что бы безопасно выйти из приложения, с помощью клавиши “Esc”, добавьте следующий обработчик :

    Private Sub Form_KeyPress(KeyAscii As Integer)
    If KeyAscii = 27 Then
    diDev.Unacquire
    dd.RestoreDisplayMode
    dd.SetCooperativeLevel Me.hWnd, DDSCL_NORMAL
    End
    End If
    End Sub

        Если не хотите связываться с DirectInput обработку нажатия клавиш для перемещения человечка можно поместить в событие Form_KeyPress (коды клавиш: “Esc”- код 27; “1”- код 49; ”2”- код 50). На рис. 12 представлено работающее приложение, описанное выше, оно не предусматривает наличие привычного для Windows-программы окна (файлы проекта см. Graphics4).
     


    Рис. 12

    Оконные приложения DirectX

        Для создания оконных приложений DirectX, последовательность действий должна быть несколько иной. Приведем простой пример приложения DirectX обладающего окном(файлы проекта см. Graphics5). Приложение в работе представлено на рис. 13 и 14.


    Рис. 13, Рис. 14

        При нажатии на кнопку “PageUp”(стрелка вверх) винтовка начинает стрелять. Как и для безоконного приложения для каждого спрайт нужно создать поверхность. Для спрайта выстрела создается последовательность рисунков. На рис. 15 представлена последовательность из файла “gun.bmp”.


    Рис. 15

        Полностью код приложения Вы можете просмотреть в среде Visual Basic. Приведем ключевые моменты. Основные глобальные объекты создаются как было показано в прошлом примере, но отсутствует DirectInput. Добавляется глобальное объявление переменной Dim objDDClip As DirectDrawClipper которая инициализируется в событии Form_Load:

    Call objDD.SetCooperativeLevel(Me.hWnd, DDSCL_NORMAL)
    ddsdScreen.lFlags = DDSD_CAPS
    ddsdScreen.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE
    Set objDDScreen = objDD.CreateSurface(ddsdScreen)
    Set objDDClip = objDD.CreateClipper(0)
    objDDClip.SetHWnd Picture1.hWnd
    objDDScreen.SetClipper objDDClip

        Это нужно для ограничения области вывода графики прямоугольной областью которое занимает окно (так как приложение оконное). Обратите внимание как изменились флаги в функции SetCooperativeLevel, а так же на вызов функции SetClipper через объект поверхности objDDScreen (которой соответствует первичная поверхность Prim из прошлого примера). После считывания этого кода приложение знает о существовании и размерах окна на которое выводится графика.
        Последние на что следует обратить внимание это вызов функции Call objDX.GetWindowRect(Picture1.hWnd, rPrim). Она вызывается перед вызовом функции блитинга для “подложки” на которую лягут спрайты. Это вызвано тем, что нужно знать положение ограничивающего прямоугольника окна(ведь окно может перемещаться по экрану).
        Важным отличием от предыдущего примера является добавление в событие Form_Load “бесконечного” цикла Do While в котором происходит “зацикливание”, необходимое для анимации.
    Изменилось и назначение таймера – теперь в нем происходит последовательная выборка вспышек выстрелов. Такая техника является заменой многопоточности в приложениях на VB.

    Пример тренажера технического процесса

        В следующем примере на этом принципе построено приложения которое может являться прототипом несложного тренажера или системой подсказки оператора, управляющего техническим процессом. Приведем относительно простой пример - за основу возьмем ввод в действие гипотетической атомного установки. Экран программы приведен на рисунке 16(файлы проекта см. Graphics6).


    Рис. 16

        Код этой программы имеет значительный размер по сравнению с приложениями приведенными ранее, поэтому рассмотрим только некоторые из его отличительных особенностей. Программа разделена на четыре модуля – модуль инициализации DirectDraw; модуль для всех функций BltFast выводящих спрайты на буфер; модуль для объявления функций API, переменных и структур(тип RECT и POINTAPI); модуль в котором находится стартовая функция Main(), загружается форма и включается “бесконечный” цикл Do While. Программа состоит из трех форм Visual Basic. Первая форма реагирует на стандартные события мыши и клавиатуры, на ней так же установлены два таймера(имитируют многопоточность). Две другие формы служат хранилищами ресурсов графики.

    Примечание!
    Часто для хранения ресурсов используют DLL.

    Ресурсы нужны для создания поверхностей DirectDraw. В каталоге приложения Вы не найдете файлов типа *.BMP и соответственно поверхности не могут быть созданы с помощью функции CreateSurfaceFromFile, в место этого применен функция CreateSurface. В тексте программы это выглядит так:

    ddsdResBMP.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
    ddsdResBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
    ddsdResBMP.lWidth = 742
    ddsdResBMP.lHeight = 313
    Set ResBMP = dd.CreateSurface(ddsdResBMP)
    ddrDC1 = ResBMP.GetDC()
    BitBlt ddrDC1, 0, 0, 742, 313, Form3.hDC, 0, 0, &HCC0020
    ResBMP.ReleaseDC ddrDC1

        Как видите и у поверхности DirectDraw имеется контекст устройства. Получить доступ к ресурсам вспомогательных форм можно было бы и таким способом:

    MBM = CreateCompatibleBitmap(Form2.hDC, 800, 600) 'Образ в памяти
    MDC = CreateCompatibleDC(Form2.hDC) 'Создание контекста
    BitBlt(ddrDC1, 216, 58, 65, 130, MDC, 64, 0, &HCC0020).

        В этом случае в памяти создается образ из массива пикселей на основе которого строится совместимый контекст памяти, передаваемый функции API BitBlt. Эта функция заполняет поверхность DirectDraw пикселями.
    Вращающиеся стрелки приборов созданы с помощью таких функций GDI как: Polygon, SelectObject, CreatePen, DeleteObject, CreateSolidBrush. Изменение координат стрелки в функции Polygon производилось с помощью нехитрых тригонометрических преобразований вида:

    With MassivTotsek2(0)
    .x = ((Cos(ugpovRes1 / 180 * 3.14) * -2) - (Sin(ugpovRes1 / 180 * 3.14) * 10)) + 475
    'CLng(0)
    .y = ((Sin(ugpovRes1 / 180 * 3.14) * -2) + (Cos(ugpovRes1 / 180 * 3.14) * 10)) + 554
    'CLng(-30) 'вверх
    End With
    With MassivTotsek2(1)
    .x = ((Cos(ugpovRes1 / 180 * 3.14) * 0) - (Sin(ugpovRes1 / 180 * 3.14) * 30)) + 475
    ' - 74 'CLng(30) 'право
    .y = ((Sin(ugpovRes1 / 180 * 3.14) * 0) + (Cos(ugpovRes1 / 180 * 3.14) * 30)) + 554 
    '- 74 'CLng(30) 'вниз
    End With
    '''''
    With MassivTotsek2(2)
    .x = ((Cos(ugpovRes1 / 180 * 3.14) * 2) - (Sin(ugpovRes1 / 180 * 3.14) * 10)) + 475
    ' - 74 'CLng(30) 'право
    .y = ((Sin(ugpovRes1 / 180 * 3.14) * 2) + (Cos(ugpovRes1 / 180 * 3.14) * 10)) + 554
    '- 74 'CLng(30) 'вниз
    End With
    

        Все навыки которые Вы получите изучая графические примитивы GDI с большой пользой можно использовать и в DirectDraw. Если бы не удалось воспользоваться GDI для рисования стрелок, пришлось бы применять спрайт для каждого положения шкалы кругового прибора или “придумывать математику” вращения изображения попиксельно, а без искажений сделать это очень трудно.
     

    Примечание!
    Платформе .NET использует для работы с графикой интерфейс GDI+, который имеет быстрые функции для поворота битовой графики.

    Работа с файлами трехмерной графики *.Х

        По мимо вывода битовой графики технология DirectX обладает интерфейсами, реализующими работу с трехмерными объектами. В этой статье будут рассмотрены только возможности DirectX по работе с популярным форматом файла трехмерной графики *.X. Самое замечательное, что готовую трехмерную сцену можно нарисовать в программах типа 3D Studio/MAX или LightWawe. Главное чтобы программа которую Вы используете для создания трехмерной графики могла экспортировать сцену в файл формата *.3DS. После создания файла *.3DS его легко перевести с помощью утилиты “conv3ds” в формат .X, который “понимает” интерфейс Direct3D Retained Mode.
        Все примеры работать с трехмерной сценой на основе файла *.X реализованы через Direct3D Retained Mode (далее D3DRM).
     

    Примечание!
    Низкоуровневый интерфейс Direct3D Immediate Mode рассматриваться не будет.

        Инициализация D3DRM (как и DirectDraw) требует ряда поэтапных стандартных действий, описанных в приведенной ниже простой программе(файлы проекта см. Graphics7).

    1. Создайте проект Standard EXE. Добавьте в форму элемент PictureBox. Подключите к проекту библиотеку типов DirectX(как и для DirectDraw - Project/References/DirectX7 for VB Type Library).
    2. Добавте к приложению модуль – Project/AddModule. В разделе модуля General, после Option Explicit введите следующий код:

    Option Explicit
    Public DxVer7 As New DirectX7 'Самый главный
    Public DDrawVer7 As DirectDraw7
    ''''''''''''''''''''''''''''''''''
    Public D3DRMVer3 As Direct3DRM3 'Для основной работы и -.X
    Public UstrRender As Direct3DRMDevice3 'Устр Рендеринга
    Public Viewport As Direct3DRMViewport2 '
    Public BuilderKubik As Direct3DRMMeshBuilder3
    Public BuilderSar As Direct3DRMMeshBuilder3
    Public Scene As Direct3DRMFrame3
    Public Camera As Direct3DRMFrame3
    Public Kubik As Direct3DRMFrame3
    Public Sar As Direct3DRMFrame3
    Public SvetFrm As Direct3DRMFrame3
    Public Svet As Direct3DRMLight
    ''''''''''''''''''''''''''''''''''
    Public OtsetsenieWndCliper As DirectDrawClipper
    
    Public Sub IniVseObjectDX7(VBPict As PictureBox)
    Set DDrawVer7 = DxVer7.DirectDrawCreate("") 'Первый всегда DDraw
    Set D3DRMVer3 = DxVer7.Direct3DRMCreate 'Создаем 3DRM
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    Set OtsetsenieWndCliper = DDrawVer7.CreateClipper(0) 'Так как прилоение оконное
    With VBPict
    OtsetsenieWndCliper.SetHWnd .hWnd
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    Set UstrRender = D3DRMVer3.CreateDeviceFromClipper(OtsetsenieWndCliper, _
    "", .ScaleWidth, .ScaleHeight)
    UstrRender.SetQuality D3DRMRENDER_GOURAUD
    Set Scene = D3DRMVer3.CreateFrame(Nothing) 'Родитель для камеры
    Scene.AddLight D3DRMVer3.CreateLight(D3DRMLIGHT_AMBIENT, _
    DxVer7.CreateColorRGB(0, 1, 0)) 'Создали свет для Scene
    Set Camera = D3DRMVer3.CreateFrame(Scene) 'Прекрепляем камеру к порту просмотра
    Set Kubik = D3DRMVer3.CreateFrame(Scene)
    Set Sar = D3DRMVer3.CreateFrame(Scene)
    ''''''''''Svet
    Set SvetFrm = D3DRMVer3.CreateFrame(Scene)
    Set Svet = D3DRMVer3.CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, 4, 0, 0)
    SvetFrm.AddLight Svet
    Set Viewport = D3DRMVer3.CreateViewport(UstrRender, _
    Camera, 0, 0, .ScaleWidth, .ScaleHeight) 'Создали порт просмотра
    End With
    End Sub
    
    Public Sub CreateMyObject()
    Set BuilderKubik = D3DRMVer3.CreateMeshBuilder
    BuilderKubik.LoadFromFile App.Path & "\n2.x", 0, D3DRMLOAD_FROMFILE, Nothing, Nothing
    Kubik.AddVisual BuilderKubik
    Set BuilderSar = D3DRMVer3.CreateMeshBuilder
    BuilderSar.LoadFromFile App.Path & "\sar.x", 0, D3DRMLOAD_FROMFILE, Nothing, Nothing
    BuilderSar.ScaleMesh 0.4, 0.4, 0.4
    Sar.AddVisual BuilderSar
    End Sub

        Инициализация объектов происходит в функции которой передается по ссылке окно вывода - Public Sub IniVseObjectDX7(VBPict As PictureBox). Обратите внимание на присутствии интерфейса DirectDraw который нужен для создания отсечения. После создания интерфейса DirectDraw создается интерфейс D3DRM. Он необходим для порождения таких типов как: Direct3DRMDevice3 – устройство рендеринга; Direct3DRMViewport2 – порт просмотра(просмотр привязан к фрейму камеры); Direct3DRMMeshBuilder3 – “строитель” фрейма в сцене; Direct3DRMFrame3 –объект обладающий координатами(возможно формой и размерами или другими характеристиками); Direct3DRMLight – объект для освещения фреймов имеющих форму. Все фреймы при создании прикрепляются к “главному” фрейму сцены методом - D3DRMVer3.CreateFrame(Scene).
        Загрузка файлов *,X происходит в процедуре Public Sub CreateMyObject(). Обратите внимание, что при загрузке файлов возможно масштабирование с помощью функции BuilderSar.ScaleMesh 0.4, 0.4, 0.4, принадлежащей объекту типа Direct3DRMMeshBuilder3. Из двух файлов *.X загружаются кубик – файл n2.x и шарик – файл sar.x(оба файла должны находится в одной папке с проектом)

    3. Для формы и ее событий введите следующий код :

    Option Explicit
    Dim FlagPaint As Long
    Dim Xp As Long
    Dim Zp As Long
    Dim Yp As Long
    
    Private Sub Form_Load()
    Zp = -20
    Xp = -5
    Yp = -5
    Dim Flag As Boolean
    Flag = True
    FlagPaint = 0
    Show 'Принудительный показ
    DoEvents
    ModD3D.IniVseObjectDX7 Picture1
    FlagPaint = 1
    '''Инициализация закончена'''
    ''''''Создание объектов''''''
    ModD3D.CreateMyObject
    ''Настройка
    Kubik.SetColor &HFFFF6060
    Kubik.SetMaterialMode D3DRMMATERIAL_FROMFRAME
    Do While Flag = True
    DoEvents
    D3DRMVer3.Tick 1
    Camera.SetPosition Scene, 0, 0, -40
    Kubik.SetPosition Scene, Xp, Yp, Zp
    Loop
    '''''''''''''''''''''''''''''
    End Sub
    
    Private Sub Picture1_KeyDown(KeyCode As Integer, Shift As Integer)
    Dim TempVector As D3DVECTOR
    If KeyCode = 38 Then 'Вверх
    Zp = Zp + 1
    ElseIf KeyCode = 40 Then
    Zp = Zp - 1
    ElseIf KeyCode = 39 Then
    Xp = Xp + 1
    ElseIf KeyCode = 37 Then
    Xp = Xp - 1
    ElseIf KeyCode = 89 Then
    Yp = Yp + 1
    ElseIf KeyCode = 72 Then
    Yp = Yp - 1
    End If
    End Sub
    
    Private Sub Form_Paint()
    If FlagPaint = 1 Then ModD3D.UstrRender.HandlePaint Picture1.hDC
    End Sub
    
    Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Set BuilderKubik = Nothing
    Set Viewport = Nothing
    Set Kubik = Nothing
    Set Svet = Nothing
    Set SvetFrm = Nothing
    Set Camera = Nothing
    Set Scene = Nothing
    Set UstrRender = Nothing
    Set OtsetsenieWndCliper = Nothing
    Set D3DRMVer3 = Nothing
    Set DDrawVer7 = Nothing
    End
    End Sub

        В разделе формы General, после Option Explicit представлены три переменные для хранения координат кубика. В событии Private Sub Form_Load() вызываются две функции из модуля. Для инициализации объектов D3DRM вызывается ModD3D.IniVseObjectDX7 Picture1, а для загрузки файлов *.X вызывается ModD3D.CreateMyObject. Фрейму кубика назначается красный цвет в строках:

    Kubik.SetColor &HFFFF6060
    Kubik.SetMaterialMode D3DRMMATERIAL_FROMFRAME

        После функций вступает в работу “бесконечный” цикл. В за один проход “бесконечный” цикл выполняет рендеринг сцены с помощью метода D3DRMVer3.Tick 1. Завершается цикл установкой фреймов камеры и кубика. Камере назначен порт просмотра - через него мы обозреваем сцену. Камера статична, а координаты кубика изменяются кнопками клавиатуры в событии Private Sub Picture1_KeyDown(KeyCode As Integer, Shift As Integer). Кнопки со стрелками перемещают кубик по горизонтали, а кнопки “Y” и “H” по вертикале. На рис. 17, 18 и 19 можно видеть приложение в работе. Рис. 18 показывает, что произойдет если исключить событие Private Sub Form_Paint().


    Рис. 17, Рис. 18, Рис. 19

    Пример построения трехмерного технического объекта

        В заключении приведем пример использования интерфейса D3DRM на базе которого можно построить трехмерное представление технического объекта, например воображаемый отсек подводной лодки. Приложение в работе представлено на рисунке 20 (файлы проекта см. Graphics8).


    Рис. 20

        Перемещение по трехмерной сцене производится с помощью кнопок клавиатуры. Просмотр осуществляется через фрейм камеры, но в данном случае камера неподвижна. Фреймы применяются и для источников освещения. Качеством отображения можно управлять меняя значения “Уд” и “Хор”(более сглаженные углы).
        В сцене предусмотрены активные элементы – это четыре клапана. При нажатии на клапан он окрасится в красный цвет, а в текстовой панели отобразится его номер (рис.21).



    Рис. 21

        Важным отличием от предыдущего примера является наличие текстур. На текстурах можно нарисовать элементы, которые придадут больше реализма трехмерной сцене. В данном случае текстуры нанесены на воображаемый пульт управления и переборку между отсеками. Самое сложное правильно растянуть текстуры по объекту. Для текстур используется тип - Direct3DRMTexture3, а для натяжения на объект тип - Direct3DRMWrap. Создать текстуру можно из файла или поверхности DirectDraw(в данном приложении текстура создана из поверхности). Код по созданию и наложению текстуры может иметь вид:

    Set PultTexture = D3DRMVer3.CreateTextureFromSurface(OsnovaBMP1)
    'Set PultTexture = D3DRMVer3.LoadTexture("pult1.bmp")
    Set RastiaskaPult = D3DRMVer3.CreateWrap(D3DRMWRAP_FLAT, _
    Nothing, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0.09, _
    0,1 / 26, 1 / 20)
    RastiaskaPult.Apply BuilderPult
    BuilderPult.SetTexture PultTexture


        Обратите внимание на флаг D3DRMWRAP_FLAT в функции CreateWrap – это значит, что текстура наносится на плоскую поверхность. Можно наносить текстуры на цилиндрические и сферические поверхности.
        Сложность трехмерной сцены, количество в ней активных элементов и текстур, ограничена только аппаратными возможностями Вашего компьютера.

    Заключение

        Среда Visual Basic достаточно проста в освоении, имеет дружественный и интуитивно понятный интерфейс, одновременно с этим Visual Basic начиная с версии 5 отвечает запросам самых требовательных Windows-разработчиков.