VBCoding Статьи Visual Basic 6 Мультимедиа Быстрое создание графических приложений с использованием Win32 GDI и Direct3D

Visual Basic 6
Быстрое создание графических приложений с использованием Win32 GDI и Direct3D

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

    Считается, что возможности 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 функциями

    Рассмотрим простой пример работы с графическими функциями 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-разработчиков.

Источник: http://www.vbstreets.ru/ 

 

Добавить комментарий


Защитный код
Обновить

 
VBCoding Статьи Visual Basic 6 Мультимедиа Быстрое создание графических приложений с использованием Win32 GDI и Direct3D  
Powered by Exponenta -