3D графика на основе OpenGL WinApi C++

Автор: | 05.02.2018

Описание шаблона приложения
Поверхностная модель. Списки. Стек матриц. OpenGL – машина состояний
Преобразование объекта
Построение вида
Перспективная и ортогональная проекции. Отсечение
Анимация
Режимы модели и проекции
Контрольные вопросы
Файлы-исходники приложения

Описание шаблона приложения

Изложение теории будет идти параллельно с практической реализацией на основе шаблона приложения для создания модели рупорной антенны.
Создайте проект WinAPI приложения на основе файлов-исходников.
По сравнению с шаблоном WinAPI приложения без использования OpenGL кроме основного файла main.cpp в проект были добавлены файл api.h (подключает библиотеки WinAPI и OpenGL) и файлы (engine.h, engine.cpp) для описания класса Engine. В классе Engine инкапсулированы функции, обеспечивающие описание модели объекта (Draw, DrawAxes, DrawModel) а также устанавливающие взаимосвязь кон-текста устройства Windows с контекстом воспроизведения OpenGL (функция SetWindowPixelFormat).

В файле api.h подключаются базовая и вспомогательные библиотеки opengl. Базовая библиотека OpenGL установлена как часть операционной системы Windows. Файлы библиотеки GLUT и GLАUX доступны бесплатно в Интернете. Например, для перекачки файлов библиотеки GLUT наберите в поисковой системе ключевое слово glutdll и получите заархивированные файлы glutdllsbeta37.zip. Архив содержит файлы, которые размещаются в директориях проекта, там же, где и исходные файлы.

При запуске программы локальная СК совпадает с глобальной СК.

Поверхностная модель. Списки. Стек матриц. OpenGL – машина состояний

В исходной программе определяется проволочная модель антенны. Необходимо создать поверхностную модель.

Проволочная модель создается примитивами GL_LINE_LOOP, GL_LINES, поверхностная –GL_TRIANGLE_STRIP.

Геометрический примитив (точку, линию, треугольник и т.д.) определяется текущим цветом и упорядоченным списком вершин между glBegin (тип примитива) и glEnd (), например:

glColor3d(1,0,0);
 glBegin(GL_LINES);
 glVertex3f (0,0,0);
 glVertex3f (0,0,1);
 glEnd();

Нижняя (правая) и верхняя (левая) поверхности антенны идентичны. Поэтому для их создания используются подпрограммы. Аналогом подпрограммы в OpenGL есть список. Пример определения и вызова списка приводится ниже:

glNewList(1, GL_COMPILE);
 glColor3d(0,0,1);
 glBegin(GL_LINES);
 glVertex3f (0,0,0);
 glVertex3f (0,0,1);
 glEnd();
 glEndList();
 glCallList(1);

Нижняя (правая) и верхняя (левая) поверхности отличаются положением. Перед повторным вызовом списка необходимо выполнить преобразование поворота. После каждого преобразования становится актив-ной текущая (локальная) СК. Для возврата в исходную (глобальную) СК необходимо сохранять ее параметры в стеке матриц (команда glPushMatrix) и восстанавливать их (команда glPopMatrix). Пример:

glCallList(1);
 glPushMatrix();
 glRotatef(180.0, 0.0, 0.0, 1.0);
 glCallList(1);
 glPopMatrix();

Результат может быть таким:



Для того, чтобы не смешивались цвета поверхностей, не забудьте включить состояние теста глубины. Для этого используйте команду glEnable(GL_DEPTH_TEST), в программе она закомментирована. OpenGL – машина состояний. Т.е., режимы и атрибуты в OpenGL будут оставаться в силе, пока они не изменяются. Большинство переменных состояния могут быть включены или отключены по мере необходимости.

Преобразование объекта

Задание. Обеспечить построение проекции антенны, расположенной по отношению к глобальной системе координат в соответствии с заданием (см. табл.). Дорисовать ось антенны.


Для выполнения задания необходимо изменить положение антенны при помощи упорядоченной последовательность функций, которые определяют поворот и перемещение текущей СК, относительно которой определена модель антенны. Примеры функций:

