一、鼠标触碰检测功能
前面图形交互1和2简要介绍了在渲染过程中如何准备数据及准备了哪些数据,在这篇中就利用前面准备的颜色、坐标及ID来实现简易版本的鼠标触碰绘制物体时物体自动变色,鼠标离开物体时物体又恢复原有颜色,整个实现流程如下:
- 获取鼠标在绘图区的坐标
- 提取鼠标所在位置的物体ID
- 提取媒介图片像素颜色绘制到显示图片,但在这个过程中如果像素点所在位置记录的ID同步骤2提取的物体ID相等那么这改变该像素颜色,如果不等则直接绘制原有颜色。
二、具体实现
1、获取鼠标在绘图区坐标
绘图区是嵌套在QWidget中,因此监听QWidget的enterEvent()和leaveEvent()事件,显然当enterEvent事件触发时才需要进行鼠标触碰检测,而当leaveEvent触发后则无需进行检测。接着监听mouseMoveEvent()鼠标移动事件,当鼠标移动时记录屏幕坐标至推送常量中作为计算的输入。
2、提取鼠标所在位置的物体ID
提取物体ID本程序采用计算着色器来实现,整个计算过程输入数据为鼠标屏幕坐标和坐标图片,计算着色器代码如下:
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout( local_size_x = 1, local_size_y = 1, local_size_z = 1 ) in;
layout(push_constant) uniform PushConsts
{
vec4 cMousePos; //[0][1]为鼠标屏幕坐标的X和Y值
} pushConsts;
layout(binding = 0) uniform sampler2D texSampler; //用于坐标图片采样
layout(std140, binding = 1) buffer bufDataResult //记录鼠标所在位置的采样结果
{
//注意:此处是1个像素点物体ID采样,如果如CAD那么将鼠标设置为一定范围的靶框,那么此存储缓存则改
// 成数组,并且修改local_size_x或在提交dispatch命令时修改行列值与靶框的像素对应
vec4 dataResult;
};
void main()
{
//采样坐标图片数据得到鼠标处像素点的坐标及ID
vec4 sampleData = texture(texSampler, pushConsts.cMousePos.xy);
if(sampleData.w > 0)
dataResult = sampleData;//当采样得到的物体ID值不为空时表示鼠标触碰到了物体,则记录采样值
else
dataResult = vec4(-1, -1, -1, -1);
}
以上着色器代码中需要注意的是管线中绑定的采样器的设置,我们通常在采样时将采样的范围设置为0-1(标准化坐标),但在这里为了与屏幕像素一一对应我们则采用实际范围的采样方式,具体采样器的创建信息代码如下:
VkSamplerCreateInfo samplerInfo
{
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
nullptr,
0,
VK_FILTER_NEAREST,
VK_FILTER_NEAREST,
VK_SAMPLER_MIPMAP_MODE_NEAREST,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
0,
VK_FALSE,
0,
VK_FALSE,
VK_COMPARE_OP_ALWAYS,
0,
0,
VK_BORDER_COLOR_INT_OPAQUE_BLACK,
VK_TRUE //此处注意,采用非标准坐标系及使用图片的实际尺寸
};
3、渲染最终显示图片
在渲染的过程中其实涵盖步骤2的计算,但为保证在渲染图片之前必须完成物体ID值的提取,因此在命令中提交完VkCmdDispatch后需要设置内存屏障,具体代码如下:
void MLINVulkanPrivate::nextDraw_1_Common(VkCommandBuffer commandBuffer, uint32_t imageIndex)
{
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS)
throw std::runtime_error("failed to begin recording command buffer!");
mMouseValComputeRes.GetCoordinate(commandBuffer);//执行vkCmdDispatch提取鼠标位置处的物体ID
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 1, &mMouseValComputeRes.mMemBarrier,
0, nullptr, 0, nullptr);//此处增加内存屏障确保在绘制图片前得到鼠标处的物体ID
mViewport.width = static_cast<float>(mSwapChainExtent.width);
mViewport.height = static_cast<float>(mSwapChainExtent.height);
vkCmdSetViewport(commandBuffer, 0, 1, &mViewport);
mScissor.extent = mSwapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &mScissor);
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = mRenderPassForCommon;
renderPassInfo.framebuffer = mSwapChainFramebuffersForCommon[imageIndex];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = mSwapChainExtent;
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = mClearValuesForCoordinateAndObjID.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
QVector<VkDescriptorSet> setes = { mPublicUniformRes.mStep1DesSet, mMouseValComputeRes.mPickDesSet };
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mObjIDAndCoordinatePipelineRes.mStep1_CommonPipeLayout, 0, 2, setes.constData(), 0, nullptr);
//采样绘制最终的显示图片
mFinalView.DrawEntity(commandBuffer, mObjIDAndCoordinatePipelineRes.mStep1_CommonPipeline);
vkCmdEndRenderPass(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
throw std::runtime_error("failed to record command buffer!");
}
最后是渲染显示图片时的偏远着色器处理,具体如下:
#version 440
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(location = 0) out vec4 fragColor;
layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputColor;
layout (input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput inputPosition;
layout(std140, set = 1, binding = 0) buffer PickResult
{
vec4 pickResult;
};
void main()
{
vec4 postion = subpassLoad(inputPosition);
if(postion.w == pickResult.w)
{
//如果为鼠标覆盖的物体,这采用简单的变色处理
vec4 baseColor = subpassLoad(inputColor);
fragColor = vec4(baseColor.x * 0.7, baseColor.y * 0.7, min(1, baseColor.z * 0.7 + 0.3), 1);
}
else
fragColor = subpassLoad(inputColor);//不是鼠标覆盖的物体仍采用原有颜色
}
4、结束语
A. 以上过程的相关管道感兴趣的朋友可以自己补充完成。这篇文章中描述的是一个简单的鼠标触碰后变色过程,如果需要更好看的交互过程如触碰后物体显示描边,那么可以在计算管线和最终的渲染管线之间增加相应的描边渲染处理。
B. 与此同时,这个过程也用来实现类似于autocad的选择集函数(acedSSGet)及实体选择函数(acedEntGet),在后续完善后再写出相关的大致过程
运行结果
20230329-224315