
It works well and consumes very little CPU.
Code: Select all
#include <irrlicht.h>
#include <windows.h>
#pragma comment(lib, "Irrlicht.lib")
// Use common Irrlicht namespaces
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;
// --- Global variables for Win32 and double buffering ---
static HWND g_hWnd; // Main application window handle
static HWND g_hIrrlichtWnd; // Child window for Irrlicht rendering
static HBRUSH g_bgBrush = NULL; // Background brush for clearing
static HDC g_hMemDC = NULL; // Memory Device Context (off-screen drawing)
static HBITMAP g_hMemBitmap = NULL; // Off-screen bitmap
static HBITMAP g_hOldBitmap = NULL; // Original bitmap to restore after drawing
// --- Irrlicht-related global pointers ---
static IrrlichtDevice* device = nullptr;
static IVideoDriver* driver = nullptr;
static ISceneManager* smgr = nullptr;
static ITexture* renderTexture = nullptr; // Off-screen render target
// --- Windows bitmap info for drawing Irrlicht output ---
static BITMAPINFO bmi = {};
static HBITMAP hBitmap = nullptr;
static void* bmpBits = nullptr; // Pointer to pixel data
// --- Window size & fullscreen/maximized tracking ---
static RECT windowRectInitial = {};
static bool isFullscreen = false;
static bool isMaximized = false;
static RECT windowRectBeforeFullscreen = {};
// --- Scene objects ---
static IAnimatedMeshSceneNode* node2 = nullptr;
static ISceneNode* node3 = nullptr;
static ISceneNode* node4 = nullptr;
static ICameraSceneNode* camera = nullptr;
// --- Camera control variables ---
f32 yaw = 0.0f; // Horizontal rotation (left/right)
f32 pitch = 0.0f; // Vertical rotation (up/down)
f32 distance = 100.0f; // Distance from camera to its target
position2di lastMousePos;
// Update the camera's target position based on yaw and pitch
void updateCameraTarget() {
f32 radYaw = yaw * core::DEGTORAD;
f32 radPitch = pitch * core::DEGTORAD;
// Convert spherical coordinates to Cartesian
f32 x = distance * cosf(radPitch) * cosf(radYaw);
f32 y = distance * sinf(radPitch);
f32 z = distance * cosf(radPitch) * sinf(radYaw);
vector3df cameraPos = camera->getPosition();
vector3df newTarget = cameraPos + vector3df(x, y, z);
camera->setTarget(newTarget);
}
// Forward declaration of main loop function
void loop_func();
// Frame count and FPS update tracking
static unsigned int frameCount = 0;
static DWORD lastFpsUpdate = 0;
// --- Create bitmap surface for drawing Irrlicht output ---
void CreateBitmapSurface() {
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = 256;
bmi.bmiHeader.biHeight = -256; // Negative for top-down bitmap
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;
HDC hdc = GetDC(NULL);
hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bmpBits, NULL, 0);
ReleaseDC(NULL, hdc);
}
// Copy pixels from Irrlicht's texture to Windows bitmap
void UpdateBitmapFromTexture() {
if (!driver || !renderTexture || !bmpBits) return;
IImage* image = driver->createImage(renderTexture, position2d<s32>(0, 0),
dimension2d<u32>(256, 256));
if (!image) return;
u8* dst = (u8*)bmpBits;
for (u32 y = 0; y < 256; ++y) {
for (u32 x = 0; x < 256; ++x) {
SColor c = image->getPixel(x, y);
int offset = (y * 256 + x) * 3;
dst[offset] = c.getBlue();
dst[offset + 1] = c.getGreen();
dst[offset + 2] = c.getRed();
}
}
image->drop();
}
// --- Main Windows procedure (handles events and double buffering) ---
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
g_hMemDC = CreateCompatibleDC(NULL);
return 0;
case WM_SIZE: {
if (g_hMemBitmap) DeleteObject(g_hMemBitmap);
HDC hdc = GetDC(hWnd);
int width = LOWORD(lParam);
int height = HIWORD(lParam);
g_hMemBitmap = CreateCompatibleBitmap(hdc, width, height);
ReleaseDC(hWnd, hdc);
g_hOldBitmap = (HBITMAP)SelectObject(g_hMemDC, g_hMemBitmap);
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
return 0;
}
case WM_ERASEBKGND:
return 1; // Prevent flickering by skipping background erase
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
FillRect(g_hMemDC, &rc, g_bgBrush);
if (hBitmap && bmpBits) {
int winWidth = rc.right - rc.left;
int winHeight = rc.bottom - rc.top;
int size = min(winWidth, winHeight);
int x = (winWidth - size) / 2;
int y = (winHeight - size) / 2;
SetStretchBltMode(g_hMemDC, COLORONCOLOR);
StretchDIBits(g_hMemDC, x, y, size, size, 0, 0, 256, 256, bmpBits, &bmi,
DIB_RGB_COLORS, SRCCOPY);
}
BitBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, g_hMemDC, 0, 0,
SRCCOPY);
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
SelectObject(g_hMemDC, g_hOldBitmap);
DeleteObject(g_hMemBitmap);
DeleteDC(g_hMemDC);
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
// Toggle between fullscreen and windowed mode
void ToggleFullscreen(HWND hWnd) {
if (!isFullscreen) {
GetWindowRect(hWnd, &windowRectBeforeFullscreen);
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = {sizeof(mi)};
GetMonitorInfo(hMonitor, &mi);
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hWnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
isFullscreen = true;
} else {
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPos(hWnd, HWND_NOTOPMOST, windowRectBeforeFullscreen.left,
windowRectBeforeFullscreen.top,
windowRectBeforeFullscreen.right - windowRectBeforeFullscreen.left,
windowRectBeforeFullscreen.bottom - windowRectBeforeFullscreen.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
isFullscreen = false;
}
// Force repaint
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
}
// Toggle between maximized and normal window size
void ToggleMaximizedWindow(HWND hWnd) {
if (!isMaximized) {
ShowWindow(hWnd, SW_MAXIMIZE);
isMaximized = true;
} else {
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPos(hWnd, HWND_NOTOPMOST, windowRectInitial.left,
windowRectInitial.top,
windowRectInitial.right - windowRectInitial.left,
windowRectInitial.bottom - windowRectInitial.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
isMaximized = false;
}
// Force repaint
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
}
// -------------------
// WinMain - Entry point of the application
// -------------------
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
// Create a solid black background brush
g_bgBrush = CreateSolidBrush(RGB(0, 0, 0));
// Register the window class
WNDCLASSEX wc = {
sizeof(WNDCLASSEX), // Size of the structure
CS_HREDRAW | CS_VREDRAW, // Redraw on horizontal/vertical resize
WndProc, // Window procedure function
0, 0, // No extra memory
hInst, // Application instance
NULL, // No icon
LoadCursor(NULL, IDC_ARROW), // Default arrow cursor
NULL, NULL, // No background brush or menu
"TestWindow", // Class name
NULL // No small icon
};
RegisterClassEx(&wc);
// Create the main window
g_hWnd = CreateWindowEx(0, "TestWindow", "CPU Render - Irrlicht Engine",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
512, 512, NULL, NULL, hInst, NULL);
GetWindowRect(g_hWnd, &windowRectInitial);
// Create a child window that Irrlicht will render into
g_hIrrlichtWnd = CreateWindowEx(0, "STATIC", NULL, WS_CHILD, 0, 0, 256, 256,
g_hWnd, NULL, hInst, NULL);
// --- Setup Irrlicht Device Parameters ---
SIrrlichtCreationParameters params;
params.DriverType = EDT_SOFTWARE; // Use software renderer
params.WindowId = g_hIrrlichtWnd; // Render into the child window
params.WindowSize = dimension2d<u32>(256, 256);
params.Bits = 8; // Color depth
// Create the Irrlicht device
device = createDeviceEx(params);
if (!device) return 1; // Failed to initialize
driver = device->getVideoDriver();
smgr = device->getSceneManager();
ShowWindow(g_hIrrlichtWnd, SW_HIDE); // Hide the child window (we draw manually)
// Create a render target texture to draw into
renderTexture = driver->addRenderTargetTexture(dimension2d<u32>(256, 256),
"RTT1", ECF_R8G8B8);
//driver->setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); // Disable mipmaps
// ----------------------------
// Load 3D assets into the scene
// ----------------------------
// Mesh 1 - Animated character
IAnimatedMesh* mesh2 = smgr->getMesh("../../media/Monster3_forirrlicht.md2");
node2 = smgr->addAnimatedMeshSceneNode(mesh2);
if (node2) {
node2->setMaterialType(EMT_SOLID);
node2->setMaterialFlag(EMF_LIGHTING, false);
node2->setMaterialTexture(0, driver->getTexture("../../media/Monster3_Albedo.jpg"));
node2->setMD2Animation("run");
node2->setAnimationSpeed(64);
node2->setScale(vector3df(4, 4, 4));
node2->setPosition(vector3df(0, 0, -15));
//node2->setMaterialFlag(EMF_USE_MIP_MAPS, false);
node2->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
}
// Mesh 2 - Scene environment
IAnimatedMesh* mesh3 = smgr->getMesh("../../media/Zone1test.md2");
node3 = smgr->addMeshSceneNode(mesh3);
if (node3) {
node3->setMaterialType(EMT_SOLID);
node3->setMaterialFlag(EMF_LIGHTING, false);
node3->setMaterialTexture(0, driver->getTexture("../../media/zone1Albedo.jpg"));
node3->setScale(vector3df(5, 5, 5));
node3->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
}
// Mesh 3 - A shadow/flat model
IAnimatedMesh* mesh4 = smgr->getMesh("../../media/shadow.obj");
node4 = smgr->addMeshSceneNode(mesh4);
if (node4) {
node4->setMaterialType(EMT_SOLID);
node4->setMaterialFlag(EMF_LIGHTING, false);
node4->setMaterialTexture(0, driver->getTexture("../../media/shadow.jpg"));
node4->setScale(vector3df(2, 2, 2));
node4->setPosition(vector3df(1, 0, -15));
node4->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
}
// Add camera to the scene
camera = smgr->addCameraSceneNode();
camera->setTarget(vector3df(0, 0, 0));
camera->setAspectRatio(1.0);
camera->setFOV(1.0f);
camera->setPosition(vector3df(0, 7, 0));
// Hide cursor (FPS style control)
gui::ICursorControl* cursorControl = device->getCursorControl();
cursorControl->setVisible(false);
// Create Windows bitmap surface to copy Irrlicht's render
CreateBitmapSurface();
// Show main window
ShowWindow(g_hWnd, nShow);
UpdateWindow(g_hWnd);
bool state;
// -------------------
// Main Application Loop
// -------------------
MainLoop:
state = device->run() ? 1 : 0;
switch (state) {
case 1: {
// Center of Irrlicht render target
dimension2d<u32> screenSize = device->getVideoDriver()->getScreenSize();
position2di center(screenSize.Width / 2, screenSize.Height / 2);
// Mouse delta movement
position2di currentPos = device->getCursorControl()->getPosition();
position2di delta = currentPos - center;
if (delta.X != 0 || delta.Y != 0) {
f32 sensitivity = 0.2f;
yaw -= delta.X * sensitivity;
pitch -= delta.Y * sensitivity;
if (pitch > 89.0f) pitch = 89.0f;
if (pitch < -89.0f) pitch = -89.0f;
updateCameraTarget();
device->getCursorControl()->setPosition(center);
}
// Render scene to texture
driver->beginScene(false, false);
driver->setRenderTarget(renderTexture, true, true, SColor(255, 30, 30, 30));
smgr->drawAll();
driver->setRenderTarget(0);
driver->endScene();
// Camera up/down movement
if (GetAsyncKeyState(VK_SPACE) & 0x8000)
camera->setPosition(camera->getPosition() + vector3df(0, 0.4f, 0));
if (GetAsyncKeyState(VK_LCONTROL) & 0x8000)
camera->setPosition(camera->getPosition() - vector3df(0, 0.4f, 0));
// WASD movement
f32 radYaw = yaw * core::DEGTORAD;
vector3df forward(cosf(radYaw), 0.0f, sinf(radYaw));
forward.normalize();
vector3df right(-forward.Z, 0.0f, forward.X);
right.normalize();
vector3df camPos = camera->getPosition();
f32 speed = 0.4f;
if (GetAsyncKeyState('W') & 0x8000) camPos += forward * speed;
if (GetAsyncKeyState('S') & 0x8000) camPos -= forward * speed;
if (GetAsyncKeyState('A') & 0x8000) camPos += right * speed;
if (GetAsyncKeyState('D') & 0x8000) camPos -= right * speed;
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) break;
camera->setPosition(camPos);
updateCameraTarget();
// Toggle fullscreen / maximized modes
bool ctrlPressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000);
bool altPressed = (GetAsyncKeyState(VK_MENU) & 0x8000);
bool enterPressed = (GetAsyncKeyState(VK_RETURN) & 0x8000);
if (ctrlPressed && altPressed && enterPressed) {
ToggleMaximizedWindow(g_hWnd);
Sleep(64); // Avoid multiple triggers
} else if (altPressed && enterPressed) {
ToggleFullscreen(g_hWnd);
Sleep(64);
}
// Copy render texture to Windows bitmap
UpdateBitmapFromTexture();
InvalidateRect(g_hWnd, NULL, FALSE);
// FPS counter in window title
++frameCount;
DWORD now = GetTickCount();
if (now - lastFpsUpdate >= 1000) {
char title[64];
wsprintfA(title, "CPU Render - Irrlicht Engine | FPS: %u", frameCount);
SetWindowTextA(g_hWnd, title);
frameCount = 0;
lastFpsUpdate = now;
}
// Continue looping
goto MainLoop;
}
case 0:
break; // Exit
}
// Cleanup
DeleteObject(hBitmap);
device->drop();
}
Inside the code, it shows how to move the ICameraSceneNode.
You have to consider that it was compiled with Irrlicht 1.9.0.
For Irrlicht to work well with culling(with software renderer), you have to add this line(not in your main program, in the dll compilation):
Code: Select all
#define __SOFTWARE_CLIPPING_PROBLEM__irrlicht-1.9.0_withss\source\Irrlicht\CSoftwareDriver.cpp
Download:
https://drive.google.com/file/d/1CbAaaR ... sp=sharing