glTranslatef(0.0, 1.5, 0.0); // СК переносится на 1.5 вдоль Y
glRotatef(30.0, 1.0, 1.0, 0.0); // СК поворачивается на 30 градусов вокруг оси x

Построение вида

Вид определяется взаимным положением объектов и наблюдателя и плоскости проекций

Возможны 2-а способа получения вида на объект:

  • объекты перемещаются, наблюдатель неподвижен;
  • объекты неподвижны, наблюдатель перемещается.

Эти действия можно сравнить с фотосессией, при которой перемещаются как фотограф, так и фотомодели. Математически они описываются матрицами преобразований объекта и матрицей вида. Обе матрицы в конечном итоге перемножаются, а композиция матриц используется для перерасчета точек объектов сцены.
Выше мы рассмотрели 1-й способ получения вида, теперь рассмотрим 2-й. Матрица вида в OpenGL определяется функцией:

gluLookAt(eyeX, eyeY, eyeZ, atX, atY, atZ, upX, upY, upZ)


Параметры (eyex, eyey, eyez), параметры (atx, aty, atz) определяют начало и конец вектора наблюдения. Параметры (upx, upy ,upz) определяют поворот камеры. Если, например, камера не повернута, то задается значение (0,1,0). Параметры функции gluLookAt определяются относительно текущей СК. Поэтому, ее целесообразно использовать до преобразования всех объектов сцены, пока видовая матрица еще единичная.

Перспективная и ортогональная проекции. Отсечение

Задание. Установить центральную проекцию (функция gluPerspective). Найти функцию в программе и, меняя параметры функции, определить наиболее наглядный вид рупорной антенны.
Два вида проекций можно создавать в OpenGL – центральная и ортогональная.

Команды, которые задают способ проецирования, одновременно определяют объем, за пределами которого объекты отсекаются. В случае центрального проецирования это усеченная пирамида, при ортогональном проецировании – параллелепипед. Объем видимости определяется в СК, ось z которой проходит из точки наблюдения через центр сцены. Обе точки определяются командой gluLookAt. Сечение пирамиды в плоскости, проходящей через центр сцены, определяет размеры видового экрана.
Центральная проекция определяется функцией:

gluPerspective(fovy, aspect, zNear, zFar ),

где:
fovy определяет угол обзора (в градусах) пирамиды отсечения в горизонтальной плоскости;
aspect определяет отношение угла обзора в горизонтальной плоскости к углу обзора во фронтальной плоскости, одновременно этот параметр определяет отношение ширины к высоте для видового экрана.
zNear определяет расстояние от наблюдателя к ближней плоскости отсечения;
zFar определяет расстояние от наблюдателя к дальней плоскости отсечения.
Ортогональная проекция определяется функцией:

glOrtho(left, right, bottom, top, near, far),

где:
left, right определяют координаты влево и вправо для ближней плоскости отсечения
bottom, top определяют координаты вниз и вверх для ближней плоскости отсечения;
near, far определяют расстояния вдоль оси z до ближней и дальней плоскостей отсечения.

Анимация

Задание. Обеспечить возможность вращения антенны:

  • изменяя угол alpha при перемещении курсора мышки вправо (влево) и угол beta при движении вверх (вниз).
  • вокруг собственной оси (изменяется угол gamma) при движении курсора мышки и нажатой клавише Ctrl.
  • сканированием – одновременное вращение вокруг оси Y глобальной СК и вокруг собственной оси (ось z). Сканирование обеспечить при помощи таймера.

Для реализации заданий необходимо в класс Engine добавить переменные alpha, beta и gama. Эти переменные инициализируются при событии WM_CREATE:

engine->alpha=0, engine->beta=0, engine->gama=0;

При событиях WM_LBUTTONDOWN и WM_MOUSEMOVE этим переменным передаются перемещения курсора:

