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绘图基础教程(1)坐标系和摄像机 https://zhuanlan.zhihu.com/p/458497389 本文介绍raylib 3d绘图常用的两个概念:坐标系和摄像机 1 坐标系 raylib使用三维平面直角坐标系来定位空间中的点。在空间中,三个相互垂直的坐标轴可以有两种关系:左手坐标系和右手坐标系。和OpenGL一样,raylib采用右手坐标系:右手四指并拢摊开指向x轴,四指弯曲指向y轴,这时右手拇指指向的就是z轴。通常,我们采用xz平面作为水平面,y轴作为垂直方向。 2 投影和摄像机 要把3维的物体显示到2维的屏幕上,必须经过一个从3维到2维的转换过程,这个转换过程就是投影。用数学的语言说,投影就是将3维坐标系中的点转换到2维坐标系中的一种几何变换。计算机图形处理中常见的投影变换有两种形式,正交变换和投影变换。正交变换多用于CAD作图和建模软件;游戏和动画中常用的是投影变换,因为后者更接近于我们常见的视觉体验。 OpenGL中的投影涉及到一些数学计算,使用起来不太直观;对此,raylib引入了摄像机的概念,其使用更符合初学者的一般直觉。我们可以直观的把投影变换的过程,想象成我们用摄像机对三维场景进行拍摄的过程。很显然,要确定摄像机此时的拍摄状态,我们需要知道这么几个参数: 摄像头在哪儿(位置) 摄像头在朝哪儿拍(朝向) 摄像头是在横拍还是竖拍(姿态) 摄像头视野大小(焦距或者视宽) 我们只需要提供摄像头的这几个参数,就可以用它来对三维场景进行拍摄(投影变换了)。因此,我们在raylib的程序中经常会用到类似下面这段代码: 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 = 70.0f;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 position参数很容易理解,它是相机所在的位置; target参数是相机朝向的目标点位置。由position点到该点的矢量决定着相机的朝向。因为这个参数只决定朝向,所以取由position出发,朝向相机实际朝向的射线上的任意一点作为target参数,效果都是一样的。 up参数是一个矢量--相机正上方的朝向,它决定了相机的姿态。比如,我们用手机竖拍时,其相机上方是朝上的;横拍时,相机上方朝左或者朝右。按理说,这个矢量应该是和前面提到的相机的朝向矢量n垂直的。但是在实际使用时,要构造一个矢量n垂直的方向比较麻烦,所以为了使用方便,raylib并不要求up参数和n必须垂直,只要它和相机实际的正上方朝向、矢量n位于同一个平面内即可,raylib可以自动通过两次叉积计算出实际的正上方朝向。一般我们习惯镜头向上拍摄,并且在布置3维场景时,以xz平面为水平面,y轴为垂直方向。这样的话,up参数设为{0,1,0}就可以了。 fovy参数决定了相机的视野大小(作用相当于相机的焦距),数字越大摄入的视野越宽(大fovy值相当于使用广角镜头拍摄);数字越小摄入的视野越小(小fovy值相当于使用长焦镜头拍摄)。 3 摄像机参数效果演示 下面两个程序演示了通过up参数设置,让相机正常拍摄,和相机旋转90度拍摄的不同效果: 3.1 正常拍摄 本例是一个镜头正向朝上正常拍摄的示例:#include 3.2 旋转90度拍摄 在本例中我们需要把相机旋转90度拍摄。其up矢量是通过y轴正向矢量和相机朝向矢量n的叉积运算计算得出的,如下例所示。注意: 矢量计算相关函数在raymath.h头文件中; 每次相机位置变化,需要重新计算up矢量 Vector3 up = Vector3CrossProduct(Vector3Subtract(camera.target,camera.position),(Vector3){ 0.0f, 1.0f, 0.0f }); camera.up = up;//相机正上方朝向矢量 完整程序如下:#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 = 70.0f;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //----- int angle=0;//多边形旋转角度 //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { double time = GetTime(); //每次循环更新一帧 //摄像机围绕y轴转动 double cameraTime = time*0.3; camera.position.x = (float)cos(cameraTime)*40.0f; camera.position.z = (float)sin(cameraTime)*40.0f; BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制水平面网格 DrawGrid(100,5); //绘制Y轴 DrawLine3D((Vector3){0,100,0},(Vector3){0,-100,0},BLACK); //绘制立方体 DrawCube((Vector3){0,0,0},10,10,10,VIOLET); DrawCubeWires((Vector3){0,0,0},10,10,10,BLACK); //绘制球体 DrawSphere((Vector3){0,-40,0},10,RED); DrawSphereWires((Vector3){0,-40,0},10,10,10,BLACK); EndMode3D(); EndDrawing(); } //关闭窗口 CloseWindow(); return 0; } #include 3.3 广角 在3.1节代码的基础上,将相机的视野宽度改设为120,模拟广角镜头拍摄效果: camera.fovy = 120.0f;//相机视野宽度 3.4 长焦 在3.1节代码的基础上,将相机的视野宽度改设为20,模拟长焦镜头拍摄效果: camera.fovy = 20.0f;//相机视野宽度 4 预置的交互摄像机 在raylib中,还提供了几种预置的摄像机,可以实现常见的用户交互。 4.1 环绕摄像机 这种摄像机会不断绕目标点旋转,从而实现定点环绕的拍摄效果。 首先,我们在创建摄像机之后,将其类型设置为CAMERA_ORBITAL; //设置摄像机为环绕型摄像机 SetCameraMode(camera, CAMERA_ORBITAL); 然后,在每一帧绘制前,计算和更新摄像机的位置 //自动计算和更新摄像机位置 UpdateCamera(&camera); 完整程序如下:#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 = 70.0f;//相机视野宽度 camera.projection = CAMERA_PERSPECTIVE;//采用透视投影 //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); //----- int angle=0;//多边形旋转角度 //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { double time = GetTime(); //每次循环更新一帧 //摄像机围绕y轴转动 double cameraTime = time*0.3; camera.position.x = (float)cos(cameraTime)*40.0f; camera.position.z = (float)sin(cameraTime)*40.0f; Vector3 up = Vector3CrossProduct(Vector3Subtract(camera.target,camera.position),(Vector3){ 0.0f, 1.0f, 0.0f }); camera.up = up;//相机正上方朝向矢量 BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制水平面网格 DrawGrid(100,5); //绘制Y轴 DrawLine3D((Vector3){0,100,0},(Vector3){0,-100,0},BLACK); //绘制立方体 DrawCube((Vector3){0,0,0},10,10,10,VIOLET); DrawCubeWires((Vector3){0,0,0},10,10,10,BLACK); //绘制球体 DrawSphere((Vector3){0,-40,0},10,RED); DrawSphereWires((Vector3){0,-40,0},10,10,10,BLACK); EndMode3D(); EndDrawing(); } //关闭窗口 CloseWindow(); return 0; } #include 4.2 漫游摄像机 使用这种摄像机,程序运行时,我们可以通过键盘和鼠标来改变摄像机的位置和姿态: 滚动鼠标中键,可以改变摄像机的视角(拉近拉远) 按住鼠标中键同时拖动鼠标,可以改变摄像机目标点的位置(摄像机朝向) 同时按住鼠标中键和Alt键同时拖动鼠标,可以改变摄像机的姿态(摄像机正上方朝向) 首先,我们在创建摄像机之后,将其类型设置为CAMERA_FREE; //设置摄像机为环绕型摄像机 SetCameraMode(camera, CAMERA_FREE); 注:类似4.1图。 4.3 第一人称摄像机 使用这种摄像机,程序运行时,我们可以用类似第一人称射击游戏里的操作方式,通过键盘和鼠标来改变摄像机的位置和姿态: 按AWSD键可以移动摄像机 移动鼠标可以转动摄像机视角 首先,我们在创建摄像机之后,将其类型设置为CAMERA_FIRST_PERSON; //设置摄像机为环绕型摄像机 SetCameraMode(camera, CAMERA_FIRST_PERSON); 注:几乎不见图形,须按Esc退出。#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, 30.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;//采用透视投影 //设置摄像机为环绕型摄像机 SetCameraMode(camera, CAMERA_ORBITAL); //创建几何体 Mesh mesh = GenMeshCube(5,5,15); //设置动画帧率(刷新率,fps)为30帧/秒 SetTargetFPS(30); int colorHue = 0; //----- //主游戏循环 while (!WindowShouldClose())//关闭窗口或者按ESC键时返回true { //自动计算和更新摄像机位置 UpdateCamera(&camera); //每次循环更新一帧 double time = GetTime(); 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; BeginDrawing(); ClearBackground(WHITE); //以摄像机视角绘制3d内容 BeginMode3D(camera); //绘制网格 DrawMesh(mesh,material,MatrixIdentity()); EndMode3D(); EndDrawing(); //释放材质和贴图 UnloadMaterial(material); UnloadTexture(texture); } //释放网格 UnloadMesh(mesh); //关闭窗口 CloseWindow(); return 0; }