Tutorial 2 do jogo C++ OpenGL 3D: Fazendo notas de estudo do OpenGL 3D Engine

Endereço do vídeo icon-default.png?t=N7T8https://www.youtube.com/watch?v=PH5kH8h82L8&list=PLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index=3

1. Aula principal

        Continuando com o artigo anterior, algum código foi adicionado ao main.cpp para torná-lo mais rigoroso:

#include<OGL3D/Game/OGame.h>
#include<iostream>

int main()
{
	try {
		OGame game;
		game.Run();
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
		return 1;
	}

	return 0;
}

2. Categoria OGame 

       Então a classe Game adiciona muito conteúdo:

        Arquivo jogo.h:

#pragma once
#include<memory>

class OGraphicsEngine;
class OWindow;
class OGame
{
public:
	OGame();
	~OGame();

	virtual void onCreate();
	virtual void onUpdate();
	virtual void onQuit();

	void Run();
protected:
	bool m_isRunning = true;
	std::unique_ptr<OGraphicsEngine> m_graphicsEngine;
	std::unique_ptr<OWindow> m_display;
};

        Arquivo jogo.cpp:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<OGL3D/Window/OWindow.h>
#include<OGL3D/Game/OGame.h>
#include<Windows.h>

OGame::OGame()
{
	m_graphicsEngine = std::make_unique<OGraphicsEngine>();
	m_display = std::make_unique<OWindow>();

	m_display->makeCurrentContext();
}

OGame::~OGame()
{
}

void OGame::onCreate()
{
	m_graphicsEngine->clear(OVec4(1,0,0,1));

	m_display->present(false);
}

void OGame::onUpdate()
{
}

void OGame::onQuit()
{
}

void OGame::Run()
{
	onCreate();

	MSG msg;
	while (m_isRunning)
	{
		msg = {};
		if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				m_isRunning = false;
				continue;
			}
			else 
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		onUpdate();
	}

	onQuit();
}

       Comparado com o artigo anterior, o objeto OGraphicEngine é criado no construtor e o método makeCurrentContext do objeto OWindow m_display é executado ao mesmo tempo.Este método também é adicionado à classe OWindow neste vídeo. Deve-se notar que a ordem destas duas linhas de código não pode estar errada:

m_graphicsEngine = std::make_unique<OGraphicsEngine>();
m_display = std::make_unique<OWindow>();

         Como o conteúdo no HDC precisa ser obtido durante a construção do OWindow, e esse conteúdo HDC deve ser definido durante a construção do OGraphicsEngine, o objeto OGraphicsEngine deve ser construído primeiro, e reverter a ordem definitivamente não funcionará.

        Ao mesmo tempo, os métodos onCreate, onUpdate e onQuit também são adicionados à classe OGame, basta prestar atenção ao tempo de chamada desses três métodos.

3. Classe OWindow

Arquivo Owindow.h

#pragma once

#include<Windows.h>
class OWindow
{
public:
		OWindow();
		~OWindow();

		void makeCurrentContext();
		void present(bool vsync);
private:
	HWND m_handle = nullptr;
	HGLRC m_context = nullptr;
};

Arquivo OWindow.cpp

#include<OGL3D/Window/OWindow.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include <Windows.h>
#include<assert.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
	{
		OWindow* window = (OWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
		break;
	}
	case WM_CLOSE:
	{
		PostQuitMessage(0);
		break;
	}
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return NULL;
}

OWindow::OWindow()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DWindow";
	wc.lpfnWndProc = WndProc;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	RECT rc = { 0,0,1024,768 };
	AdjustWindowRect(&rc, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, false);

	m_handle = CreateWindowEx(NULL,
		MAKEINTATOM(classId),
		L"Parcode | OpenGL 3D Game",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		rc.right - rc.left, rc.bottom - rc.top,
		NULL, NULL, NULL, NULL);

	assert(m_handle);

	SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)this);

	ShowWindow(m_handle, SW_SHOW);
	UpdateWindow(m_handle);

	HDC hDC = GetDC(m_handle);

	int pixelFormatAttributes[] = {
		WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
		WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
		WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
		WGL_PIXEL_TYPE_ARB,WGL_TYPE_RGBA_ARB,
		WGL_COLOR_BITS_ARB,24,
		WGL_DEPTH_BITS_ARB,24,
		WGL_STENCIL_BITS_ARB,8,
		0
	};

	int iPixelFormat = 0;
	UINT numFormats = 0;
	wglChoosePixelFormatARB(hDC, pixelFormatAttributes, nullptr, 1, &iPixelFormat, &numFormats);
	assert(numFormats);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	DescribePixelFormat(hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pixelFormatDesc);
	SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

	int openAttributes[] = {
	WGL_CONTEXT_MAJOR_VERSION_ARB,4,
	WGL_CONTEXT_MINOR_VERSION_ARB,6,
	WGL_CONTEXT_PROFILE_MASK_ARB,WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
	0
	};

	m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);
	assert(m_context);
}

OWindow::~OWindow()
{
	wglDeleteContext(m_context);
	DestroyWindow(m_handle);
}

void OWindow::makeCurrentContext()
{
	wglMakeCurrent(GetDC(m_handle), m_context);
}

void OWindow::present(bool vsync)
{
	wglSwapIntervalEXT(vsync);
	wglSwapLayerBuffers(GetDC(m_handle), WGL_SWAP_MAIN_PLANE);
}

Criar janela

        Algumas questões relativas à criação de janelas precisam ser explicadas aqui. Na verdade, deveriam ter sido explicadas no documento anterior, mas o entendimento não estava em vigor na época, então aqui está um suplemento. 
        WNDCLASSEX cria uma instância de estrutura chamada wc. Esta instância é equivalente a escrevermos um formulário de inscrição para o tipo de janela a ser criado antecipadamente. A função RegisterClass envia (registra) o formulário de inscrição wc para o sistema Window e, em seguida, cria uma janela quando necessário., o sistema Window irá criá-lo de acordo com este formulário de inscrição.
        O parâmetro formal de RegisterClass é um ponteiro para a estrutura WNDCLASSEX. Este ponteiro será adicionado à tabela atom do sistema, ou seja, SAT, para que o sistema possa encontrar a classe de janela definida pelo usuário pesquisando nesta tabela. A janela janela predefinida o ponteiro da classe também está no SAT.

        Na verdade, o SAT implementa um mapeamento para consulta. O tipo real de ATOM (traduzido como "átomo") é curto, que é um número inteiro 16. A tabela ATOM (tabela atômica) é uma tabela definida pelo sistema usada para armazenar strings e identificadores correspondentes. O programa coloca uma string na tabela ATOM e obtém um número inteiro correspondente de 16 bits.Esse número inteiro é chamado de átomo e pode ser usado para acessar a string. Uma string colocada na tabela atom é chamada de nome do átomo.

        Somente o sistema pode acessar diretamente esta tabela, mas ao chamar certas funções da API, como Registerclass, você pode solicitar ao sistema que acesse esta tabela. Claro, também existem tabelas atômicas locais e tabelas atômicas globais, que os aplicativos podem acessar diretamente.

        A macro MAKEINTATOM (localizada em winbase.h) converte o átomo especificado no endereço de string correspondente para que possa ser passado para funções que aceitam átomos ou strings. Esta macro é usada ao chamar CreateWindowEx.

Obtenha o contexto do desenho 

        O código adicionado posteriormente é de

HDC hDC = GetDC(m_handle); 

Esta linha de código começa com

assert(m_context); 

Esta linha de código termina. Seu objetivo principal é obter o contexto do desenho, que é m_context. O código usado para obter o código de contexto é:

m_context = wglCreateContextAttribsARB(hDC, 0, openAttributes);

        De modo geral, é um processo confuso para mim, mas primeiro preciso obter os parâmetros formais. O primeiro é o hDC, que é o único que precisa ser explicado. Há muito conteúdo e fiquei muito confuso depois de procurar por muito tempo. Preciso entender primeiro. O que importa.

        HDC - Handle of the Device Context, chinês significa "handle de contexto do dispositivo", essa coisa será usada muitas vezes depois. HDC é uma estrutura de dados do Windows que contém informações sobre as propriedades de desenho de um dispositivo, como um monitor ou impressora . Todas as chamadas draw são feitas por meio de objetos de contexto de dispositivo, que encapsulam a API do Windows para desenhar linhas, formas e texto.

        Embora a função GetDC obtenha facilmente esse hDC, ela define seu formato de pixel. Esse é o código abaixo:

SetPixelFormat(hDC, iPixelFormat, &pixelFormatDesc);

        Existem várias funções projetadas neste processo, mas basicamente não as entendo, então acho que posso simplesmente anotá-las.

        Vamos falar sobre uma função wglChoosePixelFormatARB, que pode encontrar o formato de pixel apropriado .

        Para definir a multiamostragem , você deve usar a função wglChoosePixelFormatARB. A chave para usar esta função corretamente é criar um formulário temporário. Através deste formulário, podemos obter o formato de pixel básico necessário e, em seguida, usar a função wglChoosePixelFormatARB para obter o formato de pixel de amostragem múltipla disponível. Finalmente, defina a janela de renderização. o formato de pixel é suficiente.

4. Classe OGraphicsEngine

        A classe OGraphicsEngine foi adicionada e o código será postado primeiro:

        Arquivo OGraphicsEngine.h:

#pragma once
#include<OGL3D/Math/OVec4.h>

class OGraphicsEngine
{
public:
	OGraphicsEngine();
	~OGraphicsEngine();
public:
	void clear(const OVec4& color);
};

        Arquivo OGraphicsEngine.cpp:

#include<OGL3D/Graphics/OGraphicsEngine.h>
#include<glad/glad.h>
#include<glad/glad_wgl.h>
#include<assert.h>
#include<stdexcept>

OGraphicsEngine::OGraphicsEngine()
{
	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpszClassName = L"OGL3DDummyWindow";
	wc.lpfnWndProc = DefWindowProc;
	wc.style = CS_OWNDC;

	auto classId = RegisterClassEx(&wc);
	assert(classId);

	auto dummyWindow = CreateWindowEx(NULL, MAKEINTATOM(classId), L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

	assert(dummyWindow);

	HDC dummyDC = GetDC(dummyWindow);

	PIXELFORMATDESCRIPTOR pixelFormatDesc = {};
	pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pixelFormatDesc.nVersion = 1;
	pixelFormatDesc.iPixelType = PFD_TYPE_RGBA;
	pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pixelFormatDesc.cColorBits = 32;
	pixelFormatDesc.cAlphaBits = 8;
	pixelFormatDesc.cDepthBits = 24;
	pixelFormatDesc.cStencilBits = 8;
	pixelFormatDesc.iLayerType = PFD_MAIN_PLANE;

	auto iPixelFormat = ChoosePixelFormat(dummyDC, &pixelFormatDesc);
	SetPixelFormat(dummyDC, iPixelFormat, &pixelFormatDesc);

	auto dummyContext = wglCreateContext(dummyDC);
	assert(dummyContext);

	wglMakeCurrent(dummyDC, dummyContext);

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

	wglMakeCurrent(dummyDC, 0);
	wglDeleteContext(dummyContext);
	ReleaseDC(dummyWindow, dummyDC);
	DestroyWindow(dummyWindow);
}

OGraphicsEngine::~OGraphicsEngine()
{
}

void OGraphicsEngine::clear(const OVec4& color)
{
	glClearColor(color.x,color.y,color.z,color.w);
	glClear(GL_COLOR_BUFFER_BIT);
}

        choosePixelFormat seleciona o conteúdo mais correspondente do HDC e retorna um índice (iPixelFormat, o i no início deve ser o índice).

        A função SetPixelformat define o formato de pixel do contexto de dispositivo especificado (HDC) para o formato especificado pelo índice (iPixelFormat).

        Esta operação significa que o PIXELFORMATDESCRIPTOR que você definiu no início é apenas sua própria ilusão. O dispositivo (monitor ou impressora) pode suportá-lo (o que é obviamente o melhor) ou pode não suportá-lo. Uma vez que não suporta. , encontraremos o mais próximo de você. Os parâmetros exigidos são aceitáveis.

        A função wglCreateContext (por que começa com wgl? Deve ser uma função GL derivada do arquivo Wingdi.h e wingdi deve ser a interface gráfica do dispositivo de janela) cria um contexto de renderização adequado para   desenhar no dispositivo referenciado por  hdc .

        Seu valor de retorno é do tipo HGLRC (o identificador do contexto de renderização GL, identificador do contexto de renderização GL)

        wglMakeCurrent indica que o trabalho de desenho subsequente em hdc é baseado em dummyContext.

        Eu andei em um grande círculo apenas para estas duas frases:

	if (!gladLoadWGL(dummyDC))throw std::runtime_error("OGraphic Engine Error:gladLoadWGL failed.");
	if (!gladLoadGL())throw std::runtime_error("OGraphic Engine Error:gladLoadGL failed.");

        Deixe-me começar dizendo que lançar uma exceção por meio de throw encerrará o thread.

        Estas duas frases também definem o status do hDC.

5. Suplemento

        Adicione dois arquivos, um é o arquivo OVec4.h e o outro é o arquivo OPerequisites.h.

        Arquivo OVec4.h:

#pragma once
#include<OGL3D/OPerequisites.h>

class OVec4
{
public:
	OVec4() {}
	OVec4(f32 x, f32 y, f32 z, f32 w) :x(x), y(y), z(z), w(w) {}
	~OVec4() {}

public:
	f32 x = 0, y = 0, z = 0, w = 0;
};

        Arquivo OPerequisites.h:

#pragma once

typedef float f32;

6. Pós-escrito

        Para ser honesto, a programação do Windows é realmente complicada e obscura (na verdade, eu queria dizer que era **, mas quero manter um resultado final elegante).Talvez isso seja resultado de um monopólio. Na verdade, quando eu estava desenvolvendo aplicativos para Apple no passado, senti que a Apple era muito hostil com os desenvolvedores (mas muito amigável com jogadores e usuários!) Não pude deixar de lembrar que programar em C# no ambiente Unity parecia realmente maravilhoso (Viva a Unidade!). Mas dito isso, o salário para desenvolver motores de jogos é geralmente maior do que usar o motor para fazer jogos. Isso pode ser considerado uma compensação por ser torturado por esse tipo de código :D       

Acho que você gosta

Origin blog.csdn.net/ttod/article/details/135437815
Recomendado
Clasificación