case WM_LBUTTONDOWN:
 mouse_point.x = LOWORD(lParam); // Координаты в системе окна
 mouse_point.y = HIWORD(lParam);
 break;
 case WM_MOUSEMOVE:
 if (UINT(wParam) & MK_LBUTTON)
 {
 if (UINT(wParam) & MK_CONTROL) {
 engine->gama += LOWORD(lParam) - mouse_point.x;
 engine->gama += HIWORD(lParam) - mouse_point.y;
 } else {
 engine->alpha += LOWORD(lParam) - mouse_point.x;
 engine->beta += HIWORD(lParam) - mouse_point.y;
 }
 mouse_point.x = LOWORD(lParam); // Координаты в системе окна
 mouse_point.y = HIWORD(lParam)
 InvalidateRect(hWnd, NULL, FALSE);
 }
 break;

Добавьте в файл engine.h описание структуры Point:

#ifndef _POINT
 struct _Point
 {
 double x, y;
 };
 #define _POINT
 #endif

В определение функции Draw (файл engine.cpp) введите описание преобразований вращения с параметрами-переменными:

//Определяется матрица модели (Mmodel)
 glTranslatef(0.0, 0.15, 0.0);
 glRotatef(alpha, 0.0, 1.0, 0.0);
 glRotatef(30.0, 0.0, 1.0, 0.0);
 glRotatef(beta, 1.0, 0.0, 0.0);
 glRotatef(-60.0, 1.0, 0.0, 0.0);
 glTranslatef(0.0, 0.0, 1.10);
 glRotatef(gama, 0.0, 0.0, 1.0);
 glRotatef(20.0, 0.0, 0.0, 1.0);

Для обеспечения сканирования антенны необходимо создать таймер.

SetTimer(hWnd,TIMER_2SEC,2000,NULL);// интервал две секунды

При вызове события таймера значения переменной углов изменяются с определенным шагом.

case WM_TIMER: // обработка сообщения WM_TIMER
 alpha = alpha +1.0;
 …
 InvalidateRect(hWnd, NULL, FALSE); //вызов события WM_PAINT
 break;

Обеспечьте возможность запуска вращения треугольника при нажатии на клавишу P (пуск) и остановку – при нажатии на клавишу S (stop). Пример оформления события нажатия на клавишу приводится ниже:

case WM_KEYDOWN:
 int KeyPressed;
 KeyPressed = int(wParam);
 if (KeyPressed == int('P'))
 { ...
 }
 break;

Работа таймера прекращается функцией:

KillTimer (hWnd,TIMER_2SEC); // уничтожение таймера

Режимы модели и проекции

Выше были описаны процессы моделирования сцены и создание проекции с отсечением. Моделирование сцены осуществлялось 2-я способами – через преобразования объекта и определение вида. Оба способа используют одни и те же базовые матричные преобразования, только для определения вида используется обратная матрица. В конечном итоге матрицы перемножаются, и сцена описывается одной матрицей (матрицей «модель-вид»).
Механизм моделирования проекции с отсечением несколько другой. Поэтому, при моделировании изображения используются матрица «модель-вид» и матрица проекции.
Для работы с матрицами предусмотрены 2-а режима, которые переключаются командой glMatrixMode:

glMatrixMode(GL_PROJECTION) – активен режим проекции;
 glMatrixMode(GL_MODELVIEW) – активен режим модели.

После включения режима инициализируется работа с матрицей командой glLoadIdentity(). И только после этого, можно использовать соответствующие режиму команды.

Контрольные вопросы:

  1. Параметры команд определения матрицы преобразований объекта (glTranlsate, glRotate, glScale).
  2. Глобальная и локальная СК. Относительно какой СК происходит следующее преобразование – текущей или глобальной? Как сохранить текущую СК?
  3. Параметры команды определения матрицы вида (gluLookAt). Когда целесообразно использовать эту команду?
  4. Модель центральной и ортогональной проекции. В какой СК определяется объем отсечения? Как определяются размеры окна изображения? Как определяется начало текущей СК в окне изображения? Как определяется положение осей текущей СК на изображении?
  5. Параметры команд gluPerspective и glOrtho.
  6. Чем обусловлено использование режимов проекции и модели?
  7. Для чего используются списки команд OpenGL
  8. OpenGL — машина состояний. Что это означает?

Файлы-исходники приложения

Header Files

api.h

