本章节,我们将要创建一个简单的创建,其中包括地形,天空盒以及跟随摄像机三种对象类。

首先我们使用3ds max创建一个地形:

第一步,在3ds max中创建一个平面,长度和宽度都是10000,长宽分段都是100。这样,我们就会创建一个拥有10000个正四边形组成的大平面(大正四边形)了。注意,每个正四边形的边长也是100。这跟我们在DirectX中使用灵活顶点构造一个超大四边形,其实是一个原理。很显然,使用3ds max制作要比DirectX简单容易多了。虽然我们在3ds max中看到整个大的平面是由10000个正四边形构成的,但是在DirectX中绘制这个平面的时候,每个四边形其实还是由两个三角形组成的。游戏引擎中最基本的图元还是三角形。需要注意的是,我们要将平面的中心点移动到世界坐标系的原点,具体操作就是选中平面,按W键(移动工具)后,将下面的x/y/z值改成0即可。

为了方便查看平面的样子,我们可以滚动鼠标中间的滚轮,来放大/缩小视图,这样可以更大范围的看到整个平面。同时也可以按下鼠标中间的滚轮拖动,来移动视图,使用更好的角度来观察整个平面。这是3ds max最基本的视图操作。

第二步,将我们提前准备好的一张草地的纹理贴图,直接拖拽到平面上。这个简便的操作实际就是给平面施加一个纹理材质,效果如下:

第三步,给草地做出高度,将平面转换成“可编辑多边形”,然后选择“面”的操作级别,这样,我们就能选中平面中的任意一个四边形了,按住Ctrl键可以帮我们加选四边形。

选中几个四边形,使用移动工具(按W键),将其沿Z轴向上拖动,使其选中的四边形向上移动。这里,我们选中的是世界坐标系原点附近的四边形。使用Ctrl键可以加选多个四边形面。

当然,我们也可以直接修改右下角的Z值,来明确向上移动的距离数值,这里我们设置为100。

第四步,使用”软选择”工具,给草地做出平滑的高度。

我们将平面的编辑模式改成”顶点”,而不是之前的”面”模式,这样,我们选中的则是平面的顶点,而不是之前的四边形。

在“顶点”选择模式的下方,由一个“软选择”工具,勾选“使用软选择”,然后设置“衰减”值,这个数值越大,影响的顶点范围就越广。此时,你点击平面上的一点,就会发下效果如下:

此时,我们同样使用移动工具(W键)来移动我们选中的顶点,就会发现附近的顶点会跟着一起移动,这样就会形成一个平滑的坡度。

其实,我们可以从顶点颜色能够推算出来,红色区域的顶点,移动距离大,橙色顶点小一些,绿色会再小一些,蓝色基本上影响不大了。在3ds max中使用颜色来区分影响程度的方式,在蒙皮的时候也会用到。当然,我们可以继续选择其他顶点,继续拖拽形成高低起伏的效果。

第五步,将我们的模型导出为X格式即可。

默认情况下,3ds max不支持导出X格式模型,我们需要借助Axe_free 插件来完成。这个插件是免费的,根据你的3ds max版本下载对应的Axe_free版本即可。解压Axe_free之后,直接放到 3ds max安装目录下的plugins下即可,非常简单。

接下来我们使用VS2019来创建新项目“D3D_10_Terrain”,首先创建公共头文件“main.h”文件,该文件中包含了我们以往用到的所有头文件,以及释放对象的宏,代码如下:

#pragma once
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <dinput.h>
#include <time.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <string>// 引入依赖的库文件
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"dinput8.lib")
#pragma comment(lib,"dxguid.lib")
#pragma comment(lib,"winmm.lib")#define WINDOW_LEFT       200             // 窗口位置
#define WINDOW_TOP      100             // 窗口位置
#define WINDOW_WIDTH    800             // 窗口宽度
#define WINDOW_HEIGHT   600             // 窗口高度
#define WINDOW_TITLE    L"D3D游戏开发"    // 窗口标题
#define CLASS_NAME      L"D3D游戏开发"    // 窗口类名// 自定义一个SAFE_RELEASE()宏, 释放指针对象
#ifndef SAFE_DELETE
#define SAFE_DELETE(p){ if (p) { delete (p); (p)=NULL; } }
#endif// 自定义一个SAFE_RELEASE()宏, 释放COM对象
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p){ if (p) { delete[] (p); (p)=NULL; } }
#endif// 自定义一个SAFE_DELETE_ARRAY()宏, 释放数组对象
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p){ if (p) { (p)->Release(); (p)=NULL; } }
#endif// 记录日志
void log(std::string txt);// wchar_t* 转 char*
char* convert(const wchar_t* wstr);

