帮酷LOGO
0 0 评论
文章标签:DIA  Directx  DIR  template  pix  对话框  SURF  TEMP  

介绍

这是我关于DirectX编程系列文章的第一部分: 标题为"我们从哪里来"。 我将简要介绍一下有些技术在时间里可以能用到的一些图纸。 of的工作台。这将为我们更好地理解DirectX和现代图形硬件下的奇妙事物。 本教程包含六个部分,有两个主要示例,介绍如何使用本教程提供的代码。

  • DirectX教程部件 I: DirectX对话框模板
  • DirectX教程第二部分:在弹球游戏中使用 CDirectXDialog
  • DirectX教程第三部分:基本 3D
  • DirectX教程第IV部分:在 Vectorballs scroller中使用 Vector
  • DirectX教程零件 V: Lambert和多边形填充
  • DirectX教程第VI部分:完成 direct 3d方法。

在本教程最后,你应该有关于 3D 数学。多边形填充的基本知识,以及使用DirectX的方式。 但是首先,让我们开始使用 Glimpse 在DirectX功能上进行远征以快速访问 Surface 内存。 这将允许我们模拟VGA像绘图到视频内存( 记住 0 xA000 )。 我们的Lambert和多边形填充器只能使用这个功能在屏幕上操作像素。

为了给出这个像素程序的快速性,我决定计算一个基于函数的数学图形(。查看上面的屏幕截图)。f(x,y) = ( x2+y2 ) 2. 正如你所看到的,这个函数与两个轴对称,因这里只能计算一个四分之一,并将值翻转。 但是这样做不会阻止我们从任何季度绘制所有的像素。 如果在平均分辨率上最大化对话框,这意味着你必须绘制 780000个像素。 在我目前的硬件配置( 奔腾 IV,3 GHz。) 中,只需要 72毫秒。 顺便说一下,我在第一个计算机( 这是一个 Amiga,只有 7.14兆赫) 上可以能已经做了 15年了。 对于 320 x256=81920pixels的默认分辨率,打印整个图像大约为 5分钟( 我已经在MC6800x0程序集中完成了所有的编程)。 对于硬件爱好者,让我们计算速度的增长: 我认为这个值是惊人的,不是?

编译要求

你必须下载并安装Microsoft以编译本教程提供的示例项目。 如果编译器找不到合适的DirectX头,请查看你的项目设置。 你必须指定公共包含的位置。 我的特殊情况下,我在 C:DXSDK. 中安装了 DirectX SDK,因此项目设置的附加包含路径是c: dxsdksamplesc++commoninclude。

直接 Surface 访问

IDirectDrawSurface7 接口为直接操作它的像素提供了一个获取 DirectX Surface 内存的方法。 方法被称为 Lock,你将必须注意,当绘图到达 Surface 内存时,将释放锁。 适当的释放方法命名为 Unlock。 虽然应用程序锁定了 Surface 内存,但没有它的他的DirectX单元可以访问它,甚至图形硬件本身。 因此,你不应该在实际的DirectX应用程序中使用这里方法,因为整体的性能。 但在我们的情况下这并不是很重要。 最后,我们只对基于像素的直接绘图例程的简单实现感兴趣。

HRESULT Lock(LPRECT lpDestRect, 
 LPDDSURFACEDESC2 lpDDSurfaceDesc, 
 DWORD dwFlags, HANDLE hEvent );
HRESULT UnLock(LPRECT lpRect);

当你获得对 Surface 内存的访问时,DirectX运行时将在名为 DDSURFACEDESC2的结构中返回关于该 Surface的信息。 DirectX Surface的内存布局可以根据系统设置的显示而变化。 因此,我们必须仔细检查这个结构来了解哪个字节对应于屏幕上的哪个像素。 可以将 Surface 内存分为可见区域和非可视区域。 可见区域的宽度和高度由 DDSURFACEDESC2 结构的属性 dwWidthdwHeight 确定。 我不太确定为什么有一个不可以见的部分,但是我想它与水平滚动有关。 通过更改 Surface的起始地址,可以操纵每个扫描行使用内存的哪个部分。 效果是,非可以见区域中的像素变为可以见,看起来像滚动曲面。

现在,要改变 Surface 上的像素,我们必须将屏幕坐标映射到 Surface 内存中的位置。 这个映射可以通过线性数学函数来描述:

memory_position = start_address + x * bytes_per_pixel + y * bytes_per_line