#ifndef __API__
#define __API__
//#pragma comment( lib, "opengl32.lib" ) // Подключается библиотека opengl32.lib
//#pragma comment( lib, "glu32.lib" ) // Подключается библиотека glu32.lib
#pragma comment( lib, "glaux.lib" ) // Подключается библиотека glaux.lib
#pragma comment( lib, "glut32.lib" ) // Подключается библиотека glut32.lib
#include <windows.h> // Заголовочный файл для использования функций Windows
//#include <gl\gl.h> // Заголовочный файл библиотеки opengl32
#include <gl\glu.h> // Заголовочный файл библиотеки glu32
#include "glaux.h" // Заголовочный файл библиотеки glaux
#include "glut.h" // Заголовочный файл библиотеки glut32
#endif
// Файли glaux.h, glut.h, glaux.lib,glut32.lib размещаются в директории проекта, 
// где и файлы-исходники (.cpp) и заголовочные файлы (.h)

engine.h

#include "api.h"
#ifndef __ENGINE
#define __ENGINE
class Engine {
 GLsizei Height, Width;
 GLvoid DrawAxes(GLvoid); // Отрисовка осей
 GLvoid DrawModel(GLvoid); // Отрисовка модели
public:
 GLvoid Resize(GLsizei width, GLsizei height); // Функция, вызываемая при изменении размеров окна
 GLvoid Init(GLvoid); // Функция, для задания начальных параметров
 GLvoid Draw(GLvoid); // Отрисовка (render) сцены 
 int SetWindowPixelFormat(HDC hDC); //функция, которая устанавливает параметры контекста воспроизведения OpenGL 
};
#endif

Source Files

main.cpp

//#include<windows.h>
#include "engine.h"
HWND hWnd;
HGLRC hGLRC;
HDC hDC;
Engine *engine;

LRESULT CALLBACK WindowFunc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
float pos[4] = {3,3,3,1};
float dir[3] = {-1,-1,-1};
PAINTSTRUCT ps;
switch(msg)
{
case WM_CREATE:
engine = new Engine();
hDC = GetDC(hWnd); // получаем контекст устройства нашего окна
engine->SetWindowPixelFormat(hDC); // устанавливаем параметры контекста воспроизведения OpenGL
hGLRC = wglCreateContext(hDC); // создаем контекст воспроизведения OpenGL
wglMakeCurrent(hDC, hGLRC); // делаем его текущим
engine->Init();
break;
case WM_DESTROY:
if (hGLRC) 
{ // удаляем созданный выше контекст воспроизведения OpenGL
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hGLRC);
} // освобождаем контекст устройства нашего окна
ReleaseDC(hWnd, hDC);
PostQuitMessage(0);
break;
case WM_PAINT:
BeginPaint(hWnd, &ps);
engine->Draw();
EndPaint(hWnd, &ps);
break;
case WM_SIZE:
engine->Resize( LOWORD(lParam), HIWORD(lParam) );
break; 
case WM_ERASEBKGND:
 return 1;
