raylib绘图库--瞿华
2022.7.1
知乎 1、raylib绘图库简介

知乎 2、raylib 2d动画/游戏教程(1)动画基本原理

知乎 3、raylib 2d动画/游戏教程(2)坐标系与颜色系统

知乎 4、raylib 2d动画/游戏教程(3)图像文件与图层

知乎 5、raylib 2d动画/游戏教程(4)raylib-drawing库

知乎 6、raylib 2d动画/游戏教程(5)键盘与鼠标输入

知乎 7、raylib 2d动画/游戏教程(6)游戏手柄输入

知乎 8、raylib 2d动画/游戏教程(7)音乐和音效

知乎 9、raylib绘制中文内容

知乎 10、使用raygui绘制控件

知乎 11、raylib 3d绘图基础教程(1)坐标系和摄像机

知乎 12、raylib 3d绘图基础教程(2)网格(Mesh)

知乎 13、raylib 3d绘图基础教程(3)几何变换

知乎 14、raylib 3d绘图基础教程(4)3d模型的载入与绘制

raylib 3d绘图基础教程(3)几何变换

1 前言
在绘制3维图形场景时,我们经常需要改变三维模型的大小(缩放)、姿态(旋转或者翻转)和位置(位移)等。这些操作都属于几何变换(Geometric Transformation),其实质就是将原三维模型上的每一个点{x,y,z}变换到新的位置{x',y',z'}上,也就是说,对原模型上每一个点的坐标{x,y,z}进行计算,得到它的新位置坐标{x',y',z'}。

因为在计算机图形处理中,3维坐标系常见的几何变换(坐标计算)都可以用四元组{x,y,y,1}乘以一个4x4的矩阵得到{x',y',z',c}来完成,所以各种3d绘图库一般都会用一个4x4矩阵来表征要对三维模型进行的变换,称为变换矩阵(transformation matrix)。raylib也一样,它用Matrix结构来表征几何变换,其在raylib.h中的定义如下:

//Matrix, 4x4 components, column major, OpenGL style, right handed
typedef struct Matrix {
    float m0, m4, m8, m12;//Matrix first row (4 components)
    float m1, m5, m9, m13;//Matrix second row (4 components)
    float m2, m6, m10, m14;//Matrix third row (4 components)
    float m3, m7, m11, m15;//Matrix fourth row (4 components)
} Matrix;
在实际编程时我们很少手工填写变换矩阵中的各个元素,而是使用raymath.h中提供的Matrix系列变换矩阵生成函数来构造变换矩阵。

2 变换矩阵生成函数
raylib 4.0的raymath.h头文件提供了下列变换函数:
函数作用
MatrixRotate(axis,angle)绕指定的坐标轴axis旋转angle度
MatrixRotateX(angle)绕X轴旋转angle度
MatrixRotateY(angle)绕Y轴旋转angle度
MatrixRotateZ(angle)绕Z轴旋转angle度
MatrixRotateXYZ(angles)依次绕X、Y、Z轴旋转angles.x,angles.y和angles.z度
MatrixRotateZYX(angles)依次绕Z、Y、X轴旋转angles.x,angles.y和angles.z度
MatrixScale(x,y,z)(缩放)分别在X、Y、Z方向上缩放x,y,z倍
MatrixTranslate(x,y,z)(平移)将坐标系原点移动到原坐标系的(x,y,z)点处
MatrixIdentity()模型保持不变(单位矩阵)
2.1 旋转 在raylib 3d绘图基础教程(2):网格(Mesh)的示例中,我们已经演示了网格模型绕y轴旋转的效果,这里不再赘述。 模型绕y轴旋转示例 2.2 缩放 在本例中,我们使用MatrixScale函数创建缩放变换矩阵,缩放因子从0.1到3来回变化: //创建缩放变换矩阵 Matrix transform=MatrixScale(scale,scale,scale); 完整程序如下: #include <raylib.h> #include <raymath.h> int main(void) { //初始化 const int screenWidth = 640; const int screenHeight = 480; //启用反锯齿 SetConfigFlags(FLAG_MSAA_4X_HINT); //初始化窗口 InitWindow(screenWidth, screenHeight, "Sample"); //初始化摄像机 Camera3D camera = { 0 }; camera.position = (Vector3){ 40.0f, 20.0f, 0.0f };//相机所在位置{x,y,z} camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };//相机朝向位置{x,y,z} camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };//相机正上方朝向矢量 camera.fovy = 40;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //创建几何体 Mesh mesh = GenMeshCube(5,5,15); //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //------ //缩放因子 double scale = 0.1; double step = 0.1; int colorHue = 0; //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { //每次循环更新一帧 double time = GetTime(); scale+=step; if (scale>2.9)step=-0.1; else if (scale<0.2)step=0.1; colorHue++; colorHue%=360; //创建贴图 Image checked = GenImageChecked(2,2,1,1,ColorFromHSV(colorHue,1,1),LIGHTGRAY); Texture2D texture = LoadTextureFromImage(checked); UnloadImage(checked); //基于贴图创建材质 Material material=LoadMaterialDefault(); material.maps[MATERIAL_MAP_DIFFUSE].texture = texture; //创建缩放变换矩阵 Matrix transform=MatrixScale(scale,scale,scale); BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制网格 DrawMesh(mesh,material,transform); EndMode3D(); EndDrawing(); //释放材质和贴图 UnloadMaterial(material); UnloadTexture(texture); } //释放网格 UnloadMesh(mesh); //关闭窗口 CloseWindow(); return 0; } 2.3 平移 在本例中,我们使用MatrixTranslate函数创建平移变换矩阵,让模型沿y轴移动: //创建平移变换矩阵 Matrix transform= MatrixTranslate(0,y,0); 完整程序如下: #include #include int main(void) { //初始化 const int screenWidth = 640; const int screenHeight = 480; //启用反锯齿 SetConfigFlags(FLAG_MSAA_4X_HINT); //初始化窗口 InitWindow(screenWidth, screenHeight, "Sample"); //初始化摄像机 Camera3D camera = { 0 }; camera.position = (Vector3){ 40.0f, 20.0f, 0.0f };//相机所在位置{x,y,z} camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };//相机朝向位置{x,y,z} camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };//相机正上方朝向矢量 camera.fovy = 40;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //创建几何体 Mesh mesh = GenMeshCube(5,5,15); //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //------ //平移y坐标 double y = -10; double step = 0.1; int colorHue = 0; //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { //每次循环更新一帧 double time = GetTime(); y+=step; if (y>9.9)step=-0.1; else if (y<-9.9)step=0.1; colorHue++; colorHue%=360; //创建贴图 Image checked = GenImageChecked(2,2,1,1,ColorFromHSV(colorHue,1,1),LIGHTGRAY); Texture2D texture = LoadTextureFromImage(checked); UnloadImage(checked); //基于贴图创建材质 Material material=LoadMaterialDefault(); material.maps[MATERIAL_MAP_DIFFUSE].texture = texture; //创建平移变换矩阵 Matrix transform= MatrixTranslate(0,y,0); BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制网格 DrawMesh(mesh,material,transform); EndMode3D(); EndDrawing(); //释放材质和贴图 UnloadMaterial(material); UnloadTexture(texture); } //释放网格 UnloadMesh(mesh); //关闭窗口 CloseWindow(); return 0; } 3 复合变换 我们可以通过矩阵乘法,将变换矩阵相乘得到新的复合变换矩阵,来实现复合变换。 3.1 边旋转边缩放 在本例中,我们将缩放矩阵和旋转矩阵相乘,得到同时缩放和旋转的效果: //创建缩放变换矩阵 Matrix scaleTransform=MatrixScale(scale,scale,scale); //创建旋转变换矩阵 Matrix rotateTransform=MatrixRotateY(angle); //两个矩阵相乘,得到复合变换矩阵 Matrix complexTransform = MatrixMultiply(scaleTransform,rotateTransform); 完整程序如下: #include <raylib.h> #include <raymath.h> int main(void) { //初始化 const int screenWidth = 640; const int screenHeight = 480; //启用反锯齿 SetConfigFlags(FLAG_MSAA_4X_HINT); //初始化窗口 InitWindow(screenWidth, screenHeight, "Sample"); //初始化摄像机 Camera3D camera = { 0 }; camera.position = (Vector3){ 40.0f, 20.0f, 0.0f };//相机所在位置{x,y,z} camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };//相机朝向位置{x,y,z} camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };//相机正上方朝向矢量 camera.fovy = 40;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //创建几何体 Mesh mesh = GenMeshCube(5,5,15); //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //------ //缩放因子 double scale = 0.1; double step = 0.01; double angle; int colorHue = 0; //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { //每次循环更新一帧 double time = GetTime(); angle = time*0.3; scale+=step; if (scale>2.9)step=-0.01; else if (scale<0.2)step=0.01; colorHue++; colorHue%=360; //创建贴图 Image checked = GenImageChecked(2,2,1,1,ColorFromHSV(colorHue,1,1),LIGHTGRAY); Texture2D texture = LoadTextureFromImage(checked); UnloadImage(checked); //基于贴图创建材质 Material material=LoadMaterialDefault(); material.maps[MATERIAL_MAP_DIFFUSE].texture = texture; //创建缩放变换矩阵 Matrix scaleTransform=MatrixScale(scale,scale,scale); //创建旋转变换矩阵 Matrix rotateTransform=MatrixRotateY(angle); //两个矩阵相乘,得到复合变换矩阵 Matrix complexTransform = MatrixMultiply(scaleTransform,rotateTransform); BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制网格 DrawMesh(mesh,material,complexTransform); EndMode3D(); EndDrawing(); //释放材质和贴图 UnloadMaterial(material); UnloadTexture(texture); } //释放网格 UnloadMesh(mesh); //关闭窗口 CloseWindow(); return 0; } 3.2 绕y轴转圈 在前面的例子中,模型本身的中心位于原点,因此绕y旋转的效果就是模型绕y轴旋转。如果我们希望模型以10为半径绕y轴转圈,该怎么办呢? 可以这样做:先通过平移,把原来的y轴移动到新坐标系中距离原点10的地方;然后让模型(中心在新坐标的原点上)绕原来的y轴旋转。 //移动原坐标系的原点到{-10,0,0},即原来的y轴通过新坐标系的{10,0,0} Matrix translateTransform=MatrixTranslate(-10,0,0); //绕原来的y轴旋转,即在新坐标系下绕经过{10,0,0}的轴旋转 Matrix rotateTransform=MatrixRotateY(angle); //两个矩阵相乘,得到复合变换矩阵 Matrix complexTransform =MatrixMultiply(translateTransform,rotateTransform); 绕y轴(图中中央黑线)转圈 完整程序如下: #include <raylib.h> #include <raymath.h> int main(void) { //初始化 const int screenWidth = 640; const int screenHeight = 480; //启用反锯齿 SetConfigFlags(FLAG_MSAA_4X_HINT); //初始化窗口 InitWindow(screenWidth, screenHeight, "Sample"); //初始化摄像机 Camera3D camera = { 0 }; camera.position = (Vector3){ 5.0f, 50.0f, 7.0f };//相机所在位置{x,y,z} camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };//相机朝向位置{x,y,z} camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };//相机正上方朝向矢量 camera.fovy = 40;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //创建几何体 Mesh mesh = GenMeshCube(5,5,15); //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //------ double angle; int colorHue = 0; //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { //每次循环更新一帧 double time = GetTime(); angle = time*0.3; colorHue++; colorHue%=360; //创建贴图 Image checked = GenImageChecked(2,2,1,1,ColorFromHSV(colorHue,1,1),LIGHTGRAY); Texture2D texture = LoadTextureFromImage(checked); UnloadImage(checked); //基于贴图创建材质 Material material=LoadMaterialDefault(); material.maps[MATERIAL_MAP_DIFFUSE].texture = texture; //移动原坐标系的原点到{-10,0,0},即原来的y轴通过新坐标系的{10,0,0} Matrix translateTransform=MatrixTranslate(-10,0,0); //绕原来的y轴旋转,即在新坐标系下绕经过{10,0,0}的轴旋转 Matrix rotateTransform=MatrixRotateY(angle); //两个矩阵相乘,得到复合变换矩阵 Matrix complexTransform =MatrixMultiply(translateTransform,rotateTransform); BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); DrawGrid(10,5); DrawLine3D((Vector3){0,100,0},(Vector3){0,-100,0},BLACK); //绘制网格 DrawMesh(mesh,material,complexTransform); EndMode3D(); EndDrawing(); //释放材质和贴图 UnloadMaterial(material); UnloadTexture(texture); } //释放网格 UnloadMesh(mesh); //关闭窗口 CloseWindow(); return 0; }