接下来,我们将对Input的输入做一个封装,这个类之前我们封装过,可以直接拿过来使用,也就是“InputClass.h”和“InputClass.cpp”两个文件。接下来,我们要创建一个可以自由移动的透视摄像机类。我们之前做的摄像机只能沿着世界坐标系X/Y/Z轴移动,并不能自由移动。这样的摄像机对于2D游戏已经够用了,但是3D游戏还是不行的。在3D游戏中,我们可以随意的移动和旋转摄像机来查看场景中的任何物体。这个自由移动的摄像机的实现,主要是一个局部坐标系的概念。我们为摄像机声明三个方向向量,向上的方向,向右的方向,以及向观察点的方向。摄像机移动或旋转的时候,就可以按照这三个方向来调整。由于这个三个方向向量是局部坐标系的概念,因此它是跟随摄像机的变动而变动的。也就是说,我们向前移动的时候,实际就是向着观察点的方向移动。当我们旋转之后,观察点也随之变动,那么我们继续向前移动的话,还是向着观察点的方向移动。也就是说,摄像机向前移动,永远都是相对于自己的前面而言的。我们创建“CameraClass.h”和“CameraClass.cpp”两个文件,首先是“CameraClass.h”文件内容:

#pragma once
#include "main.h"// 自由移动摄像机
class CameraClass {public:D3DXVECTOR3 _right;   // 右方向D3DXVECTOR3 _up;  // 上方向D3DXVECTOR3 _look;    // 观察方向D3DXVECTOR3 _pos;    // 位置public:// 构造方法CameraClass();// 有参构造函数CameraClass(float x, float y, float z);// 析构方法~CameraClass();// 前后移动void frontBackMove(float step);// 左右移动void leftRightMove(float step);// 上下移动void upDownMove(float step);// 围绕 _up 方向旋转,也就是左右旋转(摇头)void leftRightRotate(float angle);// 围绕 _right 方向旋转,也就是上下旋转(点头)void upDownRotate(float angle);// 围绕 _look 方向旋转,相当于侧头void sideRotate(float angle);// 计算取景变换矩阵void getViewMatrix(D3DXMATRIX* V);};

我们声明的三个方向向量和摄像机为位置信息,其中分别针对这三个方向定义了移动和旋转的函数。最后一个就是根据方向和位置来计算一个取景变换矩阵。“CameraClass.cpp”内容:

#include "CameraClass.h"// 自由移动摄像机: 构造方法
CameraClass::CameraClass() {_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);_look = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
}// 自由移动摄像机: 有参构造方法
CameraClass::CameraClass(float x, float y, float z) {_pos = D3DXVECTOR3(x, y, z);_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);_look = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
}// 自由移动摄像机: 析构方法
CameraClass::~CameraClass() {}// 自由移动摄像机: 前后移动
void CameraClass::frontBackMove(float step) {_pos += D3DXVECTOR3(_look.x, 0.0f, _look.z) * step;
}// 自由移动摄像机: 左右移动
void CameraClass::leftRightMove(float step) {_pos += D3DXVECTOR3(_right.x, 0.0f, _right.z) * step;
}// 自由移动摄像机: 上下移动
void CameraClass::upDownMove(float step) {_pos.y += step;
}// 自由移动摄像机: 围绕 _up 方向旋转,也就是左右旋转(摇头)
void CameraClass::leftRightRotate(float angle) {D3DXMATRIX M;D3DXMatrixRotationY(&M, angle);D3DXVec3TransformCoord(&_right, &_right, &M);D3DXVec3TransformCoord(&_look, &_look, &M);
}// 自由移动摄像机: 围绕 _right 方向旋转,也就是上下旋转(点头)
void CameraClass::upDownRotate(float angle) {D3DXMATRIX M;D3DXMatrixRotationAxis(&M, &_right, angle);D3DXVec3TransformCoord(&_up, &_up, &M);D3DXVec3TransformCoord(&_look, &_look, &M);
}// 自由移动摄像机: 围绕 _look 方向旋转,相当于侧头
void CameraClass::sideRotate(float angle) {D3DXMATRIX M;D3DXMatrixRotationAxis(&M, &_look, angle);D3DXVec3TransformCoord(&_right, &_right, &M);D3DXVec3TransformCoord(&_up, &_up, &M);
}// 自由移动摄像机: 计算取景变换矩阵
void CameraClass::getViewMatrix(D3DXMATRIX* V) {D3DXVec3Normalize(&_look, &_look);D3DXVec3Cross(&_up, &_look, &_right);D3DXVec3Normalize(&_up, &_up);D3DXVec3Cross(&_right, &_up, &_look);D3DXVec3Normalize(&_right, &_right);float x = -D3DXVec3Dot(&_right, &_pos);float y = -D3DXVec3Dot(&_up, &_pos);float z = -D3DXVec3Dot(&_look, &_pos);(*V)(0, 0) = _right.x; (*V)(0, 1) = _up.x; (*V)(0, 2) = _look.x; (*V)(0, 3) = 0.0f;(*V)(1, 0) = _right.y; (*V)(1, 1) = _up.y; (*V)(1, 2) = _look.y; (*V)(1, 3) = 0.0f;(*V)(2, 0) = _right.z; (*V)(2, 1) = _up.z; (*V)(2, 2) = _look.z; (*V)(2, 3) = 0.0f;(*V)(3, 0) = x;        (*V)(3, 1) = y;     (*V)(3, 2) = z;       (*V)(3, 3) = 1.0f;
}

上面的函数中,D3DXMatrixRotationAxis是生成一个围绕指定方向旋转指定角度的旋转矩阵。D3DXVec3TransformCoord就是将一个向量做旋转操作。最后就是计算取景变换矩阵,这个过程不需要大家掌握。接下来就是我们的地形类,首先地形是一个网格对象,因此我们需要先对网格对象进行封装,创建“MeshClass.h”和“MeshClass.cpp”两个文件,其实我们之前就已经对网格进行了封装,可以拿过来直接使用。有了基本模型的封装,那么我们的地形类就可以继承MeshClass,并在此基础上做一个调整了。我们上文中创建了一个地形模型,也就是一个大的四边形纹理,直接使用MeshClass加载进来不就得嘛。为什么还要创建一个地形类呢,答案就是地形高度的计算,也就是根据X/Z值,计算Y值。我们的地形四边形中,每个顶点记录了X/Y/Z的数值。但是,小四边形的边长是100,那么小四边形内部的X/Y/Z值怎么计算呢?X和Z是根据角色的移动计算出来的,那么我们就需要根据X/Z的值来计算Y值。计算Y值得的原理就是三角形的线性高度变化。

在上面的图例中,点P位于AB中间位置,如果AA´的距离是10,也就是说A的高度是10。那么点P的高度就应该是5,也就是P´的高度。这就是线性变化的原理。因为P是AB的一半,因此P点的高度就应该是A高度的一半。明白原理后,我们创建“TerrainClass.h”和“TerrainClass.cpp”两个文件,其中“TerrainClass.h”文件如下:

#pragma once
#include "MeshClass.h"// 地形类,主要是地形高度的计算
class TerrainClass : public MeshClass {public:// 模型顶点数组,里面包含x,y,z值D3DXVECTOR3* meshVertexes;// 模型中四边形的长度float interval = 0.0f;public:// 构造方法TerrainClass(LPDIRECT3DDEVICE9 _device, const wchar_t* _dir, const wchar_t* _file, float _interval);// 析构方法~TerrainClass();// 获取草地模型顶点数组void getMeshVertexes();// 获取草地模型顶点Y值float getMeshVertexY(float x, float z);// 获取地形高度值(Y值)float getTerrainHeight(float x, float z);};

我们提供了三个函数,getMeshVertexes函数用于获取模型中所有顶点的坐标信息。getMeshVertexY函数用于根据给定的X/Z值来获取所在小四边形的四个顶点的Y值。也就是说,我们需要确定给定的X/Z值的点,来确定该点在那个小四边形中,并获取这个小四边形的四个顶点坐标,还有进一步确认该点在这个小四边形中那个三角形中。我们知道这个小四边形是由两个三角形组成的。这个信息确认后,我们就可以借助上文的图例算法来计算了。接下来就是“TerrainClass.cpp”文件的内容:

#include "TerrainClass.h"// 地形类: 构造方法
TerrainClass::TerrainClass(LPDIRECT3DDEVICE9 _device, const wchar_t* _dir, const wchar_t* _file, float _interval) : MeshClass(_device, _dir, _file), interval(_interval) {// 获取草地模型顶点数组init();getMeshVertexes();
};// 地形类: 析构方法
TerrainClass::~TerrainClass() {delete[] meshVertexes;
};// 地形类: 获取草地模型顶点数组
void TerrainClass::getMeshVertexes() {// 顶点数量const int size = meshObj->GetNumVertices();meshVertexes = new D3DXVECTOR3[size];// 先克隆一份顶点数据LPD3DXMESH cloneMesh = 0;meshObj->CloneMeshFVF(meshObj->GetOptions(), D3DFVF_XYZ, D3DDevice, &cloneMesh);// 获取顶点缓冲数据LPDIRECT3DVERTEXBUFFER9 cloneMeshVertexBuffer;cloneMesh->GetVertexBuffer(&cloneMeshVertexBuffer);// 读取顶点缓存D3DXVECTOR3* pVertices;cloneMeshVertexBuffer->Lock(0, sizeof(meshVertexes), (void**)&pVertices, 0);for (DWORD i = 0; i < size; i++){meshVertexes[i].x = round(pVertices[i].x);meshVertexes[i].y = round(pVertices[i].y);meshVertexes[i].z = round(pVertices[i].z);}cloneMeshVertexBuffer->Unlock();// 是否克隆网格对象cloneMesh->Release();cloneMesh = NULL;
};// 地形类: 获取草地模型顶点Y值
float TerrainClass::getMeshVertexY(float x, float z) {int size = meshObj->GetNumVertices();for (int i = 0; i < size; i++) {D3DXVECTOR3 temp = meshVertexes[i];if (temp.x == x && temp.z == z) {return temp.y;}}return 0;
};// 地形类: 获取地形高度值(Y值)
float TerrainClass::getTerrainHeight(float x, float z) {// 根据坐标获取所在四边形的四个顶点float vx_min = 0, vx_max = 0, vz_min = 0, vz_max;if (x >= 0) {vx_min = (int)(x / 100) * 100;vx_max = vx_min + interval;}else {vx_max = (int)(x / 100) * 100;vx_min = vx_max - interval;}if (z >= 0) {vz_min = (int)(z / 100) * 100;vz_max = vz_min + interval;}else {vz_max = (int)(z / 100) * 100;vz_min = vz_max - interval;}// 四边形四条边平行于X/Z轴// 两个顶点的间距为 100// 下图可以理解为俯视角度// //  V1   V2//  *---*//  | / |//  *---*  //  V0   V3// // 获取四个顶点的Y值float V0 = getMeshVertexY(vx_min, vz_min);float V1 = getMeshVertexY(vx_min, vz_max);float V2 = getMeshVertexY(vx_max, vz_max);float V3 = getMeshVertexY(vx_max, vz_min);// 如果四个顶点的高度是一样的,就直接返回任意一个高度即可//if (V0 == V1 && V1 == V2 && V2 == V3) {//return V0;//}// 判断(x,z)坐标在四边形中的位置// 四边形是一个边长为100的正方形// 如果 x == z 则该点在对角线V0V2上// 如果 x < z 则该点在三角形V0V1V2中// 如果 x > z 则该点在三角形V0V2V3中// 地形高度值(Y值)float height = 0.0f;// 在三角形V0V1V2中if (x <= z){// V2和V1之间的高度差(可正可负)float uy = V2 - V1;// 点(x,z)与V1的水平差(x轴方向)float ux = x - vx_min;// 点(x,z)在V1V2的比率float ux_rate = ux / interval;// V0和V1之间的高度差(可正可负)float vy = V0 - V1;// 点(x,z)与V1的水平差(z轴方向)float vz = vz_max - z;// 点(x,z)在V1V0的比率float vz_rate = vz / interval;// 点(x,z)的高度为:V1高度 + x轴高度差 + z轴高度差// x/z轴的高度差计算公式为:V1点到V2点在x轴上的高度是线性变化的,如果V1到V2高度下降2米,则一半位置的时候,下降1米// 我们根据x轴的距离比率来计算高度差的height = V1 + uy * ux_rate + vy * vz_rate;}else{float uy = V0 - V3;float ux = vx_max - x;float ux_rate = ux / interval;float vy = V2 - V3;float vz = z - vz_min;float vz_rate = vz / interval;height = V3 + uy * ux_rate + vy * vz_rate;}// 返回地形高度return height;
};

这个实现类可能有些复杂,需要认真的去思考,里面涉及的一些细节比较多。比如如何确定指定点在那个三角形中,以及正负数的判断等等。所有类都封装完毕之后,我们就开始“main.cpp”文件的内容,首先是全局变量的声明:

// 引入头文件
#include "main.h"
#include "MeshClass.h"
#include "InputClass.h"
#include "CameraClass.h"
#include "TerrainClass.h"// Direct3D设备指针对象
LPDIRECT3DDEVICE9 D3DDevice = NULL;// 输入类
InputClass* input;// 摄像机
CameraClass* camera;// 草地模型
TerrainClass* land;// 胶囊体模型
float x, y, z;
MeshClass* capsule;// 摄像机移动和胶囊体移动速度
float distance = 5.0f;

在这里,我们使用一个胶囊体模型在草地上面进行移动,来测试我们的地形高度计算是否正确。摄像机可以根据“WASD”来进行左右前后移动,“QE”来进行左右旋转,“CV”来进行上下移动。而胶囊体模型的移动就是“IJKL”在世界坐标系的X轴和Z轴方向进行移动。注意,两者的移动是不一样的,摄像机是一个自由移动的,而胶囊体的移动仅能够在世界坐标系的X/Z轴方向移动。后期我们也会给这个胶囊体设置为一个自由移动的物体,并且让摄像机跟随起移动而移动。接下来就是initScene函数,代码如下:

// 初始化输入
input = new InputClass(D3DDevice, hwnd, hInstance);// 初始化摄像机,参数是摄像机位置
camera = new CameraClass(500, 300, -300);// 草地模型边长为10000,分段为100,因此每两个顶点间隔就是100
float interval = 100.0f;// 初始化草地模型
land = new TerrainClass(D3DDevice, L"asset/", L"ground.X", interval);// 创建一个胶囊体
x = 500.0f, y = 0.0f, z = 100.0f;
capsule = new MeshClass(D3DDevice, L"asset/", L"capsule.X");
capsule->init();// 线性纹理
D3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
D3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);// 初始化投影变换
initProjection();// 初始化光照
initLight();

由于摄像机解决了取景变换矩阵,所以我们的initProjection函数改动如下:

// 设置取景变换矩阵
D3DXMATRIX V;
camera->getViewMatrix(&V);
D3DDevice->SetTransform(D3DTS_VIEW, &V);// 设置透视投影变换矩阵
D3DXMATRIX projMatrix;
float angle = D3DX_PI * 0.5f;
float wh = (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT;
D3DXMatrixPerspectiveFovLH(&projMatrix, angle, wh, 1.0f, 1000.0f);
D3DDevice->SetTransform(D3DTS_PROJECTION, &projMatrix);// 设置视口变换
D3DVIEWPORT9 viewport = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 1 };
D3DDevice->SetViewport(&viewport);

光照还是按照我们之前的旧代码,设置全局环境光以及默认材质。接下来我们renderScene函数,代码如下:

// 绘制草地
D3DXMATRIX worldMatrix;
D3DXMatrixTranslation(&worldMatrix, 0.0f, 0.0f, 0.0f);
D3DDevice->SetTransform(D3DTS_WORLD, &worldMatrix);
land->render();// 绘制胶囊体
D3DXMATRIX worldMatrix2;
D3DXMatrixTranslation(&worldMatrix2, x, y, z);
D3DDevice->SetTransform(D3DTS_WORLD, &worldMatrix2);
capsule->render();// 调整取景变换矩阵
D3DXMATRIX V;
camera->getViewMatrix(&V);
D3DDevice->SetTransform(D3DTS_VIEW, &V);

由于我们需要操作摄像机的移动,因此在renderScene函数添加取景变换矩阵的调整,这样移动和旋转摄像机,场景画面就会变化了。我们先运行代码看看效果:

我们可以看到在草地上面放置了一个木板材质的胶囊体。接下来,我们就来完成update函数,让摄像机和胶囊体能够移动,代码如下:

// 获取键盘信息
input->update();
char key = input->key[0];// 按键判断
switch (key) {case 'A':// 摄像机向左移动camera->leftRightMove(-distance);break;case 'D':// 摄像机 向右移动camera->leftRightMove(distance);break;case 'W':// 摄像机 向前移动camera->frontBackMove(distance);break;case 'S':// 摄像机 向后移动camera->frontBackMove(-distance);break;case 'Q':// 摄像机 向左旋转camera->leftRightRotate(-0.02);break;case 'E':// 摄像机 向右旋转camera->leftRightRotate(0.02);break;case 'C':// 摄像机 向上移动camera->upDownMove(distance);break;case 'V':// 摄像机 向下移动camera->upDownMove(-distance);break;case 'I':// 模型向 z 轴正方向移动z += distance;// 根据地形高度设置模型y轴位置y = land->getTerrainHeight(x, z);break;case 'K':// 模型向 z 轴负方向移动z -= distance;// 根据地形高度设置模型y轴位置y = land->getTerrainHeight(x, z);break;case 'J':// 模型向 x 轴负方向移动x -= distance;// 根据地形高度设置模型y轴位置y = land->getTerrainHeight(x, z);break;case 'L':// 模型向 x 轴正方向移动x += distance;// 根据地形高度设置模型y轴位置y = land->getTerrainHeight(x, z);break;
}

对于胶囊体的移动,我们只需要修改器x和z的数值,然后由地形类来就算y值,这样就完成了胶囊体的移动。而摄像机的自由移动则是调用起内部方法,但是,最终的取景变换还是在renderScene函数中完成的。运行效果如下:

通过移动胶囊体,我们发现,它的Y值计算还是正确的,但是,我们分别控制胶囊体和摄像机确实比较麻烦。

本课程的所有代码案例下载地址:

workspace.zip

备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!

第十章 DirectX 绘制简单场景,地形,天空盒和跟随摄像机(上)相关推荐

  1. 第十章 DirectX 绘制简单场景,地形,天空盒和跟随摄像机(下)

    上个文章里面,我们分别控制胶囊体和摄像机确实比较麻烦,接下来,我将创建一个可以自由移动的模型对象,然后让摄像机跟随其移动和旋转.我们使用VS2019来新建一个项目"D3D_10_Follow ...

  2. DirectX11进阶6_静态天空盒、反射动态场景与天空盒、Render-To-Texture(Fade/MiniMap/ScreenShot)

    一.静态天空盒与反射天空盒 这一章我们主要学习由6个纹理所构成的立方体映射,以及用它来实现一个静态天空盒. 但是在此之前先要消除两个误区: 认为这一章的天空盒就是简单的在一个超大立方体的六个面内部贴上 ...

  3. unity场景导入与简单场景搭建

    unity场景导入与简单场景构建 unity场景导入 unity简单的场景构建 一.创建一个新的Scene场景 二.创建设置地形 三.为场景添加光照 四.设置鼠标指针图片 五.添加水面和天空盒子 un ...

  4. 揭秘《死者之书》之风、场景地形及优化技巧

    在此资源文件中,我们在使用了摄影制图法进行创作.而其中一些资源来自Quixel公司Megascans库的扫描环境艺术资源,Unity与Quixel在这个项目的制作上达成了紧密的合作. 在制作过程中,U ...

  5. 从零开始做一个SLG游戏(二):用mesh实现简单的地形

    本文主要是用mesh实现简单的地形.暂时先绘制三种地形:高山.平原.水域. 首先要做的是网格的细化: 上一篇已经实现了单个六边形的绘制,实现方式是将六边形分割成6个等边三角形,然后分别绘制. 现在需要 ...

  6. Cesium获取绘制范围内地形高程进行淹没分析

    Cesium淹没分析,看起来以为很难实现,其实实现起来还是比较简单的,构建一个面,动态设置其extrudedHeight属性,一定要使用CallbackProperty动态设置extrudedHeig ...

  7. autocad2007二维图画法_cad怎样绘制简单的二维图形

    CAD绘制二维图形非常的简单,大家经常用它来画图,下面是学习啦小编带来关于cad怎样绘制简单的二维图形的内容,希望可以让大家有所收获! cad绘制简单二维图形的方法 1.绘图菜单绘图菜单是绘制图形最基 ...

  8. python画折线图代码-python绘制简单折线图代码示例

    1.画最简单的直线图 代码如下: import numpy as np import matplotlib.pyplot as plt x=[0,1] y=[0,1] plt.figure() plt ...

  9. python画折线图详解-python绘制简单折线图代码示例

    1.画最简单的直线图 代码如下: import numpy as np import matplotlib.pyplot as plt x=[0,1] y=[0,1] plt.figure() plt ...

  10. java canvas 画图片_[Java教程][HTML5] Canvas绘制简单图片

    [Java教程][HTML5] Canvas绘制简单图片 0 2016-05-13 13:00:04 获取Image对象,new出来 定义Image对象的src属性,参数:图片路径 定义Image对象 ...

最新文章

  1. 入职阿里啦!极客时间kotlin
  2. 用Java模拟网卡、声卡继承PIC接口,实现网卡、声卡能与主板连接
  3. 发现一个好的索引-阳神
  4. 科普|推荐系统常用算法总结
  5. 【UIKit】UITableView.02
  6. LeetCode 641. 设计循环双端队列
  7. 在pandas中遍历DataFrame行
  8. harmonyOS智慧屏,在鸿蒙HarmonyOS智慧屏上实现一款粗糙的计算器
  9. byte[]和string
  10. 让数据窗口的标题栏在选中后显示为蓝色
  11. 基于JAVA实现的客户信息管理软件(简易)
  12. 卷影副本(Shadow Copies)
  13. 如何用PS的量度标尺工具调整图片
  14. springboot毕设项目热贡文化艺术展示与定制s5g19(java+VUE+Mybatis+Maven+Mysql)
  15. 思科模拟器交换机的基本配置
  16. 计算机计算资产分析表,财务指标计算器.xls
  17. 服务器physx性能测试,望穿秋水! PhysX卡性能测试首度曝光
  18. Autohotkey实现粘贴板图片用百度OCR识别
  19. WebRTC初学Demo
  20. 错题本Android Studio

热门文章

  1. WEB常用HTML颜色代码表
  2. CSS—内联样式(行内样式)、内部样式、外部样式、选择器
  3. android盒子改造,【当贝市场】废旧手机改造成电视盒子详细教程
  4. java ssm框架论文,ssm框架理解
  5. GIS空间分析 栅格数据分析3 可达性分析
  6. quartus频率计 时钟设置_FPGA021 基于QuartusⅡ数字频率计的设计与仿真
  7. 单片机特殊知识总结(二)
  8. 两相四线步进电机的驱动
  9. 电信 宽带 光猫 中兴 F460 V6.0.0P11T2sc 破解 telecomadmin 超级密码 的方法
  10. Linux下编写udp群聊室