break;
default:
return DefWindowProc(hWnd,msg,wParam,lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hThisInst,HINSTANCE hPrevInst,LPSTR str,int nWinMode)
{
MSG msg;
WNDCLASS wcl;
wcl.hInstance=hThisInst;
wcl.lpszClassName = (LPCWSTR) "OpenGLWinClass";
wcl.lpfnWndProc = WindowFunc;
wcl.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wcl.hIcon = NULL;
wcl.hCursor = LoadCursor(NULL,IDC_ARROW);
wcl.lpszMenuName = NULL;
wcl.cbClsExtra = 0;
wcl.cbWndExtra = 0;
wcl.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
RegisterClass(&wcl);
hWnd = CreateWindow((LPCWSTR) "OpenGLWinClass",L"Win32 OpenGL Template", 
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
200,
150,
400,
420,
HWND_DESKTOP, NULL,
hThisInst, NULL);
ShowWindow(hWnd,nWinMode);
UpdateWindow(hWnd);
while(1)
{
while( PeekMessage(&msg,NULL,0,0,PM_NOREMOVE) ) 
if(GetMessage(&msg,NULL,0,0))
{ 
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
return 0;
//display(); // функция display вызывается в бесконечном цикле, причем, когда в очереди сообщений окна нет ничего.
}
return 0;
}

engine.cpp

#include "engine.h"
GLvoid Engine::Resize(GLsizei width, GLsizei height){
 if (height == 0) 
 {
 height = 1; 
 }
 glViewport(0, 0, width, height); // Устанавливается область просмотра
 Height = height;
 Width = width;
 //glDepthRange(0, 1); //Глубина каждой точки записывается в z-буфер
 //с помощью которого OpenGL удаляет невидимые линии и поверхности
}

GLvoid Engine::Init(GLvoid){
 glClearColor(0.85f, 0.85f, 1.0f, 1.0f); // Устанавливается синий фон
 //glClearDepth(1.0f); // Устанавливается значение для
 // заполнения буфера глубины по умолчанию
 //glEnable(GL_DEPTH_TEST); // Включается тест глубины
 //glDepthFunc(GL_LEQUAL); // Устанавливается значение, используемое
 // в сравнениях при использовании
 // буфера глубины
//glShadeModel(GL_SMOOTH); // Включается плавное затенение
 //glEnable(GL_LINE_SMOOTH); // Включается сглаживание линий
 //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // Выбирается самый качественный
 // режим сглаживания для линий
 //glEnable(GL_BLEND); // Включается смешение цветов, необходимое
 //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // для работы сглаживания и задается
 // способ смешения
}

GLvoid Engine::Draw(GLvoid) 
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Очищается буфер кадра и буфер глубины
 // Текущая матрица сбрасывается на единичную
 glMatrixMode(GL_PROJECTION); // Действия будут производиться с матрицей проекции
 glLoadIdentity();
 //glTranslatef(0.0, 0.5, 0.0); 
 //glFrustum(-1, 1, -1, 1, 3, 10); // Устанавливается перспективная проекция
 gluPerspective (30, 1, 3, -3); // Устанавливается перспективная проекция 
 //glOrtho(-1, 1, -1, 1, 3, 10); // Устанавливается ортогональная проекция 
 //gluOrtho2D(-10, 10, -10, 10); // Устанавливается ортогональная проекция
glMatrixMode(GL_MODELVIEW); // Действия будут производиться с матрицей модели
 glLoadIdentity(); 
 gluLookAt (5, 5, 5, 0, 0, 0, 0, 1, 0); //Определяется вид ( Mview )
 DrawAxes(); //рисуются оси камеры
//Определяется матрица модели (Mmodel)
 //glTranslatef(0.0, 0.0, 8.0); // Система координат переносится на 8 единиц вглубь сцены 
 //glRotatef(-90.0, 1.0, 0.0, 0.0); // и поворачивается на 30 градусов вокруг оси x,
 //glRotatef(90.0, 0.0, 1.0, 0.0); // а затем на 20 градусов вокруг оси y
 //glRotatef(40.0, 0.0, 0.0, 1.0); // а затем на 40 градусов вокруг оси z
 //определена матрица ( Mview * Mmodel)
 //определена матрица (Mprojection*Mview * Mmodel)
// МОДЕЛИРОВАНИЕ
 //Рисуются оси модели (антенны)
 glPushMatrix();
 glScalef(0.5, 0.5, 0.5); // Масштабирование по осям
 DrawAxes(); 
 glPopMatrix();
 //glScalef(2, 2, 2); // Масштабирование по осям
//рисуется модель (антенна) 
 DrawModel(); 
SwapBuffers(wglGetCurrentDC());
}GLvoid Engine::DrawAxes(GLvoid) // Отрисовка осей
{
glColor3d(1,0,0);
 glBegin(GL_LINES); 
 glVertex3f (0,0,0); 
 glVertex3f (1,0,0); 
 glEnd();
glColor3d(0,1,0);
glBegin(GL_LINES); 
 glVertex3f (0,0,0); 
 glVertex3f (0,1,0); 
 glEnd();
glColor3d(0,0,1);
glBegin(GL_LINES); 
 glVertex3f (0,0,0); 
 glVertex3f (0,0,1); 
 glEnd();
 }
