游戏引擎开发日志(第二天)

Posted 雪靡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏引擎开发日志(第二天)相关的知识,希望对你有一定的参考价值。

上一天的地址:https://blog.csdn.net/z736248591/article/details/117201596

————————————哥是可爱的分割线————————————————

第二天 2021年5月24日

回顾:上一天创建了项目,决定了目标和工具。今天继续。

这里使用GLFW作为渲染库。

GLFW介绍:

GLFW is an Open Source, multi-platform library for OpenGL, OpenGL ES and Vulkan development on the desktop. It provides a simple API for creating windows, contexts and surfaces, receiving input and events.

GLFW is written in C and supports Windows, macOS, X11 and Wayland.

GLFW is licensed under the zlib/libpng license.

下载glfw丢到external,这里使用OpenGL。

CmakeLists.txt:

# CMakeList.txt: TheSeedGameEngine 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)

project ("TheSeedGameEngine"
        VERSION 0.0.1
        DESCRIPTION "A 2D multi-platform game engine"
        HOMEPAGE_URL "https://github.com/nayaku/TheSeedGameEngine"
        LANGUAGES C CXX)

# 添加glwf库
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory(external/glfw-3.3.4)
# 找到OpenGL
find_package(OpenGL REQUIRED)


# 将源代码添加到此项目的可执行文件。
add_executable (TheSeedGameEngine
        src/main.c)
target_link_libraries(TheSeedGameEngine 
        glfw
        OpenGL::GL)
# TODO: 如有需要,请添加测试并安装目标。

我们在src\\main.c填入代码:

#include<GLFW/glfw3.h>

int main()
{
	GLFWwindow* window;
	// 初始化GLFW库
	if (!glfwInit())
		return -1;
	// 创建窗口和OpenGL的内容
	window = glfwCreateWindow(640, 480, "The Seed Game Engine", NULL, NULL);
	if (!window)
	{
		glfwTerminate();
		return -1;
	}

	// 创建内容
	glfwMakeContextCurrent(window);

	// 主循环
	while (!glfwWindowShouldClose(window))
	{
		// 渲染
		glClear(GL_COLOR_BUFFER_BIT);

		// 交换缓冲
		glfwSwapBuffers(window);

		// 处理事件消息
		glfwPollEvents();
	}

	glfwTerminate();
	
	return 0;
}

编译运行:

image-20210524134309586

很不错!成功了。

额外补充:win10以后在命令行输入tree可以查看文件树,不需要安装额外的软件。


开始编写渲染部分

新建一个Vulkan实例。

static VkInstance vulkanInstance;
static void VulkanInitInstance()
{
    // 应用信息
    VkApplicationInfo appInfo = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = "Demo",
        .applicationVersion = VK_MAKE_VERSION(1,0,0),
        .pEngineName = "The Seed Game Engine",
        .engineVersion = VK_MAKE_VERSION(1,0,0),
        .apiVersion = VK_API_VERSION_1_0,
    };

    // 使用glfw扩展
    unsigned int glfwExtensionCount = 0;
    const char** glfwExtensions;

    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    // 实例信息
    VkInstanceCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pApplicationInfo = &appInfo,
        .enabledExtensionCount = glfwExtensionCount,
        .ppEnabledExtensionNames = glfwExtensions,
        .enabledLayerCount = 0
    };

    // 创建实例
    VkResult  result = vkCreateInstance(&createInfo,NULL,&vulkanInstance);
    if(result == VK_SUCCESS)
    {
        printf("Vulkan实例创建成功!\\n");
    }
    else
    {
        printf("Vulkan实例创建失败!\\n");
        abort();
    }
}

销毁实例:

static void VulkanDestroyInstance(){
    vkDestroyInstance(vulkanInstance,NULL);
    printf("Vulkan实例销毁完毕");
    glfwDestroyWindow(window);
    glfwTerminate();
}

初始化窗口

void InitWindow(int width,int height,const char* title)
{
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE,GLFW_FALSE);
    window = glfwCreateWindow(width,height,title,NULL,NULL);
}

这里封装一下glfwWindowShouldClose函数

int WindowShouldClose()
{
    int flag = glfwWindowShouldClose(window);
    if(!flag)
    {
        glfwPollEvents();
    }
    return  flag;
}

修改main函数

int main()
{
    InitWindow(800,600,"Demo");
    while (!WindowShouldClose())
    {

    }
    CloseWindow();
	return 0;
}

运行:

image-20210524163906008

成功运行。


继续。。。

添加获取物理设备

static void PickPhysicalDevice()
{
    uint32_t deviceCount;
    VkResult result = vkEnumeratePhysicalDevices(instance, &deviceCount, NULL);
    assert(result == VK_SUCCESS);
    if (deviceCount == 0)
    {
        printf("Failed to find GPUs with Vulkan support!");
        abort();
    }
    VkPhysicalDevice* devices = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices);

    // 选择可用的物理设备
    VkPhysicalDevice physicalDevice=devices[0];//没啥特殊要求,直接选择第一个设备即可
	free(devices);
    // 输出选择的物理设备信息
    VkPhysicalDeviceProperties deviceProperties;
    vkGetPhysicalDeviceProperties(device, &deviceProperties);
    printf("Current Physical Info:ID:  %I32u\\nName:  %s\\nVulkan Version:  %I32u\\n",
        deviceProperties.deviceID,
        deviceProperties.deviceName,
        deviceProperties.apiVersion);
}

接下去要判断可用的队列族。这里一直没搞懂什么是队列族,今天来查查看。

不同的queue有着不同的职能,有的负责普通的3D图形渲染的例如Graphic Queue,有的负责像素块Blit的例如Transfer Queue,有的是负责计算的例如Compute Queue,还有负责稀疏绑定的例如Sparse Binding。当然有的queue能同时负责多个职能的,一般第一个是全能的1。其中队列族支持的功能,用queueFlags表示2

我的集成显卡是Intel HDU Graphics630,就只有一个队列。可以图形、计算、传输、稀疏矩阵。

image-20210524213109580

(注:该软件为GPU-Z 官网 汉化版下载)

独立显卡是GTX 1050,一共有3个队列家族。

image-20210524212827454

补充PickPhysicalDevice函数完整

    // 获取设备队列族的数量
    uint32_t queueFamilyCount;
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL);
    VkQueueFamilyProperties
        * queueFamilyProperties = (VkQueueFamilyProperties*)malloc(sizeof(VkQueueFamilyProperties) * queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL);

    // 遍历队列族
    for (uint32_t i = 0; i < queueFamilyCount; i++)
    {
        // 支持图形工作
        if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
        {
            queueFamilyIndex = i;
            break;
        }
    }
    free(queueFamilyProperties);

然后在c文件的头添加queueFamilyIndex定义。

static uint32_t queueFamilyIndex = -1;

运行后输出如下:

Current Vulkan instance created success.
Physical Info:ID:  7308
Name:  GeForce GTX 1050 Ti
Vulkan Version:  4202651
Vulkan instance destroyed.

继续。。。

编写CreateLogicalDevice()函数。

这里只创建一个队列。因为很多时候没有必要创建多个队列。这是因为可以在多个线程上创建所有命令缓冲区,然后在主线程一次性的以较低开销的调用提交队列3

    float queuePriority = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfo={
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .queueFamilyIndex = queueFamilyIndex,
        .queueCount = 1,// 只需要创建一个队列
        .pQueuePriorities = &queuePriority
    }

这里需要VK_KHR_SWAPCHAIN_EXTENSION_NAME扩展支持,因为并不是所有的图形卡具备能力将绘制的图像直接显示到屏幕上4

验证层开启比较麻烦,而且有些情况下也不支持。这里先不开。

const char *deviceExtensionNames[]={VK_KHR_SWAPCHAIN_EXTENSION_NAME};
    VkDeviceCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pQueueCreateInfos = &queueCreateInfo,
        .queueCreateInfoCount = 1,
        .ppEnabledExtensionNames = deviceExtensionNames,
        .enabledLayerCount = 0 // 先不开启验证层
    };

最后创建逻辑设备

VkResult result = vkCreateDevice(physicalDevice,&createInfo,NULL,&logicalDevice);

今天到这里就结束了,也写了好长一堆代码,最后贴出完整的VulkanManager.c的全部代码。完整的可以去Github上

#include "VulkanManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// GLFW窗口
static GLFWwindow* window;
// Vulkan实例
static VkInstance instance;
// 物理设备
static VkPhysicalDevice physicalDevice;
// 使用的队列家族编号
static uint32_t queueFamilyIndex = -1;
// 逻辑设备
static VkDevice logicalDevice;

/* 初始化实例 */
static void InitInstance(const char* title)
{
    // 应用信息
    VkApplicationInfo appInfo = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = title,
        .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
        .pEngineName = "The Seed Game Engine",
        .engineVersion = VK_MAKE_VERSION(1, 0, 0),
        .apiVersion = VK_API_VERSION_1_0,
    };

    // 使用glfw扩展
    unsigned int glfwExtensionCount = 0;
    const char** glfwExtensions;

    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    // 实例信息
    VkInstanceCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pApplicationInfo = &appInfo,
        .enabledExtensionCount = glfwExtensionCount,
        .ppEnabledExtensionNames = glfwExtensions,
        .enabledLayerCount = 0
    };

    // 创建实例
    VkResult result = vkCreateInstance(&createInfo, NULL, &instance);
    if (result == VK_SUCCESS)
    {
        printf("Vulkan instance created success.\\n");
    }
    else
    {
        printf("Vulkan instance created failed.\\n");
        abort();
    }
}

/* 销毁实例 */
static void DestroyInstance()
{
    vkDestroyInstance(instance, NULL);
    printf("Vulkan instance destroyed.");
    glfwDestroyWindow(window);
}

/* 获取物理设备 */
static void PickPhysicalDevice()
{
    uint32_t deviceCount;
    VkResult result = vkEnumeratePhysicalDevices(instance, &deviceCount, NULL);
    assert(result == VK_SUCCESS);
    if (deviceCount == 0)
    {
        printf("Failed to find GPUs with Vulkan support!");
        abort();
    }
    VkPhysicalDevice* devices = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices);

    // 选择可用的物理设备
    physicalDevice = devices[0];//没啥特殊要求,直接选择第一个设备即可
    free(devices);
    // 输出选择的物理设备信息
    VkPhysicalDeviceProperties deviceProperties;
    vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
    printf("Current Physical Info:ID:  %I32u\\nName:  %s\\nVulkan Version:  %I32u\\n",
        deviceProperties.deviceID,
        deviceProperties.deviceName,
        deviceProperties.apiVersion);


    // 获取设备队列族的数量
    uint32_t queueFamilyCount;
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL);
    VkQueueFamilyProperties
        * queueFamilyProperties = (VkQueueFamilyProperties*)malloc(sizeof(VkQueueFamilyProperties) * queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL);

    // 遍历队列族
    for (uint32_t i = 0; i < queueFamilyCount; i++)
    {
        // 支持图形工作
        if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
        {
            queueFamilyIndex = i;
            break;
        }
    }
    free(queueFamilyProperties);
}

/* 创建逻辑设备 */
static void CreateLogicalDevice()
{
    // 队列优先级
    float queuePriority = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .queueFamilyIndex = queueFamilyIndex,
        .queueCount = 1,// 只需要创建一个队列
        .pQueuePriorities = &queuePriority
    };
    const char* deviceExtensionNames[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
    VkDeviceCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pQueueCreateInfos = &queueCreateInfo,
        .queueCreateInfoCount = 1,
        .ppEnabledExtensionNames = deviceExtensionNames,
        .enabledLayerCount = 0 // 先不开启验证层
    };
    VkResult result = vkCreateDevice(physicalDevice, &createInfo, NULL, &logicalDevice);
}

void InitWindow(int width, int height, const char* title)
{
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
    window = glfwCreateWindow(width, height, title, NULL, NULL);

    InitInstance(title);
    PickPhysicalDevice();
}

int WindowShouldClose()
{
    int flag = glfwWindowShouldClose(window);
    if (!flag)
    {
        glfwPollEvents();
    }
    return flag;
}
void CloseWindow()
{
    DestroyInstance();
    glfwTerminate();
}




第三天的地址:https://blog.csdn.net/z736248591/article/details/117266221


  1. vulkan的QueueFamilyProperties - 月色疯狂 - 博客园https://www.cnblogs.com/mooniscrazy/p/11711634.html ↩︎

  2. Vulkan初始化——虚拟逻辑设备 - 知乎 https://zhuanlan.zhihu.com/p/24877337 ↩︎

  3. Vulkan填坑学习Day05—逻辑设备与队列_沉默的舞台剧的博客-CSDN博客 https://blog.csdn.net/qq_35312463/article/details/103862429 ↩︎

  4. Vulkan 交换链详解_sy_liao的专栏-CSDN博客 https://blog.csdn.net/u010281924/article/details/105368560 ↩︎

以上是关于游戏引擎开发日志(第二天)的主要内容,如果未能解决你的问题,请参考以下文章

游戏引擎开发日志 (第四天 2021年6月8日)

自学游戏开发第二天(电脑游戏开发概述之电脑游戏类型,开发工具,游戏策划)

软件工程课设迭代开发第二天

「游戏引擎 浅入浅出」4.3 片段着色器

「游戏引擎 浅入浅出」4.3 片段着色器

「游戏引擎 浅入浅出」4.3 片段着色器