每个像素都在内存中使用一些位,根据系统设置的显示。 为了确定像素( 变量 bytes_per_pixel ) 实际占用的字节数,我们可以查看 DDPIXELFORMAT 结构的dwRGBBitCount 属性。 变量 bytes_per_line 对应于 DDSURFACEDESC2 结构的lPitch 属性。 但是计算特定像素的内存位置是不够的,为它设置正确的颜色。 为此,我们还必须检查哪些位属于哪个颜色通道。 在 32位模式中,实现非常简单。 在这种情况下,可以直接将RGB颜色放入 Surface 内存中,因为每个像素都是宽度为。 但是在其他情况下,映射稍微复杂一点。 例如在 16位模式中,每个像素只占用两个字节的Surface 内存。 这意味着,每个颜色通道的表示( 只有 5位精确) 都少于 8位。 因此,我们必须屏蔽并将我们的RGB值转换为对应的像素格式。 我们需要完成这个任务的唯一信息是每个颜色通道的位掩码和位位置。 相应的属性是 DDPIXELFORMAT 结构的dwRBitMaskdwGBitMaskdwBBitMask

下表为 16位模式提供了这些属性的值。 如前所述,这种模式的遮罩是 5位宽,其中蓝色通道占据最顶端的位,红色通道占据最顶端的通道。

颜色通道位掩码AttributeValue的值
dwRBitMask01111100 00000000
dwGBitMask00000011 11100000
dwBBitMask00000000 00011111

为了确定位掩码中的位数,我们不断地减少位掩码,并应用一个逻辑和操作。 以下 CDirectXDialog 类的操作执行这里计算。

int CDirectXDialog::getNumberOfBits(DWORD mask)
{
 int nob = 0;
 while (mask)
 {
 mask = mask & (mask - 1);
 nob++;
 }
 return nob;
}

最后,可以通过不断应用逻辑和操作来识别位掩码的起始位,从右向左移动变位。 为了完整性,这里是确定这个起始位位置的操作。

int CDirectXDialog::getBitMaskPosition(DWORD mask)
{
 int pos = 0;
 while (!(mask & 1 <<pos)) pos++;
 return pos;
}

操作像素

DirectX Surface 访问完全由类 CDirectXDialog 包装。 要访问 Surface 内存时,必须调用该类的BackbufferLock()。 完成绘图后,你必须调用 BackbufferUnlock()。 调用这些操作对很重要,我决定让它们成为 private。 因此,你不能直接访问这些操作。 你需要使用辅助对象,该对象负责锁定和解锁 Surface。 这里worker对象类是 CDirectXDialog的友元类,它被称为 CDirectXLockGuard

BackbufferLock() 中处理的第一个问题是 DirectX Surface的锁。 然后,检查 Surface,确定每个彩色通道的位掩码和位位置,当然还有位置值。 可以通过成员函数指针访问 CDirectXDialog的实际 setPixel 操作。 在 BackbufferLock() 中,这个函数指针是根据当前设置模式初始化的。 在 DirectX Surface 中设置像素的具体函数称为 CDirectXDialog::setPixelOPTIMIZEDCDirectXDialog::setPixelSECURE。 优化的版本只将RGB值复制到相应的内存位置。 安全版本还掩盖和移动RGB颜色值到正确的像素格式,因这里比优化的一个更慢。

inlinevoid CDirectXDialog::setPixelOPTIMIZED(int x, int y, DWORD color)
{
 *(unsignedint*)(backbuffervideodata + 
 x*x_pitch + y*y_pitch) = color;
}inlinevoid CDirectXDialog::setPixelSECURE(int x, int y, DWORD color)
{
 int offset = x*x_pitch + y*y_pitch;
 DWORD Pixel = *(LPDWORD)((DWORD)backbuffervideodata + offset);
 Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwRBitMask) | 
 ((RGB_GETRED(color)>> (8 - rbits)) <<rpos);
 Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwGBitMask) | 
 ((RGB_GETGREEN(color)>> (8 - gbits)) <<gpos);
 Pixel = (Pixel & ~ sDesc.ddpfPixelFormat.dwBBitMask) | 
 ((RGB_GETBLUE(color)>> (8 - bbits)) <<bpos);
 *(unsignedint*)(backbuffervideodata + offset) = Pixel;
}

CDirectXDialog类

CDirectXDialog 是一个抽象类。 因此,你必须将它的子类化并覆盖纯虚函数 displayFrame()。 这里操作在每次重新绘制对话框时都由框架调用。 这里示例的圆形图像在 CDlgBackgroundArtDecoDlg 类的displayFrame() 操作中绘制。 其他重要的和可以写的操作是。

  • initDirectDraw()

    在初始化对话框时由框架调用。 你可以创建和初始化其他的DirectX资源,如其他。

  • restoreSurfaces()

    当发生异常且DirectX图形 Surface 丢失时,由框架调用。 你必须在这里创建并初始化你的DirectX资源。

  • freeDirectXResources()

    当对话框被破坏时,由框架调用。 你可以在这里释放你创建的资源。

绘制图像

显示的图像在 CDlgBackgroundArtDecoDlg 类的displayFrame() 操作中计算并绘制。 我想检查 C++ 和直接汇编器实现之间的速度优势。 因此,你可以选择要激活哪个。 如果预处理器变量 IS_IT_WORTH_IT 已经定义,则图形将由x86汇编程序例程完成。 否则,它由 C++ 实现完成。 另外,我使用成员函数指针来调用assember代码 fragment 中的setPixel 成员函数。 请参阅我的另一篇文章,如果你想了解更多关于这个主题( 如何从内联汇编代码段调用调用 C++ 成员操作。)的信息。

void CDlgBackgroundArtDecoDlg::displayFrame()
{
 CRect rect; GetClientRect(rect);
 int width = rect.Width()/2;
 int height = rect.Height()/2;
 DWORD starttime,stoptime;
 starttime = GetTickCount();
 {
 g_pDisplay.Clear();
 CDirectXLockGuard lock(this);#if defined(IS_IT_WORTH_IT) setPixelPTR _setPixel = setPixel;
 int x, y, c, z = zoom;
 _asm
 {
 mov edx, width
loop1: mov ebx, height
loop2: mov eax, edx; //color = (x*x+y*y) imul eax, eax;
 mov ecx, ebx;
 imul ecx, ecx;
 add eax, ecx;
 imul eax, eax; //color = color*color; mov ecx, z; //zoom sar eax, cl;
 and eax, 0xFF;
 mov cl, al;
 and cl, 0x80; //if (color> = 128) color = color - 127; jz weiter;
 xor eax, 0x7F;
weiter: shl eax, 8+1;
 //Backup mov x, edx;
 mov y, ebx;
 mov c, eax;
 push eax; //color  mov eax, ebx; //yadd eax, height;
 push eax; 
 mov eax, edx; //xadd eax, width;
 push eax; 
 mov ecx, this; //this-call of member function pointer call _setPixel;
 mov eax, c; //color  push eax;
 mov eax, y; //yadd eax, height;
 push eax; 
 mov eax, width; //x sub eax, x;
 push eax; 
 mov ecx, this; //this-call of member function pointer call _setPixel;
 mov eax, c; //color  push eax;
 mov eax, height;//y sub eax, y;
 push eax; 
 mov eax, width; //x sub eax, x;
 push eax; 
 mov ecx, this; //this-call of member function pointer call _setPixel;
 mov eax, c; //color  push eax;
 mov eax, height;//y sub eax, y;
 push eax; 
 mov eax, x; //xadd eax, width;
 push eax; 
 mov ecx, this; //this-call of member function pointer call _setPixel;
 //Reload mov edx, x;
 mov ebx, y; 
 sub ebx, 1 jge loop2
 sub edx, 1 jge loop1 
 }#else for (long x = 0; x < width; x++)
 for (long y = 0; y < height; y++)
 {
 long g = x*x + y*y;
 g = g * g; 
 g = g>> zoom; 
 g = g & 0xFf;
 if (g & 0x80) // if (g> 0x7f) g = 0x7f - g; g = 0x7f ^ g;
 g = g <<1; 
 (this->*setPixel)(width + x, height + y,RGBA_MAKE(g,0,0,0));
 (this->*setPixel)(width - x, height + y,RGBA_MAKE(0,g,0,0));
 (this->*setPixel)(width + x, height - y,RGBA_MAKE(0,0,g,0));
 (this->*setPixel)(width - x, height - y,RGBA_MAKE(g,g,g,0));
 } #endif }
 stoptime = GetTickCount();
 char buffer[128];
 sprintf(buffer, "time: %4dms", stoptime - starttime);
 g_pTextSurface->DrawText(NULL, buffer, 0, 0, RGB(0,0,0), RGB(255,255,0));
 g_pDisplay.Blt(20, 20, g_pTextSurface, NULL);
 CDialog::OnPaint();
}

还有什么要说的?

一些人可能会说,这篇文章有点混淆,因为我想解释一下比DirectX更老的技术。 我向你保证,这是我们需要了解的关于DirectX的全部。 我使用DirectX的唯一原因是,它是快速的,而我展示的像素绘图例程对应于 DOS VGA模式。 我可以使用微软的GDI实现像素绘制程序,坦率地说,我有一个基于GDI的版本。 但是用它来创建快速多边形填充器太慢了。 因此,我认为这是一个很好。 希望你能发现这些文章有帮助和有趣。



文章标签:PAR  fast  TEMP  DIR  template  DIA  对话框  Directx  

Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语