目录

实时光追插件

实时光追插件实现了光线与模型的加速结构构建与求交功能。

您只要初始化Vulkan运行环境,输入模型顶点数据,可以构建出一个加速结构,再输入光线数据,可以计算出所有光线和模型的交点。

开发步骤

  1. 下载SDK包,解压获取SDK库,下载地址请参见SDK下载:Ray Shop插件。
  2. 在C++代码中集成光线追踪求交SDK库文件“librtcore.so”和头文件“Traversal.h”。

    例如使用CMake构建工具,CMakeLists.txt样例如下:
    cmake_minimum_required(VERSION 3.4.1)
    
    set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -O2 -DNDEBUG -DVK_USE_PLATFORM_ANDROID_KHR")
    file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")          # 引入开发者主程序代码
    
    # librtcore.so导入
    include_directories(${PROJ_ROOT}/include)
    ADD_LIBRARY(
            rtcore
            SHARED
            IMPORTED)
    SET_TARGET_PROPERTIES(rtcore
            PROPERTIES IMPORTED_LOCATION
            ${PROJ_ROOT}/libs/${ANDROID_ABI}/librtcore.so)
    
    # 编译Android NDK native lib
    add_library(native-lib SHARED ${EXAMPLE_SRC})
    add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
    
    target_link_libraries(
            native-lib
            native-app-glue
            libbase
            android
            log
            z
    )

  3. 创建Vulkan运行环境,初始化Traversal类。

    // 创建vulkan运行环境,包括VkPhysicalDevice, VkDevice, VkQueue。
    RayShop::Vulkan::Traversal traversal;
    // 调用Setup接口初始化Traversal类。
    // 将phycial devcie, logical device, compute queue, compute queue indice, jniEnv设置到Traversal。
    traversal.Setup(physicalDevice, logicalDevice, computeQueue, computeIndice, jniEnv);

    physicalDevice、logicalDevice、computeQueue、computeIndice需要开发者在创建Vulkan环境时创建并初始化。

    jniEnv需要开发者实现jni接口创建。

  4. 构造场景图元数据,构建加速结构。

    // 加载模型顶点数据,构建GeometryTriangleDescription结构。
    // 从gltf等文件中读取模型,仅参考,需自行实现
    std::vector<vkglTF::Model> scene;
    std::vector<vkvert::Vertex> worldVertices;
    scene.loadFromFile("path/to/model/file");
    worldVertices.resize(scene.vertexBuffer.size());
    scene.convertLocalVertexToWorld(modelMatrix, worldVertices);
    
    // 填充GeometryTriangleDescription结构
    RayShop::GeometryTriangleDescription geometry;
    geometry.stride = sizeof(vkvert::Vertex) / sizeof(float);
    geometry.vertices.cpuBuffer = worldVertices.data();
    geometry.vertices.type = RayShop::BufferType::CPU;
    geometry.verticesCount = static_cast<uint32_t>(worldVertices.size());
    geometry.indices.cpuBuffer = scene.indexBuffer.data();
    geometry.indices.type = RayShop::BufferType::CPU;
    geometry.indicesCount = static_cast<uint32_t>(scene.indexBuffer.size());
    
    // 多个GeometryTriangleDescription结构组成vector
    std::vector<RayShop::GeometryTriangleDescription> bvhGeometriesOnCPU;
    std::vector<RayShop::BLAS> blases;
    bvhGeometriesOnCPU.push_back(geometry);
    blases.resize(bvhGeometriesOnCPU.size());
    
    // 将每个geometry都构建成一个底层加速结构,第一个参数表示构建的方式,第二个参数表示geometry数量。
    // 第三个参数表示geometry数据,第四个参数表示返回的加速结构handle。
    RayShop::Result res = traversal.CreateBLAS(RayShop::ASBuildMethod::SAH_CPU,
    static_cast<uint32_t>(bvhGeometriesOnCPU.size()), bvhGeometriesOnCPU.data(), blases.data());
    if (res != RayShop::Result::SUCCESS) {
        LOGE("Failed to CreateBLAS, err: %s.", traversal.GetErrorCodeString(res));
        return false;
    }
    
    // 将若干个底层加速结构再构建成顶层加速结构。
    // InstanceDescription表示一个需要参与构建的底层加速结构。
    // 成员blas表示底层加速结构的handle,成员transform表示底层加速结构的变换矩阵。
    std::vector<RayShop::InstanceDescription> intances;
    for (unsigned int blase : blases) {
        RayShop::InstanceDescription instance {};
        instance.blas = blase;
        instance.transform[0][0] = 1;
        instance.transform[1][1] = 1;
        instance.transform[2][2] = 1;
        instance.transform[3][3] = 1;
        intances.push_back(instance);
    }
    res = traversal.CreateTLAS(static_cast<uint32_t>(intances.size()), intances.data());
    if (res != RayShop::Result::SUCCESS) {
        LOGE("Failed to CreateTLAS, err: %s.", traversal.GetErrorCodeString(res));
        return false;
    }

  5. (可选)当场景发生变化时,使用最新场景图元数据,更新加速结构。

    // 更新顶点数据,只能改变顶点坐标值,数量不能发生变化。
    // 可以在CPU buffer或GPU buffer上使用,推荐GPU侧,效率更高
    
    // 初始化,创建GPU buffer
    struct BVHBuffers {
        vks::Buffer vertex;
        vks::Buffer index;
    } bvhBuffers;
    struct BVHStagingBuffers {
        vks::Buffer vertex;
        vks::Buffer index;
    } bvhStagingBuffers;
    std::vector<RayShop::GeometryTriangleDescription> bvhGeometriesOnGPU;
    bvhGeometriesOnGPU.resize(bvhGeometriesOnCPU.size());
    for (size_t i = 0; i < bvhGeometriesOnGPU.size(); i++) {
        bvhGeometriesOnGPU[i].vertices.type = RayShop::BufferType::GPU;
        bvhGeometriesOnGPU[i].vertices.gpuVkBuffer = bvhBuffers.vertex.buffer;
        bvhGeometriesOnGPU[i].verticesCount = bvhGeometriesOnCPU[i].verticesCount;
        bvhGeometriesOnGPU[i].stride = bvhGeometriesOnCPU[i].stride;
        bvhGeometriesOnGPU[i].indices.type = RayShop::BufferType::GPU;
        bvhGeometriesOnGPU[i].indices.gpuVkBuffer = bvhBuffers.index.buffer;
        bvhGeometriesOnGPU[i].indicesCount = bvhGeometriesOnCPU[i].indicesCount;
    }
    
    // 更新顶点信息
    scene.convertLocalVertexToWorld(modelMatrix, m_worldVertices);  // 自定义函数
    // 拷贝到共享内存
    size_t size = m_worldVertices.size() * sizeof(vkvert::Vertex);
    memcpy(m_bvhStagingBuffers.vertex.mapped, m_worldVertices.data(), size);
    size = scene.indexBuffer.size() * sizeof(uint32_t);
    memcpy(m_bvhStagingBuffers.index.mapped, scene.indexBuffer.data(), size);
    // 拷贝到GPU侧
    VkBufferCopy copyRegion = {};
    copyRegion.srcOffset = 0;
    copyRegion.dstOffset = 0;
    
    copyRegion.size = m_bvhStagingBuffers.vertex.size;
    vkCmdCopyBuffer(cmdBuffer, m_bvhStagingBuffers.vertex.buffer, m_bvhBuffers.vertex.buffer, 1, &copyRegion);
    copyRegion.size = m_bvhStagingBuffers.index.size;
    vkCmdCopyBuffer(cmdBuffer, m_bvhStagingBuffers.index.buffer, m_bvhBuffers.index.buffer, 1, &copyRegion);
    
    // 对坐标改变的geometry更新构建底层加速结构。
    traversal.RefitBLAS(static_cast<uint32_t>(bvhGeometriesOnGPU.size()),
        bvhGeometriesOnGPU.data(), blases.data());

  6. 获得指定光线的交点信息。

    1. 在cpp文件中创建ShaderModule,在shader侧调用求交接口,(推荐使用此方法,效率更高)。
      // xxx.frag, fragment shader文件
      // (可选)使用include扩展
      #extension GL_GOOGLE_include_directive : enable
      #include "sth.glsl"
      void main()
      {
          // 创建光线并设置光线信息
          Ray ray;
          ray.origin = ...;
          ray.direction = ...;
          ray.mint = 0.01f;
          ray.maxt = 10.0f;
      
          HitInfo hitInfo;
          hitInfo.t = -1.0f;
          // 调用接口获取交点信息
          traceRay(ray, RAY_FLAG_CLOSEST_HIT, hitInfo);
          // ...
      }
      // cpp侧,配置管线时将创建着色器模块
      // 创建ShaderModule
      RayShop::Vulkan::ShaderModule rtShaderModule;
      rtShaderModule.vkHandle = VK_NULL_HANDLE;
      RayShop::Vulkan::RayTracingShaderModuleCreateInfo rtShaderCreateInfo{};
      // 从shader文本中读取内容(仅示意)
      std::string rtFragContent = Open("path/to/xxx.frag")->Read();
      
      // (可选)添加shader的包含文件,多个
      std::vector<const char *> shaderIncludes;
      std::vector<const char *> shaderIncludeSources;
      shaderIncludes.push_back("sth.glsl");
      std::string raytraceInclude = Open("path/to/sth.frag")->Read();
      shaderIncludeSources.push_back(raytraceInclude.c_str());
      
      // 设置shader信息
      rtShaderCreateInfo.pName = "rtFragmentShader";
      rtShaderCreateInfo.pSource = rtFragContent.c_str();
      rtShaderCreateInfo.stage = RayShop::Vulkan::ShaderStage::SHADER_STAGE_FRAGMENT_BIT;
      rtShaderCreateInfo.hitFormat = RayShop::TraceRayHitFormat::T_PRIMID_INSTID_U_V;
      rtShaderCreateInfo.ppShaderIncludes = shaderIncludes.data();
      rtShaderCreateInfo.shaderIncludesCnt = static_cast<uint32_t>(shaderIncludes.size());
      rtShaderCreateInfo.ppShaderIncludeSources = shaderIncludeSources.data();
      // 调用接口创建光追ShaderModule
      RayShop::Result res = traversal.CreateRayTracingShaderModule(&rtShaderCreateInfo, &rtShaderModule);
      
      // 将光追ShaderModule配置到管线中
      // 配置vertex shader ... 略
      // 指定fragment shader的资源绑定,仅示意
      VkDescriptorSetLayout m_descSetLayout = VK_NULL_HANDLE;
      std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
          vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
              VK_SHADER_STAGE_FRAGMENT_BIT, 0),
          vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
              VK_SHADER_STAGE_FRAGMENT_BIT, 1),
          vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
              VK_SHADER_STAGE_FRAGMENT_BIT, 2),
          vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
              VK_SHADER_STAGE_FRAGMENT_BIT, 3),
          // 更多开发者自定义buffer
      };
      // 调用接口获取光追所需相关buffer并填入
      VkDescriptorBufferInfo bvhTree {};
      VkDescriptorBufferInfo bvhTriangles {};
      VkDescriptorBufferInfo tlasInfo {};
      VkDescriptorBufferInfo rtCoreUniforms {};
      res = traversal.GetTraversalDescBufferInfos(&bvhTree, &bvhTriangles, &tlasInfo, &rtCoreUniforms);
      if (res != RayShop::Result::SUCCESS) {
      LOGE("%s: Failed to GetTraversalDescBufferInfos, err: %s.", __func__,
          RayShop::Vulkan::Traversal::GetErrorCodeString(res));
          return false;
      }
      // 更新descriptorsets, 仅示意。
      std::vector<VkWriteDescriptorSet> writeDescriptorSets = { Utils::WriteDescriptorSet(
          VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, bvhTree),
          Utils::WriteDescriptorSet(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, bvhTriangles),
          Utils::WriteDescriptorSet(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, tlasInfo),
          Utils::WriteDescriptorSet(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, rtCoreUniforms),
          // 更多开发者自定义
      };
      vkUpdateDescriptorSets(
          m_vulkanDevice->logicalDevice,
          static_cast<uint32_t>(writeDescriptorSets.size()),
          writeDescriptorSets.data(),
          0,
          nullptr);
      // 更多管线配置...

      流程代码多为示意,仅标出核心API使用,管线配置需要开发者定义。

    2. 在cpp侧调用求交接口,获得光线的交点信息。
      RayShop::Ray *rays;
      uint32_t rayCount;
      // 创建光线,每条光线用RayShop::Ray表示,需要初始化其中的光线原点origin和方向dir,记录光线数量。
      GenerateRays(rays, rayCount);     // 自定义函数,初始化光线
      
      // 填充表示ray的RayShop::Buffer,同时支持cpu类型的buffer地址和gpu类型的VkBuffer。
      // 如果ray buffer是cpu地址,type填BufferType::CPU,如果是vkBuffer,type填BufferType::GPU。
      RayShop::Buffer rays;
      rays.type = BufferType::CPU;
      rays.cpuBuffer = rays;
      
      // 创建保存交点信息的buffer。
      // 使用Vulkan创建一个Storage Buffer,将其handle保存到hits.gpuVkBuffer。
      // 交点信息支持不同的格式,通过GetHitFormatBytes()接口获取每种格式的大小。
      // 交点信息buffer的总大小需要是rayCount乘以一个交点信息大小。
      
      vks::Buffer hitBuffer;
      vks::VulkanDevice *vulkanDevice;
      // vulkan 流程代码,开发者实现,仅示意
      vulkanDevice = new vks::VulkanDevice(physicalDevice);
      vulkanDevice->createBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &hitBuffer,
          rayCount * RayShop::Vulkan::Traversal::GetHitFormatBytes(RayShop::TraceRayHitFormat::T_PRIMID_U_V));
      
      RayShop::Buffer hits;
      hits.gpuVkBuffer = hitBuffer.vkBuffer;
      hits.type = BufferType::GPU;
      
      // TraceRays计算光线和加速结构的交点,求交结果保存在传入的hits参数对应的buffer。
      traversal.TraceRays(rayCount, TRACERAY_FLAG_INTERSECT_DEFAULT, rays, hits,
          TraceRayHitFormat::T_PRIMID_U_V);

      GenerateRays(rays, rayCount)方法需要开发者初始化光线信息填充Ray。

  7. 结束调用,释放资源。

    traversal.DestroyBLAS(static_cast<uint32_t>(blases.size()), blases.data());
    traversal.Destroy();