//antena v10-----v9
//     v6------v5 | 
//  v1--|-----v0| |
//  |   |     | | v12
//  |  v7-----|-v8
//  |         |
// v2--------v3
GLvoid Engine::DrawModel(GLvoid) // Отрисовка модели
{
 glColor3d(0,0,0);
glBegin(GL_LINE_LOOP);
 // v0-v1-v2-v3
 glVertex3f(0.4,0.2,0);
 glVertex3f(-0.4,0.2,0);
 glVertex3f(-0.4,-0.2,0);
 glVertex3f(0.4,-0.2,0);
glEnd();
glBegin(GL_LINE_LOOP);
 // v5-v6-v7-v8
 glVertex3f(0.2,0.1,-0.4);
 glVertex3f(-0.2,0.1,-0.4);
 glVertex3f(-0.2,-0.1,-0.4);
 glVertex3f(0.2,-0.1,-0.4);
glEnd();
glBegin(GL_LINE_LOOP);
 // v9-v10-v11-v12
 glVertex3f(0.2,0.1,-0.8);
 glVertex3f(-0.2,0.1,-0.8);
 glVertex3f(-0.2,-0.1,-0.8);
 glVertex3f(0.2,-0.1,-0.8);
glEnd();
glBegin(GL_LINE_STRIP);
 // v0-v5-v9
 glVertex3f(0.4,0.2,0);
 glVertex3f(0.2,0.1,-0.4);
 glVertex3f(0.2,0.1,-0.8);
glEnd();
glBegin(GL_LINE_STRIP);
 // v1-v6-v10
 glVertex3f(-0.4,0.2,0);
 glVertex3f(-0.2,0.1,-0.4);
 glVertex3f(-0.2,0.1,-0.8);
glEnd();

glBegin(GL_LINE_STRIP);
 // v2-v7-v11
 glVertex3f(-0.4,-0.2,0);
 glVertex3f(-0.2,-0.1,-0.4);
 glVertex3f(-0.2,-0.1,-0.8);
glEnd();
glBegin(GL_LINE_STRIP);
 // v3-v8-v12
 glVertex3f(0.4,-0.2,0);
 glVertex3f(0.2,-0.1,-0.4);
 glVertex3f(0.2,-0.1,-0.8);
glEnd();
}
// код функции, которая устанавливает параметры контекста воспроизведения OpenGL. 
int Engine::SetWindowPixelFormat(HDC hDC)
{
int m_GLPixelIndex;
PIXELFORMATDESCRIPTOR pfd;
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | 
PFD_SUPPORT_OPENGL | 
PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cRedBits = 8;
pfd.cRedShift = 16;
pfd.cGreenBits = 8;
pfd.cGreenShift = 8;
pfd.cBlueBits = 8;
pfd.cBlueShift = 0;
pfd.cAlphaBits = 0;
pfd.cAlphaShift = 0;
pfd.cAccumBits = 64; 
pfd.cAccumRedBits = 16;
pfd.cAccumGreenBits = 16;
pfd.cAccumBlueBits = 16;
pfd.cAccumAlphaBits = 0;
pfd.cDepthBits = 32;
pfd.cStencilBits = 8;
pfd.cAuxBuffers = 0;
pfd.iLayerType = PFD_MAIN_PLANE;
pfd.bReserved = 0;
pfd.dwLayerMask = 0;
pfd.dwVisibleMask = 0;
pfd.dwDamageMask = 0;
// Передается на рассмотрение системе, выбранный нами формат пикселей. Функция просматривает в контексте устройства - hdc наиболее подходящий формат пикселей и выбирает его
m_GLPixelIndex = ChoosePixelFormat( hDC, &pfd);
if(m_GLPixelIndex==0) // Let's choose a default index.
{
m_GLPixelIndex = 1; 
if(DescribePixelFormat(hDC,m_GLPixelIndex,sizeof(PIXELFORMATDESCRIPTOR),&pfd)==0)
return 0;
}
// установить формат пикселей в контексте устройства
if (SetPixelFormat( hDC, m_GLPixelIndex, &pfd)==FALSE)
return 0;
return 1;
}


Автор: Николай Свирневский

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *