My Portfolio
Ubisoft La Forge Montreal에서 연구를 했었습니다. 게임같은 실시간 어플리케이션에 cloth deformation을 적용하기 위해 neural cloth synthesis를 연구 했습니다. 이 연구는 저의 석사 학위 프로그램과 함께 진행되었습니다. 연구 논문 Real-Time Neural Cloth Deformation using a Compact Latent Space and a Latent Vector Predictor가 ECCV 2024 CV2 Workshop에 승인되었습니다. 워크샵 논문이 업로드 되기전까지 해당 논문에 대한 것을 졸업 논문에서 참조가능 합니다.
We propose a method for real-time cloth deformation using neural networks, especially for draping a garment on a human body. The computational overhead of most of the existing learning methods for cloth deformation often limits their use in interactive applications. Employing a two-stage training process, our method predicts garment deformations in real-time. In the first stage, a graph neural network extracts cloth vertex features which are compressed into a latent vector with a mesh convolution network. We then decode the latent vector to blend shape weights, which are fed to a trainable blend shape module. In the second stage, we freeze the latent extraction and train a latent predictor network. The predictor uses a subset of the inputs from the first stage, ensuring that inputs are restricted to those which are readily available in a typical game engine. Then, during inference, the latent predictor predicts the compacted latent which is processed by the decoder and blend shape networks from the first stage. Our experiments demonstrate that our method effectively balances computational efficiency and realistic cloth deformation, making it suitable for real-time use in applications such as games.
사용자가 화학 정보 파일을 볼 수 있고, 렌더링 결과를 수정할 수 있고, 그 결과에 대한 labeled data를 얻을 수 있는 프로그램을 개발했습니다. 회사의 머신러닝 프로젝트의 많은 훈련 데이터셋을 생성하기 위한 프로그램입니다. 이 프로그램은 한 유명한 화학 정보 렌더링 라이브러리를 기반으로 만들어 졌습니다. Windows와 Ubuntu를 포함하여 크로스 플랫폼으로 작동하게 하고, cmake와 c++를 통해 빌드되어 집니다.
렌더러와, 그 렌더러를 위한 다른 시스템들을 개발했습니다. 이 프로젝트는 Windows, Mac, Android, 그리고 iOS를 포함하는 cross-platforms를 타겟으로 했었습니다. cmake build system과 C++11로 프로젝트가 빌드되었습니다. 렌더링과 관련된 다양한 측면 뿐만 아니라, 엔진이 튼튼하게 작동하기 위해 유용한 도구들을 다루었습니다.
ChLib의 저의 개인 라이브러리로, 미래 계획을 위해 게임엔진과 다른 어플리케이션을 만들기 위해 사용됩니다. 이 라이브러리는 다음의 것들로 개발되고 있습니다:
위의 개발 환경은 빠른 빌드타임을 갖게 해주고 코드를 즐겁게 작성할 수 있게 합니다. 코드 스타일을 보여주기 위해, chrender_vulkan_texture.c
파일을 여기에 공개합니다.
#include "chrender/vulkan/chrender_vulkan.h" #include "chrender/vulkan/chrender_vulkan_library.h" ChRenderTexture* chrender_vulkan_texture_create(ChRenderContext* ctx, ChRenderTextureParam* param) { ChAllocator allocator = param->allocator ? *param->allocator : ctx->permanent_buddy_allocator; ChRenderContextVulkan* vctx = (ChRenderContextVulkan*)ctx->handle; const u64 memory_size = sizeof(ChRenderTexture) + sizeof(ChRenderTextureVulkan); u8* memory = (u8*)allocator.alloc_func(allocator.object, memory_size, CH_PLATFORM_MIN_MALLOC_ALIGNMENT); if (memory == NULL) return NULL; memset(memory, 0, memory_size); ChRenderTexture* rt_handle = (ChRenderTexture*)memory; ChRenderTextureVulkan* rtv = (ChRenderTextureVulkan*)(memory + sizeof(ChRenderTexture)); rtv->allocator = allocator; rt_handle->type = param->type; rt_handle->format = param->format; rt_handle->width = param->width; rt_handle->height = param->height; rt_handle->depth = param->depth; rt_handle->handle = rtv; VkFormat texture_format = chrender_vulkan_get_vkformat(param->format); VkFormatProperties format_properties; vkGetPhysicalDeviceFormatProperties(vctx->physical_device, texture_format, &format_properties); /* * I am gonna always create a texture with the optimal tiling. * So, I validate the format feature support here * TODO : add more validation code here for usage and format feature */ VkImageUsageFlags image_usage = chrender_vulkan_get_vkimageusage(param->usage); bool wrong_usage = false; wrong_usage |= (image_usage & VK_IMAGE_USAGE_SAMPLED_BIT) && (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0; wrong_usage |= (image_usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) && (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) == 0; wrong_usage |= (image_usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) && (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == 0; wrong_usage |= (image_usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) && (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) == 0; wrong_usage |= (image_usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) && (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT) == 0; if (wrong_usage == true) goto FAIL; VkImageType image_type = chrender_vulkan_get_vkimagetype(param->type); VkImageCreateInfo image_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = NULL, .flags = 0, // @TODO : Do I need a param for this? .imageType = image_type, .format = texture_format, .extent = {.width = param->width, .height = param->height, .depth = param->depth}, .mipLevels = param->mip_levels, .arrayLayers = param->array_layers, .samples = param->samples, // same as vulkan .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = image_usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = NULL, .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED }; VkResult result = vkCreateImage(vctx->logical_device, &image_create_info, NULL, &rtv->image); if (result != VK_SUCCESS) goto FAIL; VkMemoryRequirements mem_req; vkGetImageMemoryRequirements(vctx->logical_device, rtv->image, &mem_req); VkMemoryPropertyFlags mem_priorities[1] = { VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT }; s32 mem_type_index = chrender_vulkan_find_memory_property_index(&vctx->device_mem_properties, mem_req.memoryTypeBits, mem_priorities[0]); if (mem_type_index == -1) goto FAIL; VkMemoryAllocateInfo mem_alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = NULL, .allocationSize = mem_req.size, .memoryTypeIndex = mem_type_index }; result = vkAllocateMemory(vctx->logical_device, &mem_alloc_info, NULL, &rtv->image_memory); if (result != VK_SUCCESS) goto FAIL; result = vkBindImageMemory(vctx->logical_device, rtv->image, rtv->image_memory, 0); if (result != VK_SUCCESS) goto FAIL; // just view image as it is VkImageViewType view_type = 0; switch (image_type) { case VK_IMAGE_TYPE_1D: { if (param->array_layers == 1) view_type = VK_IMAGE_VIEW_TYPE_1D; else view_type = VK_IMAGE_VIEW_TYPE_1D_ARRAY; break; } case VK_IMAGE_TYPE_2D: { if (param->array_layers == 1) view_type = VK_IMAGE_VIEW_TYPE_2D; else view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; break; } case VK_IMAGE_TYPE_3D: { if (param->array_layers == 1) { view_type = VK_IMAGE_VIEW_TYPE_3D; break; } else { // wrong parameter CH_ASSERT(0); } } default: // @TODO : CUBE_TYPE... { CH_ASSERT(0); } } bool is_depth, is_stencil; chrender_vulkan_check_render_depthstencil(param->format, &is_depth, &is_stencil); VkImageAspectFlags image_aspects = VK_IMAGE_ASPECT_NONE; if (is_depth || is_stencil) { if (is_depth) image_aspects |= VK_IMAGE_ASPECT_DEPTH_BIT; if (is_stencil) image_aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; } else { image_aspects = VK_IMAGE_ASPECT_COLOR_BIT; } VkImageViewCreateInfo imageview_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = NULL, .flags = 0, .image = rtv->image, .viewType = view_type, .format = texture_format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY }, // @TODO : mipmapping .subresourceRange = { .aspectMask = image_aspects, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 } }; result = vkCreateImageView(vctx->logical_device, &imageview_create_info, NULL, &rtv->imageview); if (result != VK_SUCCESS) goto FAIL; rtv->layout = VK_IMAGE_LAYOUT_PREINITIALIZED; return rt_handle; FAIL: if (rtv->imageview != NULL) { vkDestroyImageView(vctx->logical_device, rtv->imageview, NULL); } if (rtv->image_memory != NULL) { vkFreeMemory(vctx->logical_device, rtv->image_memory, NULL); } if (rtv->image != NULL) { vkDestroyImage(vctx->logical_device, rtv->image, NULL); } if (memory != NULL) { allocator.free_func(allocator.object, memory); } return NULL; } bool chrender_vulkan_texture_destroy(ChRenderContext* ctx, ChRenderTexture* texture) { ChRenderContextVulkan* vctx = (ChRenderContextVulkan*)ctx->handle; ChRenderTextureVulkan* rtv = (ChRenderTextureVulkan*)texture->handle; ChAllocator allocator = rtv->allocator; vkDestroyImageView(vctx->logical_device, rtv->imageview, NULL); vkFreeMemory(vctx->logical_device, rtv->image_memory, NULL); vkDestroyImage(vctx->logical_device, rtv->image, NULL); allocator.free_func(allocator.object, texture); return true; } bool chrender_vulkan_texture_update(ChRenderContext* ctx, ChRenderTexture* texture, ChRenderTextureUpdateParam* param) { ChRenderContextVulkan* rctxv = (ChRenderContextVulkan*)ctx->handle; ChRenderTextureVulkan* rtv = (ChRenderTextureVulkan*)texture->handle; ChRenderCommandBufferVulkan* rcbv = (ChRenderCommandBufferVulkan*)param->command_buffer->handle; VkCommandBuffer cur_command_buffer = rcbv->command_buffers[rctxv->submission_index]; /* * 1. Transfer the image layout of the texture from its oldLayout to TRANSFER_DST_OPTIMAL, * in order to update its content. * 2. Create a staging texture buffer that is visible to the cpu and update the user's texture to the staging texture buffer * 3. Copy the staging buffer data to the texture * 4. Transfer the image layout of the texture from TRANSFER_DST_OPTIMAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, so that * a shader can read this texture. */ // @TODO : add the texture update dependency to the param? VkImageMemoryBarrier image_memory_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = rtv->layout, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = rtv->image, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, // @TODO .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, } }; // VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT means // no stage of execution when specified in the first scope. VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT; vkCmdPipelineBarrier ( cur_command_buffer, src_stage_mask, dst_stage_mask, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier ); // Step 1 Done VkBuffer staging_buffer = VK_NULL_HANDLE; if (false == chrender_vulkan_staging_texture_buffer_get(rctxv, param, texture->format, &staging_buffer)) return false; // Step 2 Done VkBufferImageCopy buffer_image_copy = { .bufferOffset = 0, .bufferRowLength = param->width, .bufferImageHeight = param->height, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, // @TODO .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .imageOffset = {.x = 0, .y = 0, .z = 0}, .imageExtent = { .width = param->width, .height = param->height, .depth = 1, } }; vkCmdCopyBufferToImage ( cur_command_buffer, staging_buffer, rtv->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_image_copy ); // Step 3 Done image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT; // TODO(@CHAN): If this texture is read in vertex shader, then this sync is wrong. // So, On 20241111 I convert dst_stage_mask from VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT to // VK_PIPELINE_STAGE_VERTEX_SHADER_BIT for general usage // I should add one more additional update hint parameter for this. dst_stage_mask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; vkCmdPipelineBarrier ( cur_command_buffer, src_stage_mask, dst_stage_mask, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier ); // Step 4 Done rtv->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; return true; } ChRenderFormat chrender_vulkan_get_renderformat(VkFormat format) { switch (format) { case VK_FORMAT_R8_UNORM: return CHRENDER_FORMAT_R8_UNORM; case VK_FORMAT_R8_UINT: return CHRENDER_FORMAT_R8_UINT; case VK_FORMAT_R8G8B8_UNORM: return CHRENDER_FORMAT_R8G8B8_UNORM; case VK_FORMAT_R8G8B8A8_UNORM: return CHRENDER_FORMAT_R8G8B8A8_UNORM; case VK_FORMAT_R8G8B8A8_SNORM: return CHRENDER_FORMAT_R8G8B8A8_SNORM; case VK_FORMAT_R8G8B8A8_UINT: return CHRENDER_FORMAT_R8G8B8A8_UINT; case VK_FORMAT_R8G8B8A8_SINT: return CHRENDER_FORMAT_R8G8B8A8_SINT; case VK_FORMAT_R8G8B8A8_SRGB: return CHRENDER_FORMAT_R8G8B8A8_SRGB; case VK_FORMAT_B8G8R8A8_UNORM: return CHRENDER_FORMAT_B8G8R8A8_UNORM; case VK_FORMAT_R32_UINT: return CHRENDER_FORMAT_R32_UINT; case VK_FORMAT_R32_SFLOAT: return CHRENDER_FORMAT_R32_SFLOAT; case VK_FORMAT_R32G32_SFLOAT: return CHRENDER_FORMAT_R32G32_SFLOAT; case VK_FORMAT_R32G32B32_SFLOAT: return CHRENDER_FORMAT_R32G32B32_SFLOAT; case VK_FORMAT_R32G32B32A32_UINT: return CHRENDER_FORMAT_R32G32B32A32_UINT; case VK_FORMAT_R32G32B32A32_SINT: return CHRENDER_FORMAT_R32G32B32A32_SINT; case VK_FORMAT_R32G32B32A32_SFLOAT: return CHRENDER_FORMAT_R32G32B32A32_SFLOAT; case VK_FORMAT_D16_UNORM: return CHRENDER_FORMAT_D16_UNORM; case VK_FORMAT_D32_SFLOAT: return CHRENDER_FORMAT_D32_SFLOAT; case VK_FORMAT_D16_UNORM_S8_UINT: return CHRENDER_FORMAT_D16_UNORM_S8_UINT; case VK_FORMAT_D24_UNORM_S8_UINT: return CHRENDER_FORMAT_D24_UNORM_S8_UINT; case VK_FORMAT_D32_SFLOAT_S8_UINT: return CHRENDER_FORMAT_D32_SFLOAT_S8_UINT; } return CHRENDER_FORMAT_UNDEFINED; } VkFormat chrender_vulkan_get_vkformat(ChRenderFormat format) { switch (format) { case CHRENDER_FORMAT_R8_UNORM: return VK_FORMAT_R8_UNORM; case CHRENDER_FORMAT_R8_UINT: return VK_FORMAT_R8_UINT; case CHRENDER_FORMAT_R8G8B8_UNORM: return VK_FORMAT_R8G8B8_UNORM; case CHRENDER_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM; case CHRENDER_FORMAT_R8G8B8A8_SNORM: return VK_FORMAT_R8G8B8A8_SNORM; case CHRENDER_FORMAT_R8G8B8A8_UINT: return VK_FORMAT_R8G8B8A8_UINT; case CHRENDER_FORMAT_R8G8B8A8_SINT: return VK_FORMAT_R8G8B8A8_SINT; case CHRENDER_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_SRGB; case CHRENDER_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_UNORM; case CHRENDER_FORMAT_R32_UINT: return VK_FORMAT_R32_UINT; case CHRENDER_FORMAT_R32_SFLOAT: return VK_FORMAT_R32_SFLOAT; case CHRENDER_FORMAT_R32G32_SFLOAT: return VK_FORMAT_R32G32_SFLOAT; case CHRENDER_FORMAT_R32G32B32_SFLOAT: return VK_FORMAT_R32G32B32_SFLOAT; case CHRENDER_FORMAT_R32G32B32A32_UINT: return VK_FORMAT_R32G32B32A32_UINT; case CHRENDER_FORMAT_R32G32B32A32_SINT: return VK_FORMAT_R32G32B32A32_SINT; case CHRENDER_FORMAT_R32G32B32A32_SFLOAT: return VK_FORMAT_R32G32B32A32_SFLOAT; case CHRENDER_FORMAT_D16_UNORM: return VK_FORMAT_D16_UNORM; case CHRENDER_FORMAT_D32_SFLOAT: return VK_FORMAT_D32_SFLOAT; case CHRENDER_FORMAT_D16_UNORM_S8_UINT: return VK_FORMAT_D16_UNORM_S8_UINT; case CHRENDER_FORMAT_D24_UNORM_S8_UINT: return VK_FORMAT_D24_UNORM_S8_UINT; case CHRENDER_FORMAT_D32_SFLOAT_S8_UINT: return VK_FORMAT_D32_SFLOAT_S8_UINT; } return VK_FORMAT_UNDEFINED; } u64 chrender_vulkan_get_vkformat_size(VkFormat format) { switch (format) { case VK_FORMAT_R8_UNORM: case VK_FORMAT_R8_UINT: return 1; case VK_FORMAT_D16_UNORM: return 2; case VK_FORMAT_R8G8B8_UNORM: case VK_FORMAT_D16_UNORM_S8_UINT: return 3; case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SNORM: case VK_FORMAT_R8G8B8A8_UINT: case VK_FORMAT_R8G8B8A8_SINT: case VK_FORMAT_R8G8B8A8_SRGB: case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_R32_UINT: case VK_FORMAT_R32_SFLOAT: case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D24_UNORM_S8_UINT: return 4; case VK_FORMAT_D32_SFLOAT_S8_UINT: return 5; case VK_FORMAT_R32G32_SFLOAT: return 8; case VK_FORMAT_R32G32B32_SFLOAT: return 12; case VK_FORMAT_R32G32B32A32_UINT: case VK_FORMAT_R32G32B32A32_SINT: case VK_FORMAT_R32G32B32A32_SFLOAT: return 16; } return 0; } ChRenderTextureUsageFlags chrender_vulkan_get_textureusage(VkImageUsageFlags in_flags) { // ChRenderTextureUsageFlags are defined the same as vulkan ChRenderTextureUsageFlags flags = (ChRenderTextureUsageFlags)in_flags; return flags; } VkImageUsageFlags chrender_vulkan_get_vkimageusage(ChRenderTextureUsageFlags in_flags) { VkImageUsageFlags flags = (VkImageUsageFlags)in_flags; return flags; } ChRenderTextureType chrender_vulkan_get_imagetype(VkImageType type) { switch (type) { case VK_IMAGE_TYPE_1D: return CHRENDER_TEXTURE_TYPE_1D; case VK_IMAGE_TYPE_2D: return CHRENDER_TEXTURE_TYPE_2D; case VK_IMAGE_TYPE_3D: return CHRENDER_TEXTURE_TYPE_3D; } return 0; } VkImageType chrender_vulkan_get_vkimagetype(ChRenderTextureType type) { switch (type) { case CHRENDER_TEXTURE_TYPE_1D: return VK_IMAGE_TYPE_1D; case CHRENDER_TEXTURE_TYPE_2D: return VK_IMAGE_TYPE_2D; case CHRENDER_TEXTURE_TYPE_3D: return VK_IMAGE_TYPE_3D; } return VK_IMAGE_TYPE_MAX_ENUM; } void chrender_vulkan_check_render_depthstencil(ChRenderFormat format, bool* is_depth, bool* is_stencil) { switch (format) { case CHRENDER_FORMAT_D16_UNORM: { if (is_depth) *is_depth = true; if (is_stencil) * is_stencil = false; return; } case CHRENDER_FORMAT_D32_SFLOAT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = false; return; } case CHRENDER_FORMAT_D16_UNORM_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = true; return; } case CHRENDER_FORMAT_D24_UNORM_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = true; return; } case CHRENDER_FORMAT_D32_SFLOAT_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) * is_stencil = true; return; } } if (is_depth) *is_depth = false; if (is_stencil) *is_stencil = false; } void chrender_vulkan_check_vkdepthstencil(VkFormat format, bool* is_depth, bool* is_stencil) { switch(format) { case VK_FORMAT_D16_UNORM: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = false; return; } case VK_FORMAT_X8_D24_UNORM_PACK32: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = false; return; } case VK_FORMAT_D32_SFLOAT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = false; return; } case VK_FORMAT_D16_UNORM_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = true; return; } case VK_FORMAT_D24_UNORM_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = true; return; } case VK_FORMAT_D32_SFLOAT_S8_UINT: { if (is_depth) *is_depth = true; if (is_stencil) *is_stencil = true; return; } } if (is_depth) *is_depth = false; if (is_stencil) *is_stencil = false; }
구현한 것들 강조하고 싶은 것들의 리스트입니다.
진행상황 스크린샷들 입니다:
Hoppe et al.의 Mesh Optimization 논문을 학교 수업 (COMP 6381 - Geometric Modeling)을 위해 공부했습니다. Hoppe의 코드를 복사하여 제 코드 베이스에서 작동되게 만들었습니다. 학교 수업 제출물로서 논문을 이해하기 위해 코드를 작성헀고, mesh vertices를 무작위로 샘플된 points에 fitting하는 논문의 알고리즘의 첫 번째 단계에서 non-linear solvers를 적용하려고 했습니다 (AMPL 사용). 논문에서는 simplificaiton operation이 한 번에 한 vertex에 대해서만 작동하기 때문에 linear least-squares problem을 locally하게 풀어나갔습니다. non-linear solvers를 적용하려는 저의 시도는 optimization 결과에 눈에 띄는 차이를 만들진 못했습니다. 그렇기에 이 구현물을 수업 제출물로 사용했습니다. 이 논문은 linear least-squares problem을 실제 적으로 적용하는 것을 공부하고 mesh의 HalfEdge 자료구조로 edge collapse, edge split, 그리고 edge swap같은 topological operations을 하는 것을 배우기에 매우 교육적입니다.
mesh로부터 SDF values를 계산하고, 이것을 OpenGL API로 marching cubes algorithm으로 렌더링 하는 것을 공부하기 위해 이 프로젝트를 했습니다. 이 프로그램은 연습만을 위한 것이고 최적화 되어있지 않습니다. 여기에서 사용된 코드 스타일은 빠른 구현을 위한 것입니다.
SDF values를 계산할 때, mesh의 한 triangle를 가지는 leaf node가 있는 AABB tree를 사용했습니다. BVH구조를 통해 grid point에 대해 가장 가까운 triangle를 탐색합니다. 한 query (grid) point에 대해 가장 가까운 삼각형을 얻고나서, 그 query point에서 삼각형에 있는 가장 가까운 point를 알 수 있습니다. closest point에서 query point로 향하는 vector는 triangle normal과 함께 grid point가 삼각형의 true plane에 있는지를 아는데 사용됩니다. 만약 true plane에 있다면, 그 query point는 메쉬 밖에 있고, 이것은 SDF value가 양수라는 것을 의미합니다. 만약 그렇지 않다면, SDF value는 음수(바깥)입니다. 이 프로세스를 더 가속화하기 위해 ThreadPool를 사용합니다.
컴퓨터 그래픽스, 물리, 그리고 게임엔진을 공부하기 위해 시작했던 첫 번째 개인 라이브러리 입니다. Deferred Shading, Lighting, Shadow Cast, Post Processing (HDR, Tone Mapping, Bloom, SSAO), Perlin noise로 Terrain Rendering과 같은 다양한 그래픽스 기능들을 구현했습니다. 물리 부분에 대해선, 충돌을 탐지하고 접점을 해결하는 간단한 물리엔진을 구현했습니다. 그 물리 엔진을 위해 Rigidbody Dynamics와 Sequential Impulse Constraint Solver를 공부했었습니다. 그 이후에, Bounding Volume Hierarchy를 이용한 multi-threaded ray tracer를 구현했습니다.
Box2D의 개발자인 Erin Catto의 Game Developer Conference (GDC) 발표 자료로 충돌 탐지에서 자주 사용되는 알고리즘을 공부하고 구현했습니다.