我一直在用 C/C++ 编写这个小型光线追踪引擎,到目前为止我已经有了基本的功能,即将完成的图像写入图像。但是,我希望能够创建一个窗口并对其帧缓冲区进行低级访问。有没有一种方法可以在 Windows 中执行此操作,而不使用任何外部库(例如 SDL3 或 MiniFB)?
在 Windows 上,您可以使用 WinAPI 创建窗口。
这是带有按钮的窗口的示例:
//-----------------------------------------------------------------------------
// Defines
#define WIN32_LEAN_AND_MEAN // no MFC
//-----------------------------------------------------------------------------
// Header
#include <windows.h>
#include <TCHAR.h>
//-----------------------------------------------------------------------------
// Globals
HWND g_hwndButton = 0;
//-----------------------------------------------------------------------------
// Event Handler
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_COMMAND:
{
if (HIWORD(wparam) == BN_CLICKED &&
(HWND) lparam == g_hwndButton)
{
DestroyWindow(hwnd);
}
return 0;
}
default: break;
}
return (DefWindowProc(hwnd, msg, wparam, lparam));
}
//-----------------------------------------------------------------------------
// Main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdline, int nCmdShow)
{
WNDCLASSEX winclass;
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0; // extra class info space
winclass.cbWndExtra = 0; // extra window info space
winclass.hInstance = hInstance; // assign the application instance
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winclass.lpszMenuName = NULL; // the name of the menu to attach
winclass.lpszClassName = __T("WINCLASS1"); // the name of the class itself
winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&winclass);
HWND hwnd;
hwnd = CreateWindowEx( NULL,
__T("WINCLASS1"),
__T("Window Title"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0,
0,
200,
200,
NULL, // handle to parent
NULL, // handle to menu
hInstance, // instance of this application
NULL);
if(hwnd==NULL)
return -10;
g_hwndButton = CreateWindow(__T("BUTTON"), __T("My Button"), WS_CHILD |
WS_VISIBLE | BS_PUSHBUTTON, 20, 20, 100, 20, hwnd, NULL, hInstance,
NULL);
//UpdateWindow();
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
// translate any accelerator keys
TranslateMessage(&msg);
// send the message to the window proc
DispatchMessage(&msg);
}
return 0;
}
您需要一种可以绘图的画布,而不是按钮。一种方法是使用 WinAPI + GDI。
UINT *BitmapBytes;
BITMAPINFO BitmapInfo;
BITMAPINFOHEADER BitmapInfoHeader;
HDC BitmapHDC;
HBITMAP BitmapHandle;
BitmapInfoHeader.biSize = sizeof(BitmapInfo);
BitmapInfoHeader.biWidth = width;
BitmapInfoHeader.biHeight = height;
BitmapInfoHeader.biCompression = BI_RGB;
BitmapInfoHeader.biBitCount = 32;
BitmapInfoHeader.biPlanes = 1;
BitmapInfoHeader.biSizeImage = 0;
BitmapInfoHeader.biClrImportant = 0;
BitmapInfoHeader.biClrUsed = 0;
BitmapInfoHeader.biSizeImage = 0;
BitmapInfoHeader.biXPelsPerMeter = 0;
BitmapInfoHeader.biYPelsPerMeter = 0;
BitmapInfo.bmiHeader = BitmapInfoHeader;
BitmapHDC = CreateCompatibleDC(hdc);
BitmapHandle = CreateDIBSection(hdc, &BitmapInfo,
0, (void**)&BitmapBytes, NULL, 0);
SelectObject(BitmapHDC, BitmapHandle);
您的位图类可能如下所示:
class BitmapDIBUncompressed32Bit
{
public:
BitmapDIBUncompressed32Bit(int width, int height);
BitmapDIBUncompressed32Bit(HDC hdc, int width, int height);
int getWidth() const;
int getHeight() const;
virtual ~BitmapDIBUncompressed32Bit();
void Release();
HDC getHDC();
UINT * getBytes() const;
operator UINT * () { return BitmapBytes; };
private:
UINT *BitmapBytes;
BITMAPINFO BitmapInfo;
BITMAPINFOHEADER BitmapInfoHeader;
HDC BitmapHDC;
HBITMAP BitmapHandle;
};
然后您的画布就可以使用该位图。画布类可能如下所示:
class Canvas : public Component
{
public:
Canvas();
void setPixel(int x, int y, int color);
void paint();
void clear();
virtual ~Canvas();
private:
void create(HWND hWnd, HINSTANCE hInstance);
BitmapDIBUncompressed32Bit Bitmap;
static int NumOfRegisteredWindows;
};
paint方法的实现:
void Canvas::paint()
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_hWnd, &ps);
HDC hdcWindow = GetWindowDC(m_hWnd);
BitBlt(hdcWindow, 0, 0, m_Size.getWidth(), m_Size.getHeight(), Bitmap.getHDC(), 0, 0, SRCCOPY);
ReleaseDC(m_hWnd, hdcWindow);
EndPaint(m_hWnd, &ps);
}
画布是如何创建的:
void Canvas::create(HWND hWnd, HINSTANCE hInstance)
{
WNDCLASS wndcls;
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wndcls.lpfnWndProc = (WNDPROC)WindowsManager::windowEventsProcessor;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInstance;
wndcls.hIcon = NULL;
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1);
wndcls.lpszMenuName = NULL;
NumOfRegisteredWindows++;
string UniqueClassName = "UniqueClassNameID_Canvas_" + String::IntToString(NumOfRegisteredWindows);
wndcls.lpszClassName = UniqueClassName.c_str();
if(RegisterClass(&wndcls) == 0)
cout<<"shit happens"<<endl;
m_hWnd = CreateWindowEx(NULL,UniqueClassName.c_str(), "YourCanvas", WS_CHILD |
WS_VISIBLE, 20, 20, 100, 20, hWnd, NULL, hInstance,
NULL);
}
设置像素:
void Canvas::setPixel(int x, int y)
{
Bitmap[x+y*Bitmap.getWidth()] = 0xFFFF0000;
}
也看看PixelToaster。
如果您想获得 WinAPI + GDI 方法,您还可以在《Programming Windows: The Definitive Guide To The Win32 Api》一书中找到更多信息
作为替代方案,您可以创建一个 OpenGL/Direct3D/Vulkan 渲染器设备和一个纹理对象,然后写入纹理内存并上传。
您还可以使用tev。它提供了一个网络协议来写入其帧缓冲区。这样您就可以免费获得色调映射。
Qt 也可以是您的一个选择 - 您可以使用 QImage 并将其绘制在小部件上 - 例如:
class RenderWidget : public QWidget
{
Q_OBJECT
public:
RenderWidget(const CommandLineArguments& cla, QWidget* parent = nullptr) : QWidget(parent), render_scene_thread_(cla), render_preview_thread_(&render_scene_thread_) {
draw_next_frame();
setFixedSize(100,100);
}
virtual ~RenderWidget() {
if(render_preview_thread_.isRunning()) {
render_preview_thread_.quit();
}
if(render_scene_thread_.isRunning()) {
render_scene_thread_.quit();
}
}
void paintEvent(QPaintEvent * e) override {
if(render_preview_thread_.qimage_) {
QPainter p(this);
p.drawImage(rect(), *render_preview_thread_.qimage_);
}
else {
LOG(INFO) << "Nothing to draw";
}
}
public Q_SLOTS:
void draw_next_frame() {
if(render_scene_thread_.render_state_ == RenderState::Rendering ||
render_scene_thread_.render_state_ == RenderState::Done) {
static Vector2i old_size = Vector2i(-1,-1);
Vector2i size = render_scene_thread_.scene->sensor()->film()->size();
if(old_size != size) {
this->setFixedSize(size.x(), size.y());
old_size = size;
}
}
repaint();
QTimer::singleShot(1000, this, SLOT(draw_next_frame()));
}
public:
RenderSceneThread render_scene_thread_;
RenderPreviewThread render_preview_thread_;
};