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 <raylib.h>
#include <math.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 = 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;
}



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 <raylib.h>
#include <math.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 = 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;
}



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 <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, 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;
}


4.2 漫游摄像机

使用这种摄像机,程序运行时,我们可以通过键盘和鼠标来改变摄像机的位置和姿态:

滚动鼠标中键,可以改变摄像机的视角(拉近拉远)
按住鼠标中键同时拖动鼠标,可以改变摄像机目标点的位置(摄像机朝向)
同时按住鼠标中键和Alt键同时拖动鼠标,可以改变摄像机的姿态(摄像机正上方朝向)
首先,我们在创建摄像机之后,将其类型设置为CAMERA_FREE;

//设置摄像机为环绕型摄像机
SetCameraMode(camera, CAMERA_FREE);
注:类似4.1图。

4.3 第一人称摄像机

使用这种摄像机,程序运行时,我们可以用类似第一人称射击游戏里的操作方式,通过键盘和鼠标来改变摄像机的位置和姿态:

按AWSD键可以移动摄像机
移动鼠标可以转动摄像机视角
首先,我们在创建摄像机之后,将其类型设置为CAMERA_FIRST_PERSON;

//设置摄像机为环绕型摄像机
SetCameraMode(camera, CAMERA_FIRST_PERSON);

注:几乎不见图形,须按Esc退出。