前文我们谈论了DirectX的坐标系转换,把3D坐标转换为2D坐标系,这篇文章我们将谈论vulkan的坐标系转换,当然如果你有OpenGL的坐标系转换的基础,本文对于你会很简单,代码参考《vulkan应用详解》这边书的,我们先看一下坐标系变换关键函数:
void MyVulkanManager::initMatrix()
{
MatrixState3D::setCamera(0, 0, 200, 0, 0, 0, 0, 1, 0);//初始化摄像机
MatrixState3D::setInitStack();//初始化基本变换矩阵
float ratio = (float)screenWidth / (float)screenHeight;//求屏幕长宽比
//MatrixState3D::setProjectFrustum(-ratio, ratio, -1, 1, 1.5f, 1000);//设置投影参数
MatrixState3D::setProjectFrustum(0, (float)screenWidth, (float)screenHeight, 0, 1.5f, 1000);//设置投影参数
}
注释是原作者的代码,后面的那行是我修改的
第一行初始化摄像机,这个只操作了Z轴
第二行,初始化基本变换
void MatrixState3D::setInitStack()
{
Matrix::setIdentityM(currMatrix, 0);
//初始化从OpenGL标准设备空间到Vulkan设备空间变换的矩阵
//OpenGL标准设备空间XYZ三个轴范围都是从-1.0~+1.0
//Vulkan设备空间XYZ三个轴范围分别是 -1.0~+1.0、+1.0~-1.0、0.0~+1.0
//变换时本质上采用的是缩放加平移矩阵
//X轴不变Y轴置反Z轴缩放0.5
//缩放后Z轴正向平移0.5
vulkanClipMatrix[0] = 1.0f;
vulkanClipMatrix[1] = 0.0f;
vulkanClipMatrix[2] = 0.0f;
vulkanClipMatrix[3] = 0.0f;
vulkanClipMatrix[4] = 0.0f;
vulkanClipMatrix[5] = -1.0f;
vulkanClipMatrix[6] = 0.0f;
vulkanClipMatrix[7] = 0.0f;
vulkanClipMatrix[8] = 0.0f;
vulkanClipMatrix[9] = 0.0f;
vulkanClipMatrix[10] = 0.5f;
vulkanClipMatrix[11] = 0.0f;
vulkanClipMatrix[12] = 0.0f;
vulkanClipMatrix[13] = 0.0f;
vulkanClipMatrix[14] = 0.5f;
vulkanClipMatrix[15] = 1.0f;
}
关键的地方:
vulkanClipMatrix[5] = -1.0f;是把坐标系的Y轴本来是向上的,现在带负号就是变成向下
vulkanClipMatrix[10] = 0.5f;是变成原理的0.5,也就是缩小一半,这样Z轴范围就从0.0~+1.0变成0.0~+0.5了,
上面的Z轴还不是中心对称,我们现在要平移0.5f,于是:
vulkanClipMatrix[14] = 0.5f;就是正向平移0.5,现在Z轴就是-0.5f ~ 0.5f,x坐标轴没变,y轴反转了,z轴的范围变成对称的-0.5f ~ 0.5f
vulkanClipMatrix[15] = 1.0f;使得Z轴范围变成[-1.0f,1,0f],这就跟DirectX的范围一样了
如果不看Z轴,那么现在xy组成的坐标轴就是与2D坐标系一样了。不过我们还需让范围扩大到窗口的宽高:
MatrixState3D::setProjectFrustum(0, (float)screenWidth, (float)screenHeight, 0, 1.5f, 1000);
实际调用:
void MatrixState3D::setProjectFrustum
(
float left,
float right,
float bottom,
float top,
float near,
float far
)
{
//Matrix::frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
Matrix::orthoM(mProjMatrix,0,left,right,bottom,top,near,far);
}
注释的是原作者的代码,后面我要使用orthoM扩展坐标系,这个函数跟DirectX基本差不多,
static void orthoM(float * m, int mOffset, float left, float right, float bottom, float top, float near1, float far1)
{
assert(left != right);
assert(bottom != top);
assert(near1 != far1);
float r_width = 1.0f / (right - left);
float r_height = 1.0f / (top - bottom);
float r_depth = 1.0f / (far1 - near1);
float x = 2.0f * (r_width);
float y = 2.0f * (r_height);
float z = -2.0f * (r_depth);
float tx = -(right + left) * r_width;
float ty = -(top + bottom) * r_height;
float tz = -(far1 + near1) * r_depth;
m[mOffset + 0] = x;
m[mOffset + 5] = y;
m[mOffset + 10] = z;
m[mOffset + 12] = tx;
m[mOffset + 13] = ty;
m[mOffset + 14] = tz;
m[mOffset + 15] = 1.0f;
m[mOffset + 1] = 0.0f;
m[mOffset + 2] = 0.0f;
m[mOffset + 3] = 0.0f;
m[mOffset + 4] = 0.0f;
m[mOffset + 6] = 0.0f;
m[mOffset + 7] = 0.0f;
m[mOffset + 8] = 0.0f;
m[mOffset + 9] = 0.0f;
m[mOffset + 11] = 0.0f;
}
关键函数orthoM,关键代码:
float r_width = 1.0f / (right - left);
float r_height = 1.0f / (top - bottom);
float r_depth = 1.0f / (far1 - near1);
float x = 2.0f * (r_width);
float y = 2.0f * (r_height);
float z = -2.0f * (r_depth);
这六句的意思就是把X轴[-1.0,1.0]总共长度2.0变成2.0f / (right - left)也就是扩展到窗口宽度,y轴同样的道理扩展到窗口高度,Z轴带个负数这个是右手坐标系变为左手坐标系,因为DirectX是左手坐标系,这样摄像机镜头就是对着里面而不是对着工程师了,习惯都是左手坐标系。
现在坐标系都变换完了,那么怎么使用坐标呢,哈哈,那跟在2D中使用坐标点一样了
float* TriangleData::vdata;//数据数组首地址指针
int TriangleData::dataByteCount;//数据所占总字节数量
int TriangleData::vCount;//顶点数量
void TriangleData::genVertexData() {//顶点数据生成方法
vCount = 5;
dataByteCount = vCount * 6 * sizeof(float);
vdata = new float[vCount * 6]
{
1,720,0, 1, 0, 0, /*颜色*/
700, 720, 0, 1, 0, 0,
1, 721, 0, 0,1, 0,
700, 721, 0, 0, 1, 0,
100, 200, 0, 1, 1, 0
};
}
经过前面坐标系变换,现在2D坐标点,其原点在左上角,上面代码坐标点1,2,3,4行就是画两条平行线,相聚一个像素,看看效果:
两条相邻平行线,精度能精确到一个像素,完美解决DirectX取图不精的问题,vulkan是替代directx的不二之选,而且还支持跨平台,更重要的是你可以在QT里面调用QVulkanWindow类去使用它,QT很早就已经支持Vulkan了,这对应QT画图再合适不过了