写在前面:
如果融合软件是运行在window系统,一定要选择d3d11做绘制低层api,不要使用OpenGL!不要使用OpenGL!不要使用OpenGL。
网格是投影融合矫正的基础,投影融合的矫正部分的网格要求并不高,普通的坐标变形,极坐标表示,加一些逻辑操作 90%的投影项目都已经可以胜任了。网格做为数字几何的重要组成部分,其实是非常深奥的 比如 细分,简化,正则化 ,delauny ....幸好融合软件用的网格都是基本的,所见即所得
网格篇难点:
1:融合带处理:
融合带补偿曲线,这个可以是指数函数来定义,还可以是贝塞尔曲线来替代。
市场上的融合带大部分都是曲线+Gamm矫正,其实单靠Gamm矫正是不够。
我觉得 目视粗调(投影机自身的调节) + 摄像头(机器视觉算法)细调,是最佳的方案。这
部分后面会讲到。
处理过程:
原始投影叠加:
过调节函数后:
经 Gamma 校正公式:f(x) = x^(1/g) (x归一化处理 g区间【1.8-2。2】)
最终:
还有融合带的调整必须在片段着色器里完成,下面给出伪代码:
struct HyperParameters{
float p;
float a;
float gamma;
float can;
float4 fusion_left_right_up_down;
float4 bound_left_right_up_down;
};
cbuffer cbHyperParameters : register(b1){
HyperParameters hyperPar;
};
float edgeBlend(float p,float a,float t){
if(t>=0&&t<=0.5){
return a*(pow(2*t,p));
}else{
return 1-(1-a)*(pow(2*(1-t),p));
}
} ;
float4 rh(float2 Tex,float4 color_){
float4 finalColor = color_;
if(Tex.x<hyperPar.fusion_left_right_up_down.x){
float t =(hyperPar.fusion_left_right_up_down.x-Tex.x)/(hyperPar.fusion_left_right_up_down.x-hyperPar.bound_left_right_up_down.x);
float bs=edgeBlend(,t);
finalColor.xyz*=(1.0-bs);
}
if(Tex.x>hyperPar.fusion_left_right_up_down.y){
float t =(Tex.x-hyperPar.fusion_left_right_up_down.y)/(hyperPar.bound_left_right_up_down.y-hyperPar.fusion_left_right_up_down.y);
float bs=edgeBlend(hyperPar.fusion_bessel_key_right,t);
finalColor.xyz*=(1.0-bs);
}
if(Tex.y<hyperPar.fusion_left_right_up_down.z){
float t =(hyperPar.fusion_left_right_up_down.z-Tex.y)/(hyperPar.fusion_left_right_up_down.z-hyperPar.bound_left_right_up_down.z);
float bs=edgeBlend(hyperPar.fusion_bessel_key_up,t);
finalColor.xyz*=(1.0-bs);
}
if(Tex.y>hyperPar.fusion_left_right_up_down.w){
float t =(Tex.y-hyperPar.fusion_left_right_up_down.w)/(hyperPar.bound_left_right_up_down.w-hyperPar.fusion_left_right_up_down.w);
float bs=edgeBlend(hyperPar.fusion_bessel_key_down,t);
finalColor.xyz*=(1.0-bs);
}
return finalColor;
}
2:反向精确定位:
其实融合软件在面对沙盘 拍拍墙 mapping 的时候,都需要做内容和投影的位置对位,在网格极度变形的时候,获取网格上任何一点的纹理坐标是困难的。对图形学敏感的朋友应该会想起插值,进而想到 重心坐标:参考 重心坐标(Barycentric coordinates) - 知乎 (zhihu.com)
很多技术文档原理和推导已经给出,我这里直接贴伪代码了:
bool BarycentricCoordinates(DirectX::XMFLOAT3 orig_f, DirectX::XMFLOAT3 dir_f, DirectX::XMFLOAT3 v0_f, DirectX::XMFLOAT3 v1_f, DirectX::XMFLOAT3 v2_f,DirectX::XMFLOAT2 uv0_f, DirectX::XMFLOAT2 uv1_f, DirectX::XMFLOAT2 uv2_f, DirectX::XMFLOAT2 &uvdst) {
DirectX::XMVECTOR dir = XMLoadFloat3(&dir_f);
DirectX::XMVECTOR orig = XMLoadFloat3(&orig_f);
DirectX::XMVECTOR v0=XMLoadFloat3(&v0_f);
DirectX::XMVECTOR v1 = XMLoadFloat3(&v1_f);
DirectX::XMVECTOR v2 = XMLoadFloat3(&v2_f);
DirectX::XMVECTOR E1 = DirectX::XMVectorSubtract(v1 , v0);
DirectX::XMVECTOR E2 = DirectX::XMVectorSubtract(v2, v0);
DirectX::XMVECTOR P = DirectX::XMVector3Cross(dir, E2);
float det = 0.0f;
DirectX::XMStoreFloat(&det, DirectX::XMVector3Dot(E1, P));
DirectX::XMVECTOR T;
if (det > 0){
T = DirectX::XMVectorSubtract(orig , v0);
}
else{
T = DirectX::XMVectorSubtract(v0 , orig);
det = -det;
}
if (det < 0.0001f) {
return false;
}
float u_t = 0.0f;
DirectX::XMStoreFloat(&u_t, DirectX::XMVector3Dot(T, P));
float u = u_t;
if (u < 0.0f || u > det)
{
return false;
}
DirectX::XMVECTOR Q = DirectX::XMVector3Cross(T, E1);
float v_t = 0.0f;
DirectX::XMStoreFloat(&v_t, DirectX::XMVector3Dot(dir, Q));
float v = v_t;
if (v < 0.0f || u + v > det){
return false;
}
float t_t = 0.0f;
DirectX::XMStoreFloat(&t_t, DirectX::XMVector3Dot(E2, Q));
float t = t_t;
float t_f_invdet = 1.0f / det;
t *= t_f_invdet;
DirectX::XMFLOAT3 t_direction = { t * this->direction.x,t * this->direction.y,t * this->direction.z };
hit.x = u;
hit.y = v;
uvdst.x =uv1_f.x* hit.x + uv2_f.x * hit.y +=uv0_f.x * (1.0f- hit.x- hit.y);
uvdst.y = uv1_f.y * hit.x +uv2_f.y * hit.y +uv0_f.y * (1.0f - hit.x - hit.y);
return true;
}