UE4 4.25离线文档,基于Dash文档源,使用Zeal查阅

image-20200828133618221

image-20200828133843396

Zeal下载

4.25文档下载

使用方法

直接将压缩版解压到Zeal\docsets\路径内,没有文件夹请自行新建

然后按照下图设置docsets路径

image-20200828134552604

下载其他文档

参考如下图方式下载,因为都是国外链接,所以请酌情使用交通工具✈

image-20200828155324943

虚幻商城有一个非常成熟而且有诚意的VR框架,涵盖了基本上VR可以用到的所有主流功能,同时也有PC和移动端的支持。

商城链接

趁着项目需求借机分析一下这个框架,后续打算基于此扩展适用于项目的框架

分析记录

2020.8.21:

1.纯蓝图+组件的思路搭建,使用起来不算麻烦也不能算太方便,学习成本还是有的

2.解耦做的不是特别的好,很多类之间有强依赖,少一个就报错

3.功能做的非常完善,该有的基本的有了

4.性能无实测,但是各类组件无脑Tick初步看有性能压力

5.部分内容比如地图、边缘发光、武器/弓箭交互等单独拎出来都是非常实用的功能

关卡管理

必要元素
  • GameMode
  • BP_Info_Map
  • BP_Info_Level
  • BP_PostProcess
  • BP_PlayerPostion

以上的这些都可以用EU_Widget来生成

image-20200820111916928

BP_MapInfo

放置在场景中的具体对象,主要就是为了配置Info_Level,在BP_GameInstance_Main类中直接通过方法GetCurrentLevelClass从此对象去要到Info_Level

Info_Level
Streaming Level

用于加载子关卡,比如当前关卡是TestMainMap,LevelsToLoad设置为TestMap01,那么在进入TestMainMap后会自动加载TestMap01

Transition

切换关卡的选项,首先设置时间变量,如果是0就是瞬间加载,可以设置天空球贴图和背景色

Panel

关卡面板的UI布局信息,可以参考demo演示的布局,见下图

image-20200820135730829

key

本地化文字有关的一个变量,此框架是用表格的方式设置本地化。

此变量作为DataTable的RowName读取表格

pawn

对应平台的pawn设置

Pallet

LevelImage变量决定关卡在Map面板下的显示图片,如下图

image-20200820140512683

BP_PostProcess

维护后期效果的类,主要目的是用于高光显示,当然高光显示还有其他几个方式,只是后期方式效果比较好

BP_PlayerPostion

玩家起始位置,内部主要实现了一个传送玩家并校准位置的方法

VR玩家配置

VR玩家需要继承自BP_Pawn_VR或者BP_Pawn_VR_Char,区别在于后者提供了一个半身小白人模型

Comp_Controller_VR
  • Controller Set

自定义对应MotionController的类,框架给的BP_MotionController_Hands内通过4个ChildrenActor分别实现模型、传送、菜单、移动功能,具体功能类如下

  • BP_MComp_Teleport:传送功能,以及传送效果的一些参数配置
  • BP_MComp_Hand:手模型以及动画,提供一些抓取等参数的设置
  • BP_MComp_Controller:控制器模型,代替手
  • BP_MComp_RadialMenu:菜单功能
  • BP_MComp_Movement:移动功能
  • BP_MComp_Trigger:单纯用于监听按键事件的组件,可以用来扩展输入事件
  • BP_MComp_Laser:射线功能组件,提供一些射线相关参数配置
  • BP_MComp_Smartwatch:手表菜单组件,需要配置相应UMG

如果设置了Controller Mesh Override,就会用相应的模型覆盖上面设置的模型

  • ControlsPresets

对应平台的按键映射

  • Haptics
    • HapticsForSelect:在点开Select选单时手柄震动
    • HapticsForDevices:在与工具类型的道具交互时手柄震动
    • HapticsForGrab:在触发Grab时手柄震动
    • HapticsForTrigger:在各种操作触发触发器需要修改set值时手柄震动
Teleport/传送
  • image-20200820142920984
  • Teleport Mode:配置不同的传送交互类型
    • TeleportFree:传送到射线所指定的位置,但是在Z轴normal值小于30度的情况下不允许传送
    • TeleportAreaSelect:激活Teleport出现网格,触发手柄上的select操作进行传送,传送后网格消失
    • TeleportAreaActive:手柄指向地面时出现网格,激活Teleport即传送
    • Nav Mesh:在寻路网格可达的范围内进行传送,交互与TeleportFree类似,但是取消了30度的角度判断
  • Teleport Search Type:用于传送的射线类型
    • Straight:直线射线指向
    • Projectile:抛物线射线指向
  • Teleport Time:传送所花费的总时间
  • Use Teleport Motion:设定传送的过渡类型
    • None:没有过渡态,直接传送到目标点
    • Indication:location具有过渡态,逐步过渡到目标点
    • FullMotion:location与rotation都具有过渡态,位置过渡到目标点且角色的rotation过渡到目标rotation

在配置按键映射的DataAssets内相关的teleport选项

  • Teleport Execute:传送触发的条件
    • OnTrigger:trigger按键按到底触发
    • OnThumbstickRelease:thumbstick按下并松开时(通过thumbstick来转向时比较好用)
    • OnTeleportButtonRelease:上面注册的Teleport按钮被按下并松开时
  • Teleport Deactivate:传送取消的条件
    • OnlyExecute:只允许被触发不允许取消
    • NeutralThumbstick:trackpad中键取消传送
  • TeleportRotationType:传送时的面向操控
    • None:无法转向
    • RotateByThumbstick:通过trackpad来操作传送时的方向
    • RotateByHand:通过手的转向来操作传送时的方向
Movement/移动

image-20200820143452130

  • Movement Style:移动的方式
    • Fluent:在地面上以固定速度移动
    • StopMotion:当walk的按钮按下后产生一个ghost,然后移动固定秒后瞬移到ghost的位置,若提前松开则取消移动,最多两秒
    • Ghost:当walk的按钮按下后产生一个ghost,松开则瞬移到ghost的位置
  • Ghost Behaviour:Ghost的位移操作形式配置
    • Direction:通过手柄的方向来操作ghost移动的方向与速度
    • MovementRelativeToOrigin:通过按下walk按钮时的初始位置来计算后续的相对偏移量来操作ghost的移动,非常难用
    • PositionRelativeToOrigin:按下按钮后通过手柄的移动距离来移动ghost的位置,通过手柄的方向来改变ghost的朝向
  • Stop Motion Interval:用于配置StopMotion的固定秒数时间,大于2秒也会被设置成2秒
  • Movement Speed:ghost移动的速度
  • Move with Fade:瞬移时是否允许黑屏过渡
Rotation

RotateDerees:对应按键映射中的TurnLeftTurnRight的转向角度

按键映射补充
  • Trigger Grip:是否允许长按select的按钮来触发grip(默认是0.4秒,可以通过BP_MCmop_Laser中的Handle Trigger来修改长按间隔)
  • Holding Grip:是否允许按下按钮抓取,松开按钮放开,或者为单次按下抓取,抓取状态下再次按下松开
关于按键的自定义

按键的起始点在Pawn内,如下截图

image-20200820145557962

调用到BP_MotionController内的方法KeyInput,然后遍历调用所有BP_MComp组件的方法FunctionInput,然后继承自BP_MComp的组件各自实现/重写按键对应的逻辑,一般可以用一个Switch来做

如需要添加自定义按键事件,修改枚举Enum_Control_Function即可

Comp_RadialMenu

image-20200820150244981

如图所示,框架默认给予2个RadialMenu事件,分别可以设置2个面板,

image-20200820150349447

Comp_UI_Display

image-20200820150423064

image-20200820150455057

如图所示,手部内测的面板,通过设置该组件配置

Comp_UI_Pallet

一个次级面板,在RadiaMenu面板中如果指定了对应的类就可以触发此面板,比如指定了BP_Radial_Button_Pallet_Bookmarks,在此组件内就可以配置相应的Bookmarks内容

此组件又非常多的功能,之后逐步细讲

DataAssets

首先要回顾一下Info_Level类中有一个ActorInfos列表,需要在里面添加所有需要用到的表

image-20200820164521985

如上图所示,我们需要的DA_Test资源需要从PDA_ActorList创建,此类是继承自PrimaryDataAsset的蓝图类,内部就是创建了一系列变量;然后把这个资源放入Info_Level

image-20200820165040854

然后我们右键自建蓝图类,CreateDatatableEntry,主要选择标签和ActorList,创建后自动生成基础的DataAsset,即可添加到之前创建的DA_Test

然后我们以Map_Demo_UI关卡做测试,找到类BP_Pallet_ObjectDropperComp_UI_Pallet组件,配置如下

image-20200820165341595

运行以后就得到如下结果

image-20200820165410975

Comp_Select

关于选择的组件,比较基础也比较有用

对于VR玩家,默认使用Trigger按键类出发select功能

image-20200820172012396

  • SelectMenu

对应上面的变量SelctionTypes,点击以后就自动生成按钮

  • Custom

自定义PressSelect事件就可以

  • Trigger

需要配合组件Comp_ListenToTrigger 使用,点击以后Comp_ListenToTrigger::TriggerSingle能执行

可以设置ActorsToTrigger变量来让其他类收到Listen消息

设置ComponentTagToSearch可以对类内的不同模型进行筛选

  • window

详情请看后面的Comp_WindowObject

  • visual

配置请看后面的Comp_Visual,区别于SelectMenu+VisualChange的组合,如果直接指定Visual则是直接切换,无菜单提示

  • light

配合光源和Light组件一起使用

  • open

详情请看后面的Comp_Open

  • active

详情请看后面的Comp_Active

Comp_Drag

拖拽组件,可以平移和旋转拖拽,一般需要配合Latch组件来使用

变量解释
  • identifier:组件之间交互的名称,如果类内有多个Drag组件就需要区分不同的名称
    DragType:拖动规则
    • key:具体规则,如变量名称描述的含义
    • value
      • SnapType:吸附规则
        • FreeMovement:自由移动
        • SnapToSegment:吸附到最近的分段,即下面的sectionSet分段
        • Reset:重置为初始状态
      • StartPositon:初始位置
      • SnappingSpeed:吸附速度
      • FollowSpeed:跟随速度
      • SelectRule:取值方式,3个选项以此为四舍五入,ceil,floor
      • Toggle:强制设定每次到达极值时的set变量为上次的反向值
  • SectionSet:分段极值设定
  • TriggerAlsoOwner:通知owner
  • ComponentTagToSearch:需要拖动的mesh需要加上该标签
使用流程
  • 在被拖动的模型中加入插槽AttachPoint来指定交互点

  • 确保交互模型可以被射线检测到

  • 挂上Comp_Drag,然后需要配合Comp_Latch使用,该组件可以使用默认值

  • 在交互模型中加入以上2个组件所需要的tag,默认为Drag,Latch

  • 可以配合Comp_ListenToServer使用,可以监听每次到达极值的时间,或者Tick的Drag事件

  • 同一个类下可以挂载多个Drag组件来拖动不同的模型组件,记得修改identifierComponentTagToSearch,也需要修改Latch组件的ComponentTagToSearch变量,也不要忘记区分不同模型的Tag

  • 如果是spline模式,在类内创建一个spline组件


Comp_Grab

抓取组件,对于VR玩家可以有射线和接触抓取两种方式,不过具体的功能会有区别,见下图

image-20200821095808183

变量解释
  • GrabType:是否在抓取后模拟物理,利用的是PhysicsHandle的抓取机制,一般的需要把模型放到root位置
  • SnappingType:取消抓取后吸附的过程是否开启物理模拟
  • ShouldKeepUpright:在抓取后是否保持世界坐标正前方
  • ShouldAutoPickup:VR模式下手部接触以后直接抓取
  • CanBePickedUp:是否可以被抓取
  • SnapAndReplaceControlls:抓取之后替换手柄预设的所有操作,直到release为止,之后手柄的操作可以映射到组件的KeyInputThumbsticks方法中
  • RelativeControllerPostion:抓取以后的相对位置校准
  • MaxDistanceToSocket:最大抓取距离(距离socket)
  • SpawnTinyDisplay:抓取以后生成小提示面板,需要用DA去配置,同样的只支持VR射线模式
  • GrabAttachedActorInstead:决定是否抓取此物体时替换掉之前抓取的物体
  • GrabTag:默认抓取类中的所有物体,可以用此标签区分

Comp_Connector_Surface

配合Grab组件一起使用,目的是

  • RotateOnSurface:决定如何旋转,是否垂直墙面等
  • ShouldBeActiveEvenIfPhysics:如果开启物理模拟,就需要开启此选项,防止过多碰撞挤压
  • AllowPhysicsAfterSnap:此物体吸附到平面后是否启用物理模拟
  • RequiresTag:对吸附的平面做tag判断
  • SearchDistance:吸附距离

需要特别注意的是,需要设置此组件的朝向问题,默认是X轴检测

Comp_Anchor/Comp_Connect_Anchor

特殊的吸附配套组件,后者吸附到前者

Comp_Anchor

image-20200821170439723

  • Anchor_Deactived:关闭吸附效果
  • Allow_Physics:吸附后启用物理
  • Connect_IDs_To_Allow:需要与配套组件对应的ID
  • Connect_Should_Attach:子物体吸附到此物体后是否attach
  • Sphere_Radius:查找半径
  • Attached_Lock:吸附以后锁定,即不能再交互
Comp_Connect_Anchor

image-20200821170830375

  • Tag Of Component to Attach:初始时此Actor attach到Attached to Actor下的带有此tag的component中
  • Attached to Socket:当Attached to Actor不为None,且Tag Of Component to Attach也不为None时,该actor会attach到Tag Of Component to Attach所提到的component中的名为Attached to Socket的socket中,若Attached to Actor不为None,但是Tag Of Component to Attach为None时,此Actor会attach根组件下名为Attached to Socket的socket上,若没有则会attach到Attached to Actor的根组件下
  • ConnectorID:与配套组件对应的ID
  • SearchRadius: 查找半径
  • SearchDistance:判断连接时的球形射线距离组件原点的距离

最终效果

image-20200821171243617

image-20200821171309985

Comp_Latch

在之前Grab组件中已经有用到,主要作用是用来锁定位置

主要设置就是LatchType

  • Drag:对应的是就是抓取
  • SimpleLatch:简单的吸附,不需要配合Grab组件,手靠近以后就自动吸附,针对VR玩家
  • Climb:攀爬模式,可以用于攀岩类操作,针对VR玩家

Comp_ListenToServer

除了上面的Grab用到的功能,该组件还有其他作用

Light

设置为Light模式,需要配合组件Comp_Light使用,在类内需要至少给一个光源,对于多个光源可以一起被作用

测试类如下图

image-20200821105432348

如果使用ListenToSingle模式那么对于Drag过来的事件按50%作为临界点开关

Active

详情请看后面的Comp_Active

Open

详情请看后面的Comp_Open

Window

详情请看后面的Comp_WindowObject

Comp_Active

配合其他可以发送active事件的组件使用,如select.

该组件可以收到事件ActiveStateChangedConstActiveStateChanged,自行实现后续的逻辑

Comp_Open

配合其他可以发送Open事件的组件,比如Select或者Drag组件,来实现移动、旋转或者缩放的操作

需要将key的值对应到交互模型的tag

可以通过Listen组件来监听其他类的事件发送

Comp_Overlap

一般搭配Drag组件来模拟按钮效果

image-20200821152007053

image-20200821152143566

如上图所示,绿色模型开启overlap效果,增加tag成员overlap,drag

抓取其他物件对绿色部分overlap以后就能模拟按钮效果

Comp_WindowObject

主要是文字和图片说明的窗口,可以配合Select组件展示

需要注意的是挂载此组件的类必须注册到PDA_Actor_List里面,否则框架没有判空直接报错,具体请看之前Info_level以及Data_Assets内容

image-20200821172837931

image-20200821172859847

Comp_Visual

改变外观的组件,一般配合select组件的selectionMenu模式,然后在SelectionTypes内添加VisualChange成员来实现

image-20200821154014085

  • CurrentMaterials:当前的材质类型,对应下面的配置
  • PossibleMaterials:材质配置表
    • Key:材质效果的键
    • ButtonMaterial/ButtonTexture:按钮的材质和图片
    • MeshInfo:替换模型的效果
      • Key:需要把这个值填写到模型的Tag中去
      • Value:替换模型的模型和材质效果

image-20200821154329493

Comp_Widget

封装了UMG的组件,同时方便于交互

image-20200821160827586

  • UsesRecursiveWidgetSelection:使用框架自定义的UMG交互,如Widget_Button
  • ‘UsesWidgetInteractionComponent:使用默认UMG空间交互,如Button
UsesRecursiveWidgetSelection

我们创建一个继承自Widget_Pallet_Button_Normal的UMG,直接放入测试UMG中,监听Pressed事件即可得到点击事件

UsesWidgetInteractionComponent

创建一个Button,点击交互后就可以走到Pressed事件

要注意Clicked事件无效

框架UMG类

待补充….


Map

小地图的使用,分为2D和3D两种模式

在需要显示在地图上的类中需要挂载组件Comp_MapLocation

设置Icon及尺寸,以及是否支持旋转缩放等选项

SubstitueMaterial可以用来覆盖原有材质,让物体在小地图里显示特殊材质

image-20200821112246681

然后需要若干个地面,用框架中的类BP_Floorplanmeasurement

指定贴图,该贴图最好跟场景的样貌匹配,给定FloorName,用于显示在面板上的名称

2DMap

使用类BP_2DMap,直接拖入场景中即可,记得设置InitTab

3DMap

使用类BP_3DMap,拖入场景中以后需要给与变量Measurement一个地面单位即可显示3D地形;

或者直接使用Comp_Child_3DMap也是一样的,记得把地形的缩放拷贝到这个类的缩放中,不然会导致变形

第一步我们需要将虚拟机的网络模式修改为桥接模式,并且勾选复制物理网络连接状态选项

ubuntu在20.4版本以后修改IP的方法与之前的有了比较大的区别,在之前的版本是通过编辑/etc/network/interfaces文件去修改IP,在新版本以后修改的配置文件是

/etc/netplan/01-cloud-init.yaml,这个01可能每个人不相同,网上有些人是00,我这边是01

所以,我们用用sudo vim /etc/netplan/01-network-manager-all.yaml打开,输入以下指令

1
2
3
4
5
6
7
8
9
10
11
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
ethernets:
ens33:
dhcp4: false
addresses: [192.168.1.222/24]//ip地址,后面的24不需要改,更掩码有关系
nameservers:
addresses: [8.8.8.8,192.168.0.1]
~

上面的222是你自己设定的IP地址,前面需要参考你真是物理IP的情况

然后输入sudo netplan --debug apply应用

然后通过ifconfig查看IP

git 是一种基于命令的版本控制系统,全命令操作,没有可视化界面

gitlab 是一个基于git实现的在线代码仓库软件,提供web可视化管理界面,通常用于企业团队内部协作开发

当然还有github也可以用作代码仓库,较多用于个人

准备工作

安装git

git的使用和安装推荐本人另外一篇博客git使用指北

安装虚拟机

推荐下载VMwareWorkstation,可以到官网下载,然后百度搜索绿色方法

然后我们安装Linux镜像,本人使用的是Ubuntu20.4版本,这个网上一搜一大堆,随意选择版本,初学者可以选择桌面版本,如这里下载

然后一路安装,运行,设置root账户等

这里推荐虚拟机的使用2核,至少使用超过4GB的内存,本人直接上40GB

搭建基本环境

安装依赖项

如果是桌面版,右键进入终端,非桌面版忽略

1
2
sudo apt update
sudo apt install ca-certificates curl openssh-server postfix
安装GitLab
1
2
cd /tmp
curl -LO https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh

tmp目录安装gitlab,当然你可以自己选择目录

1
sudo bash /tmp/script.deb.sh

该脚本将设置您的服务器以使用GitLab维护的存储库。这使您可以使用与其他系统软件包相同的软件包管理工具来管理GitLab。完成后,您可以使用以下apt命令安装实际的GitLab应用程序

1
sudo apt install gitlab-ce

此条命令将在系统上安装必要组件

如果在安装gitlab使有如下报错,可以今夏如下操作

1
2
3
4
5
# apt-get install gitlab-ce
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package gitlab-ce

修改安装脚本

1
2
sudo vim /etc/apt/sources.list.d/gitlab_gitlab-ce.list
1

OLD

1
2
3
deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ focal main
deb-src https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ focal main
12

NEW

1
2
3
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main
deb-src https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main
12

再次执行

1
2
3
sudo apt update
sudo apt install gitlab-ce
12

参考链接:https://gitlab.com/gitlab-org/gitlab-foss/-/issues/2370

修改防火墙
1
sudo ufw status

检查防火墙状态,如果没有防火墙需要apt安装

1
2
3
sudo ufw allow http
sudo ufw allow https
sudo ufw allow OpenSSH

然后通过上述3条命令让防火墙支持http\https\OpenSSH

再次检查以后大概会有如下状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Status: active

To Action From
-- ------ ----
80 ALLOW Anywhere
22 ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
OpenSSH ALLOW Anywhere
80 (v6) ALLOW Anywhere (v6)
22 (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
OpenSSH (v6) ALLOW Anywhere (v6)
修改GitLab配置文件
1
sudo vim /etc/gitlab/gitlab.rb

该文件比较大,在顶部找到``external_url `,按照如下注释修改

1
external_url 'https://example.com' // 此处修改为您的域名或ip地址,不用加端口

然后查找letsencrypt['contact_emails'],如果没有先忽略跳过,如果有则进行如下修改

1
letsencrypt['contact_emails'] = ['sammy@example.com'] // 此处修改为您的邮箱地址

然后重新配置GitLab

1
sudo gitlab-ctl reconfigure // 每次修改配置文件都需要重新配置

本人在这一步遇到一个报错,最后一句是Please upgrade your ACME client to a version that supports ACMEv2 / RFC

解决方法是在 /etc/gitlab/gitlab.rb 中添加 letsencrypt['enable'] = false

重启

1
sudo gitlab-ctl restart
GitLab常用命令
常用命令 说明
sudo gitlab-ctl reconfigure 重新加载配置,每次修改/etc/gitlab/gitlab.rb文件之后执行
sudo gitlab-ctl status 查看 GitLab 状态
sudo gitlab-ctl start 启动 GitLab
sudo gitlab-ctl stop 停止 GitLab
sudo gitlab-ctl restart 重启 GitLab
sudo gitlab-ctl tail 查看所有日志
sudo gitlab-ctl tail nginx/gitlab_acces.log 查看 nginx 访问日志
sudo gitlab-ctl tail postgresql 查看 postgresql 日志
systemctl enable gitlab-runsvdir.service 开机启动
systemctl disable gitlab-runsvdir.service 禁止开机自启动
首次登陆

在Web浏览器中访问GitLab服务器的域名:

1
https://example.com // 您external_url配置的地址

第一次访问需要设置管理员密码,记得保存,之后如果没有gitlab账户就需要创建一个并登陆,然后就可以正常创建项目了

证书问题

有时候因为各种原因需要修改仓库端的虚拟机ip,也就意味着gitlab需要修改ip地址

这些操作很基础,在上面也讲了,但是修改IP以后本人发现原有的证书就无效了,应该是第一次部署的时候默认会生成我们IP对应的证书,我这边的第一次部署的ip地址是192.168.1.111如下图

image-20201021104015652

后面因为网段改为了2,所以整体把虚拟机和gitlab地址都改成了2段,然后运行gitlab就发现如下日志

image-20201021104134810

直接告诉我招不到证书文件,于是翻遍资料最后找到一个粗暴的方法,手动生成证书文件

  1. 创建私钥和证书请求
1
openssl req -nodes -newkey rsa:2048 -keyout 192.168.2.110.key -out 192.168.2.110.csr

上面的ip地址需要替换你对应的地址

私钥会提示输入密码,记住了跟着提示输入

输入以后跟着提示输入你的信息,我这边老老实实的输入了正确信息,不确定可否随意输入

1
2
3
4
5
6
7
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:zhejiang
Locality Name (eg, city) [Default City]:hangzhou
Organization Name (eg, company) [Default Company Ltd]:xiaoshan
Organizational Unit Name (eg, section) []:xiaoshan
Common Name (eg, your name or your server's hostname) []:xiaoshan
Email Address []:自己的邮箱地址
  1. 移除私钥的密码短语
1
2
3
4
cp -v /etc/gitlab/ssl/192.168.2.110.{key,original}
openssl rsa -in /etc/gitlab/ssl/192.168.2.110
-out /etc/gitlab/ssl/192.168.2.110.key
rm -v /etc/gitlab/ssl/192.168.2.110.original
  1. 创建证书
1
openssl x509 -req -days 1460 -in /etc/gitlab/ssl/192.168.2.110.csr -signkey /etc/gitlab/ssl/192.168.2.110.key -out /etc/gitlab/ssl/192.168.2.110.crt
  1. 设置权限
1
chmod 600 /etc/gitlab/ssl/192.168.2.110.*

有文章提示可以删除.csr文件,我这里没删除也没遇到问题

如果需要删除,运行rm -v /etc/gitlab/ssl/192.168.2.110.csr

然后重新配置gitlab,重启gitlab就可以了

仓库操作

创建仓库流程于github非常类似而且非常简单

image-20200818071803709

然后我们需要为项目添加成员,如下图输入其他成员的邮箱或者用户名,添加以后其他用户自己登陆后也能看到项目了

image-20200818073638776

完成仓库操作以后可以添加SSH Key来允许用户提交

image-20200818071718978

然后我们可以在windows端clone下来,就可以正常git流程操作了

image-20200818071856868

本文主要介绍UE4下的JSON文件的读写

文件读写

首先我们需要扩展普通的文件读写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool UTestLib::V_LoadStringFromFile(const FString& FileName, const FString& RelativePath, FString& result)
{
if (!FileName.IsEmpty())
{
FString file=FPaths::ProjectContentDir()+RelativePath+FileName;
return FFileHelper::LoadFileToString(result,*file);

}
return false;
}
bool UTestLib::V_SaveStringToFile(const FString& FileName, const FString& RelativePath, const FString& string)
{

if (!FileName.IsEmpty())
{
FString file = FPaths::ProjectContentDir() + RelativePath + FileName;
return FFileHelper::SaveStringToFile(string,*file);

}
return false;
}

此方法是以工程目录下的Content路径为基础的,如果需要指定其他路径就需要替换 FPaths::ProjectContentDir()方法或者干脆直接把整个路径给公开

阅读全文 »

本文描述了如何简单的创建自定义的视图和节点

后续补充具体功能开发

image-20200805140828530
插件模块

创建插件TestGraph

  • 包含如下模块
1
2
3
4
5
6
7
8
9
10
11
"Projects",
"InputCore",
"UnrealEd",
"LevelEditor",
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"ToolMenus",
"GraphEditor",
"EditorStyle"

在模块类里声明2个变量

1
2
class UEdGraph* GraphObj;//图表实例的类型,定义了该图表的行为(例如保存图表
TSharedPtr<class SGraphEditor> GraphEdSlate;//图表编辑器的Slate类型

构造GraphObj,指定GraphObj内的Schema

创建GraphEdSlate,同时把GraphObj指定给GraphEdSlate

1
2
3
4
5
GraphObj=NewObject<UEdGraph>();
GraphObj->AddToRoot();
GraphObj->Schema=UTestGraphSchema::StaticClass();//自定义的schema类,刚开始可以先使用默认的UGraphSchema即可显示基础图表
GraphEdSlate=SNew(SGraphEditor)
.GraphToEdit(GraphObj);

在返回中添加次Slate

1
2
3
4
5
6
7
8
9
10
11
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SNew(SBox)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
GraphEdSlate.ToSharedRef()//需要转成ref
]
];
Schema
  1. EdGraphSchema里定义了图表操作的大部分全局行为
  2. FBlueprintEditorUtils::CreateNewGraph中将UEdGraphEdGraphSchema建立映射
  3. FEdGraphSchemaAction类主要执行了一个PerformAction,用于生成UEdGraphNode
FEdGraphSchemaAction
  • PerformAction

生成节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
UEdGraphNode* FTestGraphSchemaAction::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /* = true */)
{
UEdGraphNode* resNode=nullptr;
if (NodeHello)
{
const FScopedTransaction Trans(LOCTEXT("FF","Hello:NewNode"));
ParentGraph->Modify();
if (FromPin!=nullptr)
{
FromPin->Modify();
}
NodeHello->Rename(TEXT("TEST"),ParentGraph);
ParentGraph->AddNode(NodeHello,true,bSelectNewNode);

NodeHello->CreateNewGuid();//唯一标识
NodeHello->PostPlacedNewNode();//初始化
NodeHello->AllocateDefaultPins();//针脚
NodeHello->AutowireNewNode(FromPin);//自动创建节点

NodeHello->NodePosX=Location.X;
NodeHello->NodePosY = Location.Y;

NodeHello->SetFlags(RF_Transactional);

resNode=NodeHello;
}

return resNode;

}
UEdGraphSchema
  • GetGraphContextActions

右键点击空白页面,用于创建选择节点

1
2
3
4
5
6
7
8
9
10
11
12
void UTestGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
TSharedPtr<FTestGraphSchemaAction> NewNodeAction(new
FTestGraphSchemaAction(LOCTEXT("11","GraphNode"),
LOCTEXT("Desc","TestNodeDesc"), //desc
LOCTEXT("NewGraphText","Add Hello Node"),//hover
0));

NewNodeAction->NodeHello=NewObject<UTestNode_Hello>(ContextMenuBuilder.OwnerOfTemporaries/*存储节点*/);
ContextMenuBuilder.AddAction(NewNodeAction);

}

image-20200805110906761

如上图,显示3个对应的名称

  • GetContextMenuActions

右键点击节点后的效果,产生例如BreakLink之类的效果

此方法在4.24版本(不确定之前哪个版本开始)有更改如下

1
2
virtual void GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context)const//新版本
void GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, class FMenuBuilder* MenuBuilder, bool bIsDebugging) const; //旧版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void UTestGraphSchema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const
{
if (Context->Pin)
{
{
FToolMenuSection& Section = Menu->AddSection("TestGraphSchemaNodeActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions ___"));
// Only display the 'Break Links' option if there is a link to break!
if (Context->Pin->LinkedTo.Num() > 0)
{
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakPinLinks);

// add sub menu for break link to
if (Context->Pin->LinkedTo.Num() > 1)
{
Section.AddSubMenu(
"BreakLinkTo",
LOCTEXT("BreakLinkTo", "Break Link To..."),
LOCTEXT("BreakSpecificLinks", "Break a specific link..."),
{}
);
}
}
}
}
else
if (Context->Node)
{
{
FToolMenuSection& Section = Menu->AddSection("TestGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);

Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
}
Super::GetContextMenuActions(Menu, Context);
}

image-20200805111804599

连接方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UENUM()
enum ECanCreateConnectionResponse
{
/** 多对多 */
CONNECT_RESPONSE_MAKE,

/*不能连接,默认 */
CONNECT_RESPONSE_DISALLOW,

/** 多对一 */
CONNECT_RESPONSE_BREAK_OTHERS_A,

/** 一对多 */
CONNECT_RESPONSE_BREAK_OTHERS_B,

/** 一对一 */
CONNECT_RESPONSE_BREAK_OTHERS_AB,

/** Make the connection via an intermediate cast node, or some other conversion node. */
CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE,

CONNECT_RESPONSE_MAX,
};

重写CanCreateConnection设置节点的连接方式

1
2
3
4
virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
{
return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT("Not 2 by this schema"));
}
  • 创建连接方式

使用自定义的类FTestConnectionDrawingPolicy来定义连接方式

1
2
3
4
class FConnectionDrawingPolicy* UTestGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
return new FTestConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,InZoomFactor,InClippingRect,InDrawElements, InGraphObj);
}
自定义节点
UEdGraphNode
  1. EdGraphNode是图表节点实例的类型,定义了节点的行为
  2. AutowireNewNode定义了节点的自动连接行为( 参考上述在schema创建的时候调用)

class UTestNode_Hello :public UEdGraphNode

重写2个方法

AllocateDefaultPins:用于创建节点

GetNodeTitle:标题

可以参考系统的节点

目录大多在 \Engine\Source\Editor\GraphEditor\Public\KismetPins\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void UTestNode_Hello::AllocateDefaultPins()
{
CreatePin(EGPD_Input,"HelloNodeInput_Bool",FName(),TEXT("bool"));
CreatePin(EGPD_Input, "HelloNodeInput_Color", FName(), TEXT("color"));
CreatePin(EGPD_Input, "HelloNodeInput_Enum", FName(), TEXT("enum"));
CreatePin(EGPD_Input, "HelloNodeInput_Exec", FName(), TEXT("exec"));
CreatePin(EGPD_Input, "HelloNodeInput_NameList", FName(), TEXT("namelist"));
CreatePin(EGPD_Input, "HelloNodeInput_Object", FName(), TEXT("obj"));
CreatePin(EGPD_Input, "HelloNodeInput_String", FName(), TEXT("str"));
CreatePin(EGPD_Input, "HelloNodeInput_Vector4", FName(), TEXT("v4"));
CreatePin(EGPD_Input, "HelloNodeInput_Integer", FName(), TEXT("int"));
CreatePin(EGPD_Input, "HelloNodeInput_Vector2", FName(), TEXT("v2"));

CreatePin(EGPD_Output, "HelloNodeInput_1", FName(), TEXT("1"));
CreatePin(EGPD_Output, "HelloNodeInput_2", FName(), TEXT("2"));
CreatePin(EGPD_Output, "HelloNodeInput_3", FName(), TEXT("3"));
CreatePin(EGPD_Output, "HelloNodeInput_4", FName(), TEXT("4"));
CreatePin(EGPD_Output, "HelloNodeInput_5", FName(), TEXT("5"));
}

FText UTestNode_Hello::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return FText::FromString("Hello !");
}
SGraphNode

此类是用于显示具体效果的

1
2
3
4
5
6
void Construct(const FArguments& InArgs, UEdGraphNode* InNode);//构造跟默认slate不一样
virtual void UpdateGraphNode()override;//刷新节点
virtual void CreatePinWidgets()override;//创建pin的ui显示
virtual void AddPin( const TSharedRef<SGraphPin>& PinToAdd )override;//添加pin,不重写就使用默认的(一个圆点)
protected:
TSharedPtr<class SBox> PinBox;
  • 构造
1
2
3
GraphNode= InNode;//自带的变量,存储对应的UEdGraphNode
this->SetCursor(EMouseCursor::GrabHand);//鼠标样式
this->UpdateGraphNode();
  • 刷新节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void STestNode::UpdateGraphNode()
{
//清空所有的Pin和box
InputPins.Empty();
OutputPins.Empty();
RightNodeBox.Reset();
LeftNodeBox.Reset();
//从style得到icon
const FSlateBrush* nodeIcon=FEditorStyle::GetBrush(TEXT("Graph.StateNode.Icon"));

//用此方法创建一个视图
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew(PinBox,SBox)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SNew(SBorder)
.BorderBackgroundColor_Lambda([&]()
{
FSlateColor color(FLinearColor(1,1,1));
return color;
})
//注释代码可以手动设置图片
//.BorderImage_Lambda([&]()
// {
// const FVector2D iconSize(64.0,64.0);
// return FTestGraphStyle::GetImageBrush(TEXT("ButtonIcon_40x"),iconSize);
// })
[
//添加2个自带的NodeBox,注意顺序需要先左后右
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
SAssignNew(LeftNodeBox,SVerticalBox)
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
SAssignNew(RightNodeBox,SVerticalBox)
]

]

]


];

//设置顶层box尺寸
PinBox->SetWidthOverride(400);
PinBox->SetHeightOverride(600);
//创建PinWidget
CreatePinWidgets();
}
  • 创建Pin的图形

2种方式,可以手动指定Pin,也可以使用系统的Pin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//手动指定所有Pin
void STestNode::CreatePinWidgets()
{
UTestNode_Hello* helloNode=CastChecked<UTestNode_Hello>(GraphNode);
if (helloNode)
{
//全部的Pin都指定为STestPin
for (auto p : helloNode->Pins)
{
TSharedPtr<SGraphPin> pin = SNew(STestPin, p);
pin->SetIsEditable(IsEditable);//把Node的可编辑性赋予Pin
this->AddPin(pin.ToSharedRef());//添加pin
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//使用系统的Pin
//用一个宏快速定义
//注意宏里的newPin对应的是第15行创建的指针变量
//顺序对应的是UEdGraphNode中创建的Pin
#define RESET_PIN(STestPin,GraphPinObj,...)\
newPin = SNew(STestPin,GraphPinObj,__VA_ARGS__);\
newPin->SetIsEditable(IsEditable);\
this->AddPin(newPin.ToSharedRef());
void STestNode::CreatePinWidgets()
{
static TArray<TSharedPtr<FName>> nameList;
nameList.Add(MakeShareable(new FName("player")));
nameList.Add(MakeShareable(new FName("monster")));
nameList.Add(MakeShareable(new FName("ai")));

TSharedPtr<SGraphPin> newPin=nullptr;
RESET_PIN(SGraphPinBool,helloNode->Pins[0]);
RESET_PIN(SGraphPinColor, helloNode->Pins[1]);
RESET_PIN(SGraphPinEnum, helloNode->Pins[2]);
RESET_PIN(SGraphPinExec, helloNode->Pins[3]);
RESET_PIN(SGraphPinNameList, helloNode->Pins[4],nameList);
RESET_PIN(SGraphPinObject, helloNode->Pins[5]);
RESET_PIN(SGraphPinString, helloNode->Pins[6]);
RESET_PIN(SGraphPinVector4, helloNode->Pins[7]);
RESET_PIN(SGraphPinInteger, helloNode->Pins[8]);
RESET_PIN(SGraphPinVector2D, helloNode->Pins[9]);
RESET_PIN(SGraphPinClass, helloNode->Pins[10]);
RESET_PIN(STestPin, helloNode->Pins[11]);
RESET_PIN(STestPin, helloNode->Pins[12]);
RESET_PIN(STestPin, helloNode->Pins[13]);
RESET_PIN(STestPin, helloNode->Pins[14]);
}
  • AddPin

用来设置Pin的显示和布局

如果不重写,就使用系统自带的Pin(非常的小)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void STestNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
//设置owner
PinToAdd->SetOwner(SharedThis(this));
//得到对应的Obj
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
//设置可见性
if (PinObj&&PinObj->bAdvancedView)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
#if CUSTOM_PIN
//如果是自定义的Pin就设置缩放,用系统自带的就不需要了
PinToAdd->SetDesiredSizeScale(FVector2D(16, 16));
#endif

//判断是输入和是输出节点
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
//添加到左边盒子里
LeftNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.0f)
.Padding(20.0f, 0)
[
PinToAdd
];
//添加到InputPins数组里
InputPins.Add(PinToAdd);
}
else if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Output)
{
RightNodeBox->AddSlot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.FillHeight(1.0f)
.Padding(-30.0f, 0.f)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
自定义连线

继承自FConnectionDrawingPolicy

  • 构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//.h
FTestConnectionDrawingPolicy(int32 InBackLayerID, //线ID
int32 InFrontLayerID, //箭头ID
float InZoomFactor, //缩放
const FSlateRect& InClippingRect,//视口裁剪
FSlateWindowElementList& InDrawElements,//绘制元素
UEdGraph* InGraphObj);
/*
protected:
UEdGraph* EdGraphObj;
TMap<UEdGraph*, int32>EdNodeWidgetMap;*/

//cpp
FTestConnectionDrawingPolicy::FTestConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements , UEdGraph* InGraphObj)
:FConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,InZoomFactor,InClippingRect,InDrawElements),EdGraphObj(InGraphObj)
{

}
  • 设置样式
1
2
3
4
5
6
7
8
9
void FTestConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params)
{
Params.WireThickness=3;//宽度
Params.WireColor=FLinearColor::Green;//颜色
if (HoveredPins.Num()>0)
{
ApplyHoverDeemphasis(OutputPin,InputPin, Params.WireThickness = 3, Params.WireColor);//此方法显示拖动效果
}
}
  • 绘制连接

如果不重写就使用默认的贝塞尔曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//绘制直线+流动的泡泡
void FTestConnectionDrawingPolicy::DrawConnection(int32 LayerId, const FVector2D& Start, const FVector2D& End, const FConnectionParams& Params)
{
const FVector2D delta=End-Start;
const FVector2D DirDelta=delta.GetSafeNormal();
//变成直线
FSlateDrawElement::MakeDrawSpaceSpline(
DrawElementsList,
LayerId,
Start,DirDelta,
End,DirDelta,
Params.WireThickness,
ESlateDrawEffect::None,
Params.WireColor
);
//使用气泡
//底线一堆算法就是为了MakeBox
if (bUseBubble)
{
FInterpCurve<float> SplineCurve;
float splineLength=MakeSplineReparamTable(Start,DirDelta,End,DirDelta,SplineCurve);

const float BubbleSpacing=32*ZoomFactor;
const float BubbleSpeed=64*ZoomFactor;
const FVector2D BubbleSize=BubbleImage->ImageSize*ZoomFactor*0.1*Params.WireThickness;

float deltaTime=FPlatformTime::Seconds()-GStartTime;
const float BubbleOffset=FMath::Fmod(deltaTime*BubbleSpeed,BubbleSpacing);
const int32 num=FMath::CeilToInt(splineLength-BubbleSpacing);

for (int32 i=0;i<num;i++)
{
const float dis = float(i)*BubbleSpacing+BubbleOffset;
if (dis<splineLength)
{
const float alpha=SplineCurve.Eval(dis,0);
FVector2D bubblePos=FMath::CubicInterp(Start,DirDelta,End,DirDelta,alpha);
bubblePos-= BubbleSize*0.5;

FSlateDrawElement::MakeBox(DrawElementsList,LayerId,
FPaintGeometry(bubblePos,BubbleSize,ZoomFactor),
BubbleImage,
ESlateDrawEffect::None,
Params.WireColor
);
}
}
}
}

工厂类

可以用工厂类来注册Node,Pin,ConnectionPolicy

重写如下方法

1
2
3
virtual TSharedPtr<class SGraphNode> CreateNode(class UEdGraphNode* Node) const { return NULL; }
virtual TSharedPtr<class SGraphPin> CreatePin(class UEdGraphPin* Pin) const { return NULL; }
virtual class FConnectionDrawingPolicy* CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const { return nullptr; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
TSharedPtr<class SGraphNode> FTestNodeFactory::CreateNode(class UEdGraphNode* Node) const
{
if (UTestNode_Hello* nd=Cast<UTestNode_Hello>(Node))
{
return SNew(STestNode,nd);
}
return nullptr;
}
TSharedPtr<class SGraphPin> FTestNodePinFactory::CreatePin(class UEdGraphPin* Pin) const
{
if (UTestNode_Hello* nd=Cast<UTestNode_Hello>(Pin->GetOuter()))
{
return SNew(STestPin,Pin);
}
return nullptr;
}
class FConnectionDrawingPolicy* FTestConnectionFactory::CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
if (Schema->IsA(UTestGraphSchema::StaticClass()))
{
return new FTestConnectionDrawingPolicy(InBackLayerID,InFrontLayerID,ZoomFactor,InClippingRect,InDrawElements, InGraphObj);
}
return nullptr;
}

然后在模块加载的时候注册,这样就可以去掉在schema内的内容

1
2
3
FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FTestNodeFactory));
FEdGraphUtilities::RegisterVisualPinConnectionFactory(MakeShareable(new FTestConnectionFactory));
FEdGraphUtilities::RegisterVisualPinFactory(MakeShareable(new FTestNodePinFactory));
1
//virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj)const override;

UE4在4.22版本后加入了子系统即Subsystem,子系统是生命周期受控的自动实例化类,非常易用。

概述

目前在4.24版本中,子系统主要分为4大类

  • 引擎UEngineSubsystem

当引擎子系统的模块加载时,子系统将在模块的 Startup() 函数返回后执行 Initialize(),子系统将在模块的 Shutdown() 函数返回后执行 Deinitialize()

通过GEngine访问

1
UMyEngineSubsystem MySubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>();
  • 编辑器UEditorSubsystem

当编辑器子系统的模块加载时,子系统将在模块的 Startup() 函数返回后执行 Initialize(),子系统将在模块的 Shutdown() 函数返回后执行 Deinitialize()

这些子系统可通过GEditor访问

1
UMyEditorSubsystem MySubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>();
  • 游戏实例UGameInstanceSubsystem

生命周期跟随游戏实例的初始化和卸载

可通过UGameInstance访问

1
2
UGameInstance* GameInstance = ...;
UMyGameSubsystem* MySubsystem = GameInstance->GetSubsystem<UMyGameSubsystem>();
  • 本地玩家ULocalPlayerSubsystem

随着本地玩家创建而创建

通过ULocalPlayer访问

1
2
ULocalPlayer* LocalPlayer = ...;
UMyPlayerSubsystem * MySubsystem = LocalPlayer->GetSubsystem();

示例

创建UMyGameInstanceSubsystem继承自UGameInstanceSubsystem

重写基类方法,如下图所示

1
2
3
4
5
6
//返回false就不会自动创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
//初始化
virtual void Initialize(FSubsystemCollectionBase& Collection)override;
//卸载
virtual void Deinitialize()override;
1
2
3
4
5
6
7
8
void UMyGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp,Log,TEXT("MySubsystem Initialize!!!!"));
}
void UMyGameInstanceSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Log, TEXT("MySubsystem Deinitialize!!!!"));
}

另外我们手动创建一个继承自UGameInstance的蓝图类

image-20200803100004102

然后我们在子系统里实现一个测试方法

1
2
3
4
UFUNCTION(BlueprintCallable)
void TestPrint();
UPROPERTY(BlueprintReadWrite,EditAnywhere)
int32 Number=0;
1
2
3
4
void UMyGameInstanceSubsystem::TestPrint()
{
UE_LOG(LogTemp,Log,TEXT("Number= %d !!"),Number);
}

在蓝图里默认直接可以得到子系统如下图

image-20200803100155123

在关卡蓝图里测试

image-20200803100224677

得到结果

image-20200803100258176

由于自己平时在项目开发中经常遇到大批量的资源需要重命名,特别是美术资源如贴图、动画这些

而UE自带的批量操作又没有发现有重命名方法

所以就制作了这个简单的批量重命名的小插件

Github下载链接

  • 动图演示

Rename

说明
  • Prefix前缀
  • Name:修改后的名称
  • StartIndex:在多选的情况下会自动生成序号,此选项为起始序号
  • Suffix:后缀
  • 点击Rename按键就执行重命名

帧资源

上一篇在绘制每一帧都会进行一次CPU与GPU的同步,目的是

  1. GPU未结束命令分配器中所有命令执行之前,不能重置,如果重置,那么GPU当前还未处理的命令就会被清除
  2. 对于常量缓冲区的数据而言,不同步的话会导致数据异常

所以我们用FlushCommandQueue来保持同步

但是有2个缺点导致性能浪费

  1. 每一帧起始阶段GPU无任务,处于闲置状态
  2. 帧结尾CPU处于闲置状态等待GPU完成绘制
帧资源

CPU会比GPU提前两帧工作,以保持GPU可持续工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//存有CPU为构建美珍命令列表所需的资源
//其中的数据将依程序而异,取决于实际绘制所需的资源
struct FrameResource
{
public:

FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount);
FrameResource(const FrameResource& rhs) = delete;
FrameResource& operator=(const FrameResource& rhs) = delete;
~FrameResource();

// CPU未结束命令分配器中所有命令执行之前,不能重置
//因此每一帧都要有他们自己的命令分配器
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc;

//在GPU完成引用此常量缓冲区的命令之前,我们不能对他进行更新
//因此每一帧都要有他们自己的常量缓冲区
std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;
std::unique_ptr<UploadBuffer<ObjectConstants>> ObjectCB = nullptr;

// 通过围栏值将命令标记到此围栏点,这是我们可以检测到GPU是否还在使用这些帧资源
UINT64 Fence = 0;
};
1
2
3
4
5
6
7
8
9
10

FrameResource::FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount)
{
ThrowIfFailed(device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));

PassCB = std::make_unique<UploadBuffer<PassConstants>>(device, passCount, true);
ObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(device, objectCount, true);
}

然后我们的应用程序类将用一个vector容器实例化3个帧资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const int gNumFrameResources = 3;
std::vector<std::unique_ptr<FrameResource>> mFrameResources;
FrameResource* mCurrFrameResource = nullptr;
int mCurrFrameResourceIndex = 0;

void ShapesApp::BuildFrameResources()
{
for(int i = 0; i < gNumFrameResources; ++i)
{
mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
1, (UINT)mAllRitems.size()));
}
}

然后在CPU处理第n帧的算法是如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void ShapesApp::Update(const GameTimer& gt)
{

// 循环获取帧资源数组中的元素
mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();
//GPU是否执行完所有命令?
//如果还没有就令CPU等待,直到GPU完成所有命令的执行并且抵达这个围栏点
if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
1
2
3
4
5
6
7
8
9
10
void ShapesApp::Draw(const GameTimer& gt)
{
//.....构建命令列表

//增加围栏点,将命令标记到此围栏点
mCurrFrameResource->Fence = ++mCurrentFence;
//向命令列表添加一条指令来设置一个新的围栏点
//由于当前的GPU正在执行绘制命令,所以在GPU处理完Signal()函数之前的所有命令以前,并不会设置新的围栏点
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}

这种处理无法完全避免等待的发生

如果GPU速度远大于CPU,,则GPU会处于空闲

反之,CPU会空闲,但是CPU空闲是有利的,因为CPU还需要处理其他算法

采用多帧资源的方法目的是让命令队列一直处于非空状态,即GPU一直有事情可以做

渲染项

单次绘制调用过程中,需要向渲染流水线提交的数据集成为渲染项

我们用一个轻量级的结构体来存储绘制物体所需的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct RenderItem
{
RenderItem() = default;

// 描述了局部空间相对于世界空间的矩阵
//定义了物体位于世界空间的位置,朝向和大小
XMFLOAT4X4 World = MathHelper::Identity4x4();

// 用dirty flag来标志物体的相关数据已经发生改变,意味着我们次数需要更新常量缓冲区
// 由于每个帧资源中都有一个物体常量缓冲区,所以需要对每个帧资源进行更新
//即,当修改物体数据时,应当按NumFramesDirty= gNumFrameReousreces进行设置,从而使每个帧资源都更新
int NumFramesDirty = gNumFrameResources;

// 该索引指向的GPU常量缓冲区对应当前渲染项中的物体常量缓冲区
UINT ObjCBIndex = -1;

MeshGeometry* Geo = nullptr;

//图元拓扑
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

// DrawIndexedInstanced方法的参数
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;
};

渲染过程中所用到的常量数据

  • 扩展常量缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//着色器
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//与之对应的常量缓冲区数据结构
struct PassConstants
{
DirectX::XMFLOAT4X4 View = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 InvView = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 Proj = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 InvProj = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 ViewProj = MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 InvViewProj = MathHelper::Identity4x4();
DirectX::XMFLOAT3 EyePosW = { 0.0f, 0.0f, 0.0f };
float cbPerObjectPad1 = 0.0f;
DirectX::XMFLOAT2 RenderTargetSize = { 0.0f, 0.0f };
DirectX::XMFLOAT2 InvRenderTargetSize = { 0.0f, 0.0f };
float NearZ = 0.0f;
float FarZ = 0.0f;
float TotalTime = 0.0f;
float DeltaTime = 0.0f;
};
  • 刷新常量缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{
auto currObjectCB = mCurrFrameResource->ObjectCB.get();
for(auto& e : mAllRitems)
{
// 只对发生常量改变的缓冲区刷新
if(e->NumFramesDirty > 0)
{
XMMATRIX world = XMLoadFloat4x4(&e->World);

ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));

currObjectCB->CopyData(e->ObjCBIndex, objConstants);

// 还需要对下一个FrameResource更新
e->NumFramesDirty--;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);

XMMATRIX viewProj = XMMatrixMultiply(view, proj);
XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view);
XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj);

XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
mMainPassCB.EyePosW = mEyePos;
mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
mMainPassCB.NearZ = 1.0f;
mMainPassCB.FarZ = 1000.0f;
mMainPassCB.TotalTime = gt.TotalTime();
mMainPassCB.DeltaTime = gt.DeltaTime();

auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}
  • 着色器改变
1
2
3
4
5
6
7
8
9
10
11
12
13
VertexOut VS(VertexIn vin)
{
VertexOut vout;

// Transform to homogeneous clip space.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosH = mul(posW, gViewProj);

// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;

return vout;
}
  • 调整根签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void ShapesApp::BuildRootSignature()
{
// 创建2个描述
CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);

CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);

// 2个成员的描述符表
CD3DX12_ROOT_PARAMETER slotRootParameter[2];

// 创建根CBV
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);


CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
//*************************
}

不同形状的几何体

GeometryGenerator类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class GeometryGenerator
{
public:

using uint16 = std::uint16_t;
using uint32 = std::uint32_t;

struct Vertex
{
Vertex(){}
Vertex(
const DirectX::XMFLOAT3& p,
const DirectX::XMFLOAT3& n,
const DirectX::XMFLOAT3& t,
const DirectX::XMFLOAT2& uv) :
Position(p),
Normal(n),
TangentU(t),
TexC(uv){}
Vertex(
float px, float py, float pz,
float nx, float ny, float nz,
float tx, float ty, float tz,
float u, float v) :
Position(px,py,pz),
Normal(nx,ny,nz),
TangentU(tx, ty, tz),
TexC(u,v){}

DirectX::XMFLOAT3 Position;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT3 TangentU;
DirectX::XMFLOAT2 TexC;
};

struct MeshData
{
std::vector<Vertex> Vertices;
std::vector<uint32> Indices32;

std::vector<uint16>& GetIndices16()
{
if(mIndices16.empty())
{
mIndices16.resize(Indices32.size());
for(size_t i = 0; i < Indices32.size(); ++i)
mIndices16[i] = static_cast<uint16>(Indices32[i]);
}

return mIndices16;
}

private:
std::vector<uint16> mIndices16;
};

//创建盒子
MeshData CreateBox(float width, float height, float depth, uint32 numSubdivisions);

//创建球体
MeshData CreateSphere(float radius, uint32 sliceCount, uint32 stackCount);
//创建以给定的半径创建一个以原点为中心的地层
MeshData CreateGeosphere(float radius, uint32 numSubdivisions);

///创建一个平行于y轴并以原点为中心的圆柱体。
///上下半径可以变化以形成各种圆锥形状,而不是真实的圆柱体。 切片和堆栈参数控制镶嵌的程度。
MeshData CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount);

//在xz平面中以m行和n列为中心创建一个mxn网格,在具有指定宽度和深度的原点。
MeshData CreateGrid(float width, float depth, uint32 m, uint32 n);

//创建与屏幕对齐的四边形。 这对于后期处理和屏幕效果很有用。
MeshData CreateQuad(float x, float y, float w, float h, float depth);

private:
void Subdivide(MeshData& meshData);
Vertex MidPoint(const Vertex& v0, const Vertex& v1);
void BuildCylinderTopCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData);
void BuildCylinderBottomCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData);
};

细分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void GeometryGenerator::Subdivide(MeshData& meshData)
{
// v1
// *
// / \
// / \
// m0*-----*m1
// / \ / \
// / \ / \
// *-----*-----*
// v0 m2 v2
//****我里个去*****

//保存一份拷贝
MeshData inputCopy=meshData;
meshData.Vertices.resize(0);
meshData.Indices32.reserve(0);//清空

uint32 numTris =(uint32)inputCopy.Indices32.size()/3; //三角形数量
for (uint32 i=0;i<numTris;++i)
{
Vertex v0 = inputCopy.Vertices[inputCopy.Indices32[i * 3 + 0]];
Vertex v1 = inputCopy.Vertices[inputCopy.Indices32[i * 3 + 1]];
Vertex v2 = inputCopy.Vertices[inputCopy.Indices32[i * 3 + 2]];


//创建中间点
Vertex m0 = MidPoint(v0, v1);
Vertex m1 = MidPoint(v1, v2);
Vertex m2 = MidPoint(v0, v2);

//添加新的几何体
meshData.Vertices.push_back(v0); // 0
meshData.Vertices.push_back(v1); // 1
meshData.Vertices.push_back(v2); // 2
meshData.Vertices.push_back(m0); // 3
meshData.Vertices.push_back(m1); // 4
meshData.Vertices.push_back(m2); // 5

meshData.Indices32.push_back(i * 6 + 0);
meshData.Indices32.push_back(i * 6 + 3);
meshData.Indices32.push_back(i * 6 + 5);

meshData.Indices32.push_back(i * 6 + 3);
meshData.Indices32.push_back(i * 6 + 4);
meshData.Indices32.push_back(i * 6 + 5);

meshData.Indices32.push_back(i * 6 + 5);
meshData.Indices32.push_back(i * 6 + 4);
meshData.Indices32.push_back(i * 6 + 2);

meshData.Indices32.push_back(i * 6 + 3);
meshData.Indices32.push_back(i * 6 + 1);
meshData.Indices32.push_back(i * 6 + 4);
}

}
中间点的生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Vertex GeometryGenerator::MidPoint(const Vertex& v0, const Vertex& v1)
{
XMVECTOR p0 = XMLoadFloat3(&v0.Position);
XMVECTOR p1 = XMLoadFloat3(&v1.Position);

XMVECTOR n0 = XMLoadFloat3(&v0.Normal);
XMVECTOR n1 = XMLoadFloat3(&v1.Normal);

XMVECTOR tan0 = XMLoadFloat3(&v0.TangentU);
XMVECTOR tan1 = XMLoadFloat3(&v1.TangentU);

XMVECTOR tex0 = XMLoadFloat2(&v0.TexC);
XMVECTOR tex1 = XMLoadFloat2(&v1.TexC);

//计算中间数据
XMVECTOR pos=0.5f*(p0+p1);
XMVECTOR normal=XMVector3Normalize(0.5f*(n0+n1));
XMVECTOR tangent = XMVector3Normalize(0.5f*(tan0 + tan1));
XMVECTOR tex = 0.5f*(tex0 + tex1);

Vertex v;
XMStoreFloat3(&v.Position,pos);
XMStoreFloat3(&v.Normal, normal);
XMStoreFloat3(&v.TangentU, tangent);
XMStoreFloat2(&v.TexC, tex);

return v;

}
盒子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth, uint32 numSubdivisions)
{
MeshData meshData;

/*
构建顶点
*/

Vertex v[24];
float w2 = 0.5f*width;
float h2 = 0.5f*height;
float d2 = 0.5f*depth;

// 前面
v[0] = Vertex(-w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[1] = Vertex(-w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[2] = Vertex(+w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
v[3] = Vertex(+w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

// 后面
v[4] = Vertex(-w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[5] = Vertex(+w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[6] = Vertex(+w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[7] = Vertex(-w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

// 顶部面
v[8] = Vertex(-w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[9] = Vertex(-w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[10] = Vertex(+w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
v[11] = Vertex(+w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

// 底部面
v[12] = Vertex(-w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[13] = Vertex(+w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[14] = Vertex(+w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[15] = Vertex(-w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

// 左边面
v[16] = Vertex(-w2, -h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[17] = Vertex(-w2, +h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[18] = Vertex(-w2, +h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[19] = Vertex(-w2, -h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

// 右边面
v[20] = Vertex(+w2, -h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
v[21] = Vertex(+w2, +h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
v[22] = Vertex(+w2, +h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
v[23] = Vertex(+w2, -h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);


meshData.Vertices.assign(&v[0],&v[24]);



/*
构建序列
*/

uint32 i[36];

// Fill in the front face index data
i[0] = 0; i[1] = 1; i[2] = 2;
i[3] = 0; i[4] = 2; i[5] = 3;

// Fill in the back face index data
i[6] = 4; i[7] = 5; i[8] = 6;
i[9] = 4; i[10] = 6; i[11] = 7;

// Fill in the top face index data
i[12] = 8; i[13] = 9; i[14] = 10;
i[15] = 8; i[16] = 10; i[17] = 11;

// Fill in the bottom face index data
i[18] = 12; i[19] = 13; i[20] = 14;
i[21] = 12; i[22] = 14; i[23] = 15;

// Fill in the left face index data
i[24] = 16; i[25] = 17; i[26] = 18;
i[27] = 16; i[28] = 18; i[29] = 19;

// Fill in the right face index data
i[30] = 20; i[31] = 21; i[32] = 22;
i[33] = 20; i[34] = 22; i[35] = 23;

meshData.Indices32.assign(&i[0],&i[36]);


//细分
numSubdivisions = std::min<uint32>(numSubdivisions,6u);

for (uint32 i = 0;i<numSubdivisions;++i)
{
Subdivide(meshData);//细分
}
return meshData;
}
球体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
GeometryGenerator::MeshData GeometryGenerator::CreateSphere(float radius, uint32 sliceCount, uint32 stackCount)
{
MeshData meshData;

Vertex topVertex(0.0f,+radius, 0.0f, 0.0f, +1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);//顶部顶点
Vertex bottomVertex(0.0f, -radius, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);//底部顶点

meshData.Vertices.push_back(topVertex);//添加顶部顶点

float phiStep = XM_PI/stackCount;//水平面分段
float thetaStep=2.0f*XM_PI/sliceCount;//竖直面分段

for (uint32 i=0;i<=stackCount-1;++i)
{
float phi = i*phiStep;//yaw角度

for (uint32 j = 0 ;j<=sliceCount;++j)
{
float theta=j*thetaStep;//pitch角度

Vertex v;

v.Position.x = radius*sinf(phi)*cosf(theta);
v.Position.y = radius * cosf(phi);
v.Position.z = radius * sinf(phi)*sinf(theta);

v.TangentU.x = -radius * sinf(phi)*sinf(theta);
v.TangentU.y = 0.0f;
v.TangentU.z = +radius * sinf(phi)*cosf(theta);

XMVECTOR T = XMLoadFloat3(&v.TangentU);
XMStoreFloat3(&v.TangentU, XMVector3Normalize(T));

XMVECTOR p = XMLoadFloat3(&v.Position);
XMStoreFloat3(&v.Normal, XMVector3Normalize(p));

v.TexC.x = theta / XM_2PI;
v.TexC.y = phi / XM_PI;

meshData.Vertices.push_back(v);
}
}

//最底下一个顶点
meshData.Vertices.push_back(bottomVertex);




//顶部面所有点的索引
for (uint32 i = 1; i <= sliceCount; ++i)
{
meshData.Indices32.push_back(0);
meshData.Indices32.push_back(i + 1);
meshData.Indices32.push_back(i);
}

uint32 baseIndex = 1;
uint32 ringVertexCount = sliceCount + 1;
//高度细分数量-2,因为顶部底部不算在内
for (uint32 i = 0; i < stackCount - 2; ++i)
{
for (uint32 j = 0; j < sliceCount; ++j)
{
meshData.Indices32.push_back(baseIndex + i * ringVertexCount + j);
meshData.Indices32.push_back(baseIndex + i * ringVertexCount + j + 1);
meshData.Indices32.push_back(baseIndex + (i + 1)*ringVertexCount + j);

meshData.Indices32.push_back(baseIndex + (i + 1)*ringVertexCount + j);
meshData.Indices32.push_back(baseIndex + i * ringVertexCount + j + 1);
meshData.Indices32.push_back(baseIndex + (i + 1)*ringVertexCount + j + 1);
}
}

//底部的点的序号
uint32 southPoleIndex = (uint32)meshData.Vertices.size() - 1;
baseIndex = southPoleIndex - ringVertexCount;
//底部面所有索引
for (uint32 i = 0; i < sliceCount; ++i)
{
meshData.Indices32.push_back(southPoleIndex);
meshData.Indices32.push_back(baseIndex + i);
meshData.Indices32.push_back(baseIndex + i + 1);
}

return meshData;
}
柱体网格

image-20200704142449983

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
GeometryGenerator::MeshData GeometryGenerator::CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount)
{

MeshData meshData;
//高度递增
float stackHeight = height / stackCount;
//从底部开始,每一次半径递增
float radiusStep = (topRadius - bottomRadius) / stackCount;
//环数
uint32 rightCount = stackCount +1;
for (uint32 i=0;i<rightCount;++i)
{
float y = -0.5f*height + i*stackHeight;//从底部开始往上加高度
float r = bottomRadius+i*radiusStep;

float dTheta= 2.0f*XM_PI/sliceCount;//360角度范围的弧度,水平角度yaw
for (uint32 j=0;j<=sliceCount;++j)
{
Vertex vertex;

float c = cosf(j*dTheta);
float s = sinf(j*dTheta);

vertex.Position=XMFLOAT3(r*c,y,r*s);
vertex.TexC.x = (float)j/sliceCount;
vertex.TexC.y = 1.0f - (float)i / stackCount;
vertex.TangentU = XMFLOAT3(-s, 0.0f, c);
float dr = bottomRadius - topRadius;
XMFLOAT3 bitangent(dr*c, -height, dr*s);
XMVECTOR T = XMLoadFloat3(&vertex.TangentU);
XMVECTOR B = XMLoadFloat3(&bitangent);
XMVECTOR N = XMVector3Normalize(XMVector3Cross(T, B));
XMStoreFloat3(&vertex.Normal, N);

meshData.Vertices.push_back(vertex);
}
}


uint32 ringVertexCount = sliceCount + 1;

for (uint32 i = 0; i < stackCount; ++i)
{
for (uint32 j = 0; j < sliceCount; ++j)
{
meshData.Indices32.push_back(i*ringVertexCount + j);
meshData.Indices32.push_back((i + 1)*ringVertexCount + j);
meshData.Indices32.push_back((i + 1)*ringVertexCount + j + 1);

meshData.Indices32.push_back(i*ringVertexCount + j);
meshData.Indices32.push_back((i + 1)*ringVertexCount + j + 1);
meshData.Indices32.push_back(i*ringVertexCount + j + 1);
}
}

BuildCylinderTopCap(bottomRadius, topRadius, height, sliceCount, stackCount, meshData);
BuildCylinderBottomCap(bottomRadius, topRadius, height, sliceCount, stackCount, meshData);

return meshData;
}

每一个环最后一个顶点与第一个顶点是重合的,但是纹理坐标不同,只有这样才能保证绘制正确纹理

构建顶部和底部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void GeometryGenerator::BuildCylinderTopCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData)
{
uint32 baseIndex = (uint32)meshData.Vertices.size();

float y = 0.5f*height;
float dTheta = 2.0f*XM_PI / sliceCount;

//顶点重复,因为纹理坐标和法线不同。
for (uint32 i = 0; i <= sliceCount; ++i)
{
float x = topRadius * cosf(i*dTheta);
float z = topRadius * sinf(i*dTheta);

//按高度缩小以尝试使顶盖纹理坐标区域与底面成比例??
float u = x / height + 0.5f;
float v = z / height + 0.5f;

meshData.Vertices.push_back(Vertex(x, y, z, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, u, v));
}

// 中心点
meshData.Vertices.push_back(Vertex(0.0f, y, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f));

// 中心序号
uint32 centerIndex = (uint32)meshData.Vertices.size() - 1;

for (uint32 i = 0; i < sliceCount; ++i)
{
meshData.Indices32.push_back(centerIndex);
meshData.Indices32.push_back(baseIndex + i + 1);
meshData.Indices32.push_back(baseIndex + i);
}
}

void GeometryGenerator::BuildCylinderBottomCap(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount, MeshData& meshData)
{
uint32 baseIndex = (uint32)meshData.Vertices.size();
float y = -0.5f*height;


float dTheta = 2.0f*XM_PI / sliceCount;
for (uint32 i = 0; i <= sliceCount; ++i)
{
float x = bottomRadius * cosf(i*dTheta);
float z = bottomRadius * sinf(i*dTheta);


float u = x / height + 0.5f;
float v = z / height + 0.5f;

meshData.Vertices.push_back(Vertex(x, y, z, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, u, v));
}


meshData.Vertices.push_back(Vertex(0.0f, y, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f));

uint32 centerIndex = (uint32)meshData.Vertices.size() - 1;

for (uint32 i = 0; i < sliceCount; ++i)
{
meshData.Indices32.push_back(centerIndex);
meshData.Indices32.push_back(baseIndex + i);
meshData.Indices32.push_back(baseIndex + i + 1);
}
}
几何球体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
GeometryGenerator::MeshData GeometryGenerator::CreateGeosphere(float radius, uint32 numSubdivisions)
{
MeshData meshData;

// 为细分数设置上限。
numSubdivisions = std::min<uint32>(numSubdivisions, 6u);

// 通过镶嵌二十面体来近似一个球体。

const float X = 0.525731f;
const float Z = 0.850651f;

XMFLOAT3 pos[12] =
{
XMFLOAT3(-X, 0.0f, Z), XMFLOAT3(X, 0.0f, Z),
XMFLOAT3(-X, 0.0f, -Z), XMFLOAT3(X, 0.0f, -Z),
XMFLOAT3(0.0f, Z, X), XMFLOAT3(0.0f, Z, -X),
XMFLOAT3(0.0f, -Z, X), XMFLOAT3(0.0f, -Z, -X),
XMFLOAT3(Z, X, 0.0f), XMFLOAT3(-Z, X, 0.0f),
XMFLOAT3(Z, -X, 0.0f), XMFLOAT3(-Z, -X, 0.0f)
};

uint32 k[60] =
{
1,4,0, 4,9,0, 4,5,9, 8,5,4, 1,8,4,
1,10,8, 10,3,8, 8,3,5, 3,2,5, 3,7,2,
3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
10,1,6, 11,0,9, 2,11,9, 5,2,9, 11,2,7
};

meshData.Vertices.resize(12);
meshData.Indices32.assign(&k[0], &k[60]);

for(uint32 i = 0; i < 12; ++i)
meshData.Vertices[i].Position = pos[i];

for(uint32 i = 0; i < numSubdivisions; ++i)
Subdivide(meshData);

//将顶点投影到球体和比例上。
for(uint32 i = 0; i < meshData.Vertices.size(); ++i)
{
// 投影到单位球体上。
XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&meshData.Vertices[i].Position));

// 投影到球体上。
XMVECTOR p = radius*n;

XMStoreFloat3(&meshData.Vertices[i].Position, p);
XMStoreFloat3(&meshData.Vertices[i].Normal, n);

// 从球坐标导出纹理坐标。
float theta = atan2f(meshData.Vertices[i].Position.z, meshData.Vertices[i].Position.x);

// 输入[0,2pi]。
if(theta < 0.0f)
theta += XM_2PI;

float phi = acosf(meshData.Vertices[i].Position.y / radius);

meshData.Vertices[i].TexC.x = theta/XM_2PI;
meshData.Vertices[i].TexC.y = phi/XM_PI;

// P关于theta的偏导数
meshData.Vertices[i].TangentU.x = -radius*sinf(phi)*sinf(theta);
meshData.Vertices[i].TangentU.y = 0.0f;
meshData.Vertices[i].TangentU.z = +radius*sinf(phi)*cosf(theta);

XMVECTOR T = XMLoadFloat3(&meshData.Vertices[i].TangentU);
XMStoreFloat3(&meshData.Vertices[i].TangentU, XMVector3Normalize(T));
}

return meshData;
}

绘制几何体示例

我们已经创建一个由FrameResource类型元素所构成的向量,每个FrameReousrce中都有上传缓冲区,用于为场景中每一个渲染项存储渲染过程常量和物体常量数据。

如果有3个帧资源和n个渲染项,那么我们需要创建3n个物体常量缓冲区(object constant buffer)以及3个渲染过程常量缓冲区(pass 从constant buffer),因此我们需要创建3(n+1)个常量缓冲区视图。所以我们要修改CBV堆以容纳额外的描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
#include "../Common/UploadBuffer.h"
#include "DXApp.h"
#include "../Common/MathHelper.h"
#include "GeometryGenerator.h"
#include "FrameResource.h"

using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

const int gNumFrameResources = 3;

struct RenderItem
{
RenderItem() = default;

// 描述了局部空间相对于世界空间的矩阵
//定义了物体位于世界空间的位置,朝向和大小
XMFLOAT4X4 World = MathHelper::Identity4x4();

// 用dirty flag来标志物体的相关数据已经发生改变,意味着我们次数需要更新常量缓冲区
// 由于每个帧资源中都有一个物体常量缓冲区,所以需要对每个帧资源进行更新
//即,当修改物体数据时,应当按NumFramesDirty= gNumFrameReousreces进行设置,从而使每个帧资源都更新
int NumFramesDirty = gNumFrameResources;

//索引到此渲染项目对应于ObjectCB的GPU常量缓冲区。
UINT ObjCBIndex = -1;

MeshGeometry* Geo = nullptr;

//图元拓扑
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

// DrawIndexedInstanced方法的参数
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;
};


class ShapesApp : public DXApp
{
public:
ShapesApp(HINSTANCE hInstance);
ShapesApp(const ShapesApp& rhs) = delete;
ShapesApp& operator=(const ShapesApp& rhs) = delete;
~ShapesApp();
virtual bool Initialize()override;

private:
virtual void OnResize()override;
virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;

virtual void OnMouseDown(WPARAM btnState, int x, int y)override;
virtual void OnMouseUp(WPARAM btnState, int x, int y)override;
virtual void OnMouseMove(WPARAM btnState, int x, int y)override;

void OnKeyboardInput(const GameTimer& gt);
void UpdateCamera(const GameTimer& gt);
void UpdateObjectCBs(const GameTimer& gt);
void UpdateMainPassCB(const GameTimer& gt);

void BuildDescriptorHeaps();
void BuildConstantBufferViews();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildShapeGeometry();
void BuildPSOs();
void BuildFrameResources();
void BuildRenderItems();
void DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems);

private:

std::vector<std::unique_ptr<FrameResource>> mFrameResources;//帧资源数组
FrameResource* mCurrFrameResource = nullptr;//当前帧资源
int mCurrFrameResourceIndex = 0;//当前帧资源序号

ComPtr<ID3D12RootSignature> mRootSignature = nullptr;//根签名
ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
ComPtr<ID3D12DescriptorHeap> mSrvDescriptorHeap = nullptr;

std::unordered_map<std::string, std::unique_ptr<MeshGeometry>> mGeometries;//几何体哈希表
std::unordered_map<std::string, ComPtr<ID3DBlob>> mShaders;//着色器表
std::unordered_map<std::string, ComPtr<ID3D12PipelineState>> mPSOs;//流水线对象表

std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

std::vector<std::unique_ptr<RenderItem>> mAllRitems;

std::vector<RenderItem*> mOpaqueRitems;

PassConstants mMainPassCB;

UINT mPassCbvOffset = 0;

bool mIsWireframe = false;

XMFLOAT3 mEyePos = { 0.0f, 0.0f, 0.0f };
XMFLOAT4X4 mView = MathHelper::Identity4x4();
XMFLOAT4X4 mProj = MathHelper::Identity4x4();

float mTheta = 1.5f*XM_PI;
float mPhi = 0.2f*XM_PI;
float mRadius = 15.0f;

POINT mLastMousePos;
};

ShapesApp::ShapesApp(HINSTANCE hInstance):DXApp(hInstance)
{

}

ShapesApp::~ShapesApp()
{
if (md3dDevice!=nullptr)
{
FlushCommandQueue();
}
}

bool ShapesApp::Initialize()
{
if (!DXApp::Initialize())
{
return false;
}


ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

BuildRootSignature();
BuildShadersAndInputLayout();
BuildShapeGeometry();
BuildRenderItems();
BuildFrameResources();
BuildDescriptorHeaps();
BuildConstantBufferViews();
BuildPSOs();

ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

FlushCommandQueue();

return true;



return true;
}




void ShapesApp::OnResize()
{
DXApp::OnResize();


XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
}

void ShapesApp::Update(const GameTimer & gt)
{
OnKeyboardInput(gt);
UpdateCamera(gt);
mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

//GPU是否执行完所有命令?
//如果还没有就令CPU等待,直到GPU完成所有命令的执行并且抵达这个围栏点
if (mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}

UpdateObjectCBs(gt);
UpdateMainPassCB(gt);
}

void ShapesApp::Draw(const GameTimer & gt)
{
auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;

// Reuse the memory associated with command recording.
// We can only reset when the associated command lists have finished execution on the GPU.
ThrowIfFailed(cmdListAlloc->Reset());

// A command list can be reset after it has been added to the command queue via ExecuteCommandList.
// Reusing the command list reuses memory.
if (mIsWireframe)
{
ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque_wireframe"].Get()));
}
else
{
ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get()));
}

mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);

// 指示资源使用情况的状态转换。
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

// 清除后缓冲区和深度缓冲区。
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

// 指定我们要渲染到的缓冲区。
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

int passCbvIndex = mPassCbvOffset + mCurrFrameResourceIndex;
auto passCbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
passCbvHandle.Offset(passCbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(1, passCbvHandle);

DrawRenderItems(mCommandList.Get(), mOpaqueRitems);

// 指示资源使用情况的状态转换。
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));


ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

// 扩展围栏值以将命令标记到该围栏点。
mCurrFrameResource->Fence = ++mCurrentFence;

// 将指令添加到命令队列以设置新的围栏点。
//因为我们在GPU时间轴上,所以新的隔离点将不会
//设置直到GPU完成处理此Signal()之前的所有命令为止。
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}

void ShapesApp::OnMouseDown(WPARAM btnState, int x, int y)
{
mLastMousePos.x = x;
mLastMousePos.y = y;

SetCapture(mhMainWnd);
}

void ShapesApp::OnMouseUp(WPARAM btnState, int x, int y)
{

ReleaseCapture();
}

void ShapesApp::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != 0)
{

float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

mTheta -= dx;
mPhi -= dy;


mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if ((btnState & MK_RBUTTON) != 0)
{

float dx = 0.05f*static_cast<float>(x - mLastMousePos.x);
float dy = 0.05f*static_cast<float>(y - mLastMousePos.y);

mRadius += dx - dy;

mRadius = MathHelper::Clamp(mRadius, 5.0f, 150.0f);
}

mLastMousePos.x = x;
mLastMousePos.y = y;
}

void ShapesApp::OnKeyboardInput(const GameTimer& gt)
{
if (GetAsyncKeyState('1') & 0x8000)
mIsWireframe = true;
else
mIsWireframe = false;
}

void ShapesApp::UpdateCamera(const GameTimer& gt)
{
mEyePos.x = mRadius * sinf(mPhi)*cosf(mTheta);
mEyePos.z = mRadius * sinf(mPhi)*sinf(mTheta);
mEyePos.y = mRadius * cosf(mPhi);

// Build the view matrix.
XMVECTOR pos = XMVectorSet(mEyePos.x, mEyePos.y, mEyePos.z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);
}

void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{

auto currObjectCB = mCurrFrameResource->ObjectCB.get();
for (auto& e : mAllRitems)
{
//仅在常量更改后才更新cbuffer数据。
//需要针对每个框架资源进行跟踪。
if (e->NumFramesDirty > 0)
{
XMMATRIX world = XMLoadFloat4x4(&e->World);

ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));

currObjectCB->CopyData(e->ObjCBIndex, objConstants);


e->NumFramesDirty--;
}
}
}

void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);

XMMATRIX viewProj = XMMatrixMultiply(view, proj);
XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view);
XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj);

XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
mMainPassCB.EyePosW = mEyePos;
mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
mMainPassCB.NearZ = 1.0f;
mMainPassCB.FarZ = 1000.0f;
mMainPassCB.TotalTime = gt.TotalTime();
mMainPassCB.DeltaTime = gt.DeltaTime();

auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}
/*我们已经创建一个由FrameResource类型元素所构成的向量,每个FrameReousrce中都有上传缓冲区,用于为场景中每一个渲染项存储渲染过程常量和物体常量数据。

如果有3个帧资源和n个渲染项,那么我们需要创建3n个物体常量缓冲区(object constant buffer)以及3个渲染过程常量缓冲区(pass 从constant buffer),因此我们需要创建3(n+1)个常量缓冲区视图。所以我们要修改CBV堆以容纳额外的描述符*/
void ShapesApp::BuildDescriptorHeaps()
{
UINT objCount = (UINT)mOpaqueRitems.size();
//每个帧资源的每个对象都需要一个CBV描述符,每个帧资源的perPass CBV +1。
UINT numDescriptors = (objCount+1)*gNumFrameResources;
//将偏移量保存到通过CBV的起点。 这是最后3个描述符。
mPassCbvOffset=objCount*gNumFrameResources;

//创建描述符堆
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = numDescriptors;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap)));

}
//我们可以使用下列代码来填充CBV堆了,其中0-n-1描述符为第0个帧资源的物体CBV……3n、3n + 1、3n + 2分别为帧资源的渲染过程CBV
void ShapesApp::BuildConstantBufferViews()
{
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

UINT objCount = (UINT)mOpaqueRitems.size();
//每个帧资源的每个对象都需要一个CBV描述符。
for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
{
auto objectCB = mFrameResources[frameIndex]->ObjectCB->Resource();
for (UINT i = 0; i < objCount; ++i)
{
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = objectCB->GetGPUVirtualAddress();

//缓冲区中第i个对象常量缓冲区的偏移量。
cbAddress += i * objCBByteSize;

//偏移量到描述符堆中的对象cbv。
int heapIndex = frameIndex * objCount + i;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
//通过调用ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart方法可以获得堆中的第一个描述符的句柄,但是现在堆中存储的不是一个描述符了,如果想要访问到堆中的其他描述符,必须进行偏移
handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = objCBByteSize;

md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
}
}

UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));

//最后三个描述符是每个帧资源的传递CBV。
for (int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
{
auto passCB = mFrameResources[frameIndex]->PassCB->Resource();
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = passCB->GetGPUVirtualAddress();

// 偏移到描述符堆中的传递cbv。
int heapIndex = mPassCbvOffset + frameIndex;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = passCBByteSize;

md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
}
}

void ShapesApp::BuildRootSignature()
{
// 创建2个描述
CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);

CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);

// 2个成员的描述符表
CD3DX12_ROOT_PARAMETER slotRootParameter[2];

// Create root CBVs.
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);

// 创建根CBV
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

if (errorBlob != nullptr)
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);

ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

void ShapesApp::BuildShadersAndInputLayout()
{
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\color7.hlsl", nullptr, "VS", "vs_5_1");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\color7.hlsl", nullptr, "PS", "ps_5_1");

mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
}

void ShapesApp::BuildShapeGeometry()
{
GeometryGenerator geoGen;
GeometryGenerator::MeshData box = geoGen.CreateBox(1.5f, 0.5f, 1.5f, 3);
GeometryGenerator::MeshData grid = geoGen.CreateGrid(20.0f, 30.0f, 60, 40);
GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 20, 20);
GeometryGenerator::MeshData cylinder = geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20);

//顶点偏移量缓存到级联的顶点缓冲区中的每个对象。
UINT boxVertexOffset = 0;
UINT gridVertexOffset = (UINT)box.Vertices.size();
UINT sphereVertexOffset = gridVertexOffset + (UINT)grid.Vertices.size();
UINT cylinderVertexOffset = sphereVertexOffset + (UINT)sphere.Vertices.size();
//将每个对象的起始索引缓存在串联索引缓冲区中。
UINT boxIndexOffset = 0;
UINT gridIndexOffset = (UINT)box.Indices32.size();
UINT sphereIndexOffset = gridIndexOffset + (UINT)grid.Indices32.size();
UINT cylinderIndexOffset = sphereIndexOffset + (UINT)sphere.Indices32.size();

SubmeshGeometry boxSubmesh;
boxSubmesh.IndexCount = (UINT)box.Indices32.size();
boxSubmesh.StartIndexLocation = boxIndexOffset;
boxSubmesh.BaseVertexLocation = boxVertexOffset;

SubmeshGeometry gridSubmesh;
gridSubmesh.IndexCount = (UINT)grid.Indices32.size();
gridSubmesh.StartIndexLocation = gridIndexOffset;
gridSubmesh.BaseVertexLocation = gridVertexOffset;

SubmeshGeometry sphereSubmesh;
sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size();
sphereSubmesh.StartIndexLocation = sphereIndexOffset;
sphereSubmesh.BaseVertexLocation = sphereVertexOffset;

SubmeshGeometry cylinderSubmesh;
cylinderSubmesh.IndexCount = (UINT)cylinder.Indices32.size();
cylinderSubmesh.StartIndexLocation = cylinderIndexOffset;
cylinderSubmesh.BaseVertexLocation = cylinderVertexOffset;

//顶点数总计
auto totalVertexCount =
box.Vertices.size() +
grid.Vertices.size() +
sphere.Vertices.size() +
cylinder.Vertices.size();

std::vector<Vertex> vertices(totalVertexCount);

//添加所有顶点和颜色数据
UINT k = 0;
for (size_t i = 0; i < box.Vertices.size(); ++i, ++k)
{
vertices[k].Pos = box.Vertices[i].Position;
vertices[k].Color = XMFLOAT4(DirectX::Colors::Blue);
}

for (size_t i = 0; i < grid.Vertices.size(); ++i, ++k)
{
vertices[k].Pos = grid.Vertices[i].Position;
vertices[k].Color = XMFLOAT4(DirectX::Colors::ForestGreen);
}

for (size_t i = 0; i < sphere.Vertices.size(); ++i, ++k)
{
vertices[k].Pos = sphere.Vertices[i].Position;
vertices[k].Color = XMFLOAT4(DirectX::Colors::Crimson);
}

for (size_t i = 0; i < cylinder.Vertices.size(); ++i, ++k)
{
vertices[k].Pos = cylinder.Vertices[i].Position;
vertices[k].Color = XMFLOAT4(DirectX::Colors::SteelBlue);
}

//添加所有索引
std::vector<std::uint16_t> indices;
indices.insert(indices.end(), std::begin(box.GetIndices16()), std::end(box.GetIndices16()));
indices.insert(indices.end(), std::begin(grid.GetIndices16()), std::end(grid.GetIndices16()));
indices.insert(indices.end(), std::begin(sphere.GetIndices16()), std::end(sphere.GetIndices16()));
indices.insert(indices.end(), std::begin(cylinder.GetIndices16()), std::end(cylinder.GetIndices16()));

const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

auto geo = std::make_unique<MeshGeometry>();
geo->Name = "shapeGeo";

ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);

geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);


geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;

geo->DrawArgs["box"] = boxSubmesh;
geo->DrawArgs["grid"] = gridSubmesh;
geo->DrawArgs["sphere"] = sphereSubmesh;
geo->DrawArgs["cylinder"] = cylinderSubmesh;

mGeometries[geo->Name] = std::move(geo);
}

void ShapesApp::BuildPSOs()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;


ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
opaquePsoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
opaquePsoDesc.pRootSignature = mRootSignature.Get();
opaquePsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders["standardVS"]->GetBufferPointer()),
mShaders["standardVS"]->GetBufferSize()
};
opaquePsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["opaquePS"]->GetBufferPointer()),
mShaders["opaquePS"]->GetBufferSize()
};
opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
opaquePsoDesc.SampleMask = UINT_MAX;
opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
opaquePsoDesc.NumRenderTargets = 1;
opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
opaquePsoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));


D3D12_GRAPHICS_PIPELINE_STATE_DESC opaqueWireframePsoDesc = opaquePsoDesc;
opaqueWireframePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;//网格显示
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaqueWireframePsoDesc, IID_PPV_ARGS(&mPSOs["opaque_wireframe"])));
}

void ShapesApp::BuildFrameResources()
{
for (int i = 0; i < gNumFrameResources; ++i)
{
mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
1, (UINT)mAllRitems.size()));
}
}

void ShapesApp::BuildRenderItems()
{
auto boxRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&boxRitem->World, XMMatrixScaling(2.0f, 2.0f, 2.0f)*XMMatrixTranslation(0.0f, 0.5f, 0.0f));
boxRitem->ObjCBIndex = 0;
boxRitem->Geo = mGeometries["shapeGeo"].get();
boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;
boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;
boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;
mAllRitems.push_back(std::move(boxRitem));

auto gridRitem = std::make_unique<RenderItem>();
gridRitem->World = MathHelper::Identity4x4();
gridRitem->ObjCBIndex = 1;
gridRitem->Geo = mGeometries["shapeGeo"].get();
gridRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
mAllRitems.push_back(std::move(gridRitem));

UINT objCBIndex = 2;
for (int i = 0; i < 5; ++i)
{
auto leftCylRitem = std::make_unique<RenderItem>();
auto rightCylRitem = std::make_unique<RenderItem>();
auto leftSphereRitem = std::make_unique<RenderItem>();
auto rightSphereRitem = std::make_unique<RenderItem>();

XMMATRIX leftCylWorld = XMMatrixTranslation(-5.0f, 1.5f, -10.0f + i * 5.0f);
XMMATRIX rightCylWorld = XMMatrixTranslation(+5.0f, 1.5f, -10.0f + i * 5.0f);

XMMATRIX leftSphereWorld = XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i * 5.0f);
XMMATRIX rightSphereWorld = XMMatrixTranslation(+5.0f, 3.5f, -10.0f + i * 5.0f);

XMStoreFloat4x4(&leftCylRitem->World, rightCylWorld);
leftCylRitem->ObjCBIndex = objCBIndex++;
leftCylRitem->Geo = mGeometries["shapeGeo"].get();
leftCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
leftCylRitem->IndexCount = leftCylRitem->Geo->DrawArgs["cylinder"].IndexCount;
leftCylRitem->StartIndexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
leftCylRitem->BaseVertexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;

XMStoreFloat4x4(&rightCylRitem->World, leftCylWorld);
rightCylRitem->ObjCBIndex = objCBIndex++;
rightCylRitem->Geo = mGeometries["shapeGeo"].get();
rightCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightCylRitem->IndexCount = rightCylRitem->Geo->DrawArgs["cylinder"].IndexCount;
rightCylRitem->StartIndexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
rightCylRitem->BaseVertexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;

XMStoreFloat4x4(&leftSphereRitem->World, leftSphereWorld);
leftSphereRitem->ObjCBIndex = objCBIndex++;
leftSphereRitem->Geo = mGeometries["shapeGeo"].get();
leftSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
leftSphereRitem->IndexCount = leftSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
leftSphereRitem->StartIndexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
leftSphereRitem->BaseVertexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;

XMStoreFloat4x4(&rightSphereRitem->World, rightSphereWorld);
rightSphereRitem->ObjCBIndex = objCBIndex++;
rightSphereRitem->Geo = mGeometries["shapeGeo"].get();
rightSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightSphereRitem->IndexCount = rightSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
rightSphereRitem->StartIndexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
rightSphereRitem->BaseVertexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;

mAllRitems.push_back(std::move(leftCylRitem));
mAllRitems.push_back(std::move(rightCylRitem));
mAllRitems.push_back(std::move(leftSphereRitem));
mAllRitems.push_back(std::move(rightSphereRitem));
}

// All the render items are opaque.
for (auto& e : mAllRitems)
mOpaqueRitems.push_back(e.get());
}
//在该步骤中,最复杂的一个部分便是为了绘制物体而根据偏移量找到它在描述符堆中对应的CBV了:
void ShapesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

auto objectCB = mCurrFrameResource->ObjectCB->Resource();
//遍历RenderItem
for (size_t i = 0; i < ritems.size(); ++i)
{
auto ri = ritems[i];

cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

// 此对象和此帧资源的描述符堆中的CBV偏移。
UINT cbvIndex = mCurrFrameResourceIndex * (UINT)mOpaqueRitems.size() + ri->ObjCBIndex;
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);

cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{


#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

try
{
ShapesApp theApp(hInstance);
if (!theApp.Initialize())
return 0;

return theApp.Run();
}
catch (DxException& e)
{
MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
return 0;
}



}

根签名

根参数
1
2
3
4
5
6
7
8
9
10
11
typedef struct D3D12_ROOT_PARAMETER
{
D3D12_ROOT_PARAMETER_TYPE ParameterType;
union
{
D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;//指定类型,如描述符表,根常量,CBV,SRV,UAV
D3D12_ROOT_CONSTANTS Constants;
D3D12_ROOT_DESCRIPTOR Descriptor;
} ;
D3D12_SHADER_VISIBILITY ShaderVisibility;//指定着色器可见性
} D3D12_ROOT_PARAMETER;
1
2
3
4
5
6
7
8
9
typedef 
enum D3D12_ROOT_PARAMETER_TYPE
{
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE = 0,
D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS = ( D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE + 1 ) ,
D3D12_ROOT_PARAMETER_TYPE_CBV = ( D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS + 1 ) ,
D3D12_ROOT_PARAMETER_TYPE_SRV = ( D3D12_ROOT_PARAMETER_TYPE_CBV + 1 ) ,
D3D12_ROOT_PARAMETER_TYPE_UAV = ( D3D12_ROOT_PARAMETER_TYPE_SRV + 1 )
} D3D12_ROOT_PARAMETER_TYPE;
  1. 描述符表:引用的是描述符堆种的一块连续范围,用于确定要绑定的资源.每个描述符表占用1DWORD
  2. 跟描述符:通过直接设置跟描述符即可指示要绑定的资源,而且无需将它存于描述符中,但是只有常量缓冲区CBV,以及缓冲区的SRV/UAV才可以根据描述符的身份进行绑定.而纹理SRV不可以.每个描述符占用2DWORD
  3. 根常量:借助根常量直接绑定一系列的32位的常量值.每个常量32位,占用1个DWORD
描述符表
1
2
3
4
5
6
7
8
typedef struct D3D12_DESCRIPTOR_RANGE
{
D3D12_DESCRIPTOR_RANGE_TYPE RangeType;//类型,如...SRV,...CBV
UINT NumDescriptors;//描述符的数量
UINT BaseShaderRegister;//绑定的寄存器槽
UINT RegisterSpace;//空间,默认=0
UINT OffsetInDescriptorsFromTableStart;//此描述符距离表其实地址的偏移量
}

CD3D12_DESCRIPTOR_RANGE是封装了一些便捷方法,通过Init方法初始化

如果NumDescriptors设置为3,BaseShaderRegister设置为1,类型为CBV,那么对应的HLSL就是

1
2
3
cbuffer cbA : register(b1){};//从1开始
cbuffer cbB : register(b2){};
cbuffer cbC : register(b3){};
  • 示例
1
2
3
4
5
6
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 2, 0, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 3, 0, 2/*起始偏移量*/);
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 5/*起始偏移量*/);

slotRootParameter[0].InitAsDescriptorTable(3, descRange, D3D12_SHADER_VISIBILITY_ALL);
根描述符

填写D3D12_ROOT_PARAMETER中的Descriptor,可以定义为根描述符

1
2
3
4
5
6
typedef struct D3D12_ROOT_DESCRIPTOR
{
UINT ShaderRegister;
UINT RegisterSpace;
} D3D12_ROOT_DESCRIPTOR;

  1. ShaderRegister:如果设置为2,类型为CBV,那么对应的常量缓冲区是register(b2)
  2. RegisterSpace:空间

与描述符表需要在描述符堆中设置对应的描述符句柄不同,根描述符只需要直接绑定资源的虚拟地址就可以

示例

1
2
3
4
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress();
objCBAddress += ri->ObjCBIndex*objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);
根常量
1
2
3
4
5
6
typedef struct D3D12_ROOT_CONSTANTS
{
UINT ShaderRegister;
UINT RegisterSpace;
UINT Num32BitValues;//根常量所需的32位常量的个数
} D3D12_ROOT_CONSTANTS;
  • 示例
1
2
3
4
5
6
7
8
9
10
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsConstants(12,0);

CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
//应用程序部分
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size()/2;

cmdList->SetGraphicsRoot32BitConstant(0,1,&blurRadius,0);
cmdList->SetGraphicsRoot32BitConstant(0,(UINT)weights.size(),weights.data(),1);
  • HLSL代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cbuffer cbSetting : register(b0)
{
int gBlurRadius;
//11种模糊值
float w0;
float w1;
float w2;
float w3;
float w4;
float w5;
float w6;
float w7;
float w8;
float w9;
float w10;

};

与根描述符一i杨,根常量无需涉及描述符堆

根签名复杂示例
  • 着色器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Texture2D gDiffuseMap : register(t0);

cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};

cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
//....
};

cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};

  • 根签名
1
2
3
4
5
6
7
8
9
10
11
12
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV,1,0);
CD3DX12_ROOT_PARAMETER slotRootParameter[4];

slotRootParameter[0].InitAsDescriptorTable(1, texTable, D3D12_SHADER_VISIBILITY_PIXEL);
slotRootParameter[1].InitAsConstantBufferView(0);//寄存器b0
slotRootParameter[2].InitAsConstantBufferView(1);//寄存器b1
slotRootParameter[3].InitAsConstantBufferView(2);//寄存器b2


CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

陆地与波浪演示程序

栅格坐标

构建一个m*n个顶点组成的栅格,意味着具有(m-1)*(n-1)个四边形,即2倍的三角形

如果宽度为w,深度为d,那么x和y轴方向上的间距分别为dx=w/(n-1)dz=d/(m-1)

那么第i行,第j列的最坐标为Vij=[-0.5w+j*dx, 0.0, 0.5d-i*dz]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
uint32 vertexCount = m*n;
uint32 faceCount = (m-1)*(n-1)*2;

float halfWidth = 0.5f*width;
float halfDepth = 0.5f*depth;

float dx = width / (n-1);
float dz = depth / (m-1);

float du = 1.0f / (n-1);
float dv = 1.0f / (m-1);

meshData.Vertices.resize(vertexCount);
for(uint32 i = 0; i < m; ++i)
{
float z = halfDepth - i*dz;
for(uint32 j = 0; j < n; ++j)
{
float x = -halfWidth + j*dx;

meshData.Vertices[i*n+j].Position = XMFLOAT3(x, 0.0f, z);
meshData.Vertices[i*n+j].Normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
meshData.Vertices[i*n+j].TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);

meshData.Vertices[i*n+j].TexC.x = j*du;
meshData.Vertices[i*n+j].TexC.y = i*dv;
}
}
栅格索引

三角形ABC的索引(i*n+j, i*n+j+1, (i+1)*n+j)

三角形CBD的索引((i+1)*n+j, i*n+j+1, (i+1)*n+j+1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
meshData.Indices32.resize(faceCount*3); // 3 indices per face


uint32 k = 0;
for(uint32 i = 0; i < m-1; ++i)
{
for(uint32 j = 0; j < n-1; ++j)
{
meshData.Indices32[k] = i*n+j;
meshData.Indices32[k+1] = i*n+j+1;
meshData.Indices32[k+2] = (i+1)*n+j;

meshData.Indices32[k+3] = (i+1)*n+j;
meshData.Indices32[k+4] = i*n+j+1;
meshData.Indices32[k+5] = (i+1)*n+j+1;

k += 6; // next quad
}
}

image-20200713060500724

构建山体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void LandAndWavesApp::BuildWavesGeometryBuffers()
{
std::vector<std::uint16_t> indices(3 * mWaves->TriangleCount()); // 3 indices per face
assert(mWaves->VertexCount() < 0x0000ffff);

// Iterate over each quad.
int m = mWaves->RowCount();
int n = mWaves->ColumnCount();
int k = 0;
for(int i = 0; i < m - 1; ++i)
{
for(int j = 0; j < n - 1; ++j)
{
indices[k] = i*n + j;
indices[k + 1] = i*n + j + 1;
indices[k + 2] = (i + 1)*n + j;

indices[k + 3] = (i + 1)*n + j;
indices[k + 4] = i*n + j + 1;
indices[k + 5] = (i + 1)*n + j + 1;

k += 6; // next quad
}
}

UINT vbByteSize = mWaves->VertexCount()*sizeof(Vertex);
UINT ibByteSize = (UINT)indices.size()*sizeof(std::uint16_t);

auto geo = std::make_unique<MeshGeometry>();
geo->Name = "waterGeo";

// Set dynamically.
geo->VertexBufferCPU = nullptr;
geo->VertexBufferGPU = nullptr;

ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);

geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;

SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;

geo->DrawArgs["grid"] = submesh;

mGeometries["waterGeo"] = std::move(geo);
}

动态顶点缓冲区

动态顶点缓冲区即有可以频繁修改其中顶点数据的资源

如我们可以得到随着时间流走根性三角形的顶点高度,将此数据创建为动态顶点缓冲区

另外比如执行复杂的物理模拟和碰撞检测的粒子系统也需要用到

完整代码
  • waves
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#pragma once
#ifndef WAVES_H
#define WAVES_H

#include <vector>
#include <DirectXMath.h>


class Waves
{
public:

Waves(int m, int n, float dx, float dt, float speed, float damping);
Waves(const Waves& rhs) = delete;
Waves& operator=(const Waves& rhs) = delete;
~Waves();

int RowCount()const;
int ColumnCount()const;
int VertexCount()const;
int TriangleCount()const;
float Width()const;
float Depth()const;

// 第i个点的坐标
const DirectX::XMFLOAT3& Position(int i)const { return mCurrSolution[i]; }

// 第i个点的法线
const DirectX::XMFLOAT3& Normal(int i)const { return mNormals[i]; }

// 第i个点的切线
const DirectX::XMFLOAT3& TangentX(int i)const { return mTangentX[i]; }

void Update(float dt);
void Disturb(int i, int j, float magnitude);


private:
int mNumRows = 0;//行数
int mNumCols = 0;//列数

int mVertexCount = 0;
int mTriangleCount = 0;

//预先计算仿真常数。
float mK1 = 0.0f;
float mK2 = 0.0f;
float mK3 = 0.0f;

float mTimeStep = 0.0f;//时间步长
float mSpatialStep = 0.0f;//空间步长


std::vector<DirectX::XMFLOAT3> mPrevSolution;//上一个解决方案
std::vector<DirectX::XMFLOAT3> mCurrSolution;//当前解决方案
std::vector<DirectX::XMFLOAT3> mNormals;
std::vector<DirectX::XMFLOAT3> mTangentX;
};


#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include "Waves.h"
#include <DirectXMath.h>
#include <ppl.h>


using namespace DirectX;


Waves::Waves(int m, int n, float dx, float dt, float speed, float damping)
{
mNumRows = m;
mNumCols = n;

mVertexCount = m * n;
mTriangleCount = (m - 1)*(n - 1) * 2;

mTimeStep = dt;
mSpatialStep = dx;


float d = damping * dt + 2.0f;
float e = (speed*speed)*(dt*dt) / (dx*dx);

mK1 = (damping*dt - 2.0f) / d;
mK2 = (4.0f - 8.0f*e) / d;
mK3 = (2.0f*e) / d;

mPrevSolution.resize(m*n);
mCurrSolution.resize(m*n);
mNormals.resize(m*n);
mTangentX.resize(m*n);


float halfWidth = (n - 1)*dx*0.5f;
float halfDepth = (m - 1)*dx*0.5f;
for (int i = 0; i < m; ++i)
{
float z = halfDepth - i * dx;
for (int j = 0; j < n; ++j)
{
float x = -halfWidth + j * dx;

mPrevSolution[i*n + j] = XMFLOAT3(x, 0.0f, z);
mCurrSolution[i*n + j] = XMFLOAT3(x, 0.0f, z);
mNormals[i*n + j] = XMFLOAT3(0.0f, 1.0f, 0.0f);
mTangentX[i*n + j] = XMFLOAT3(1.0f, 0.0f, 0.0f);
}
}
}

Waves::~Waves()
{

}

int Waves::RowCount() const
{
return mNumRows;
}

int Waves::ColumnCount() const
{
return mNumCols;
}

int Waves::VertexCount() const
{
return mVertexCount;
}

int Waves::TriangleCount() const
{
return mTriangleCount;
}

float Waves::Width() const
{
return mNumRows*mSpatialStep;
}

float Waves::Depth() const
{
return mNumCols*mSpatialStep;
}

void Waves::Update(float dt)
{
static float t =0;

if (t>=mTimeStep)
{
concurrency::parallel_for(1, mNumRows - 1, [this](int i)
{
//此更新后,我们将丢弃旧的旧版本缓冲区,因此请使用新的更新覆盖该缓冲区。
//注意我们如何就地执行此操作(读/写同一元素)
//因为我们不再需要prev_ij,并且赋值最后发生。
//注意j索引x和i索引z:h(x_j,z_i,t_k)
//此外,我们的+ z轴会“向下”; 这只是为了与行索引下降保持一致。
for (int j = 1; j < mNumCols - 1; ++j)
{
mPrevSolution[i*mNumCols + j].y =
mK1 * mPrevSolution[i*mNumCols + j].y +
mK2 * mCurrSolution[i*mNumCols + j].y +
mK3 * (mCurrSolution[(i + 1)*mNumCols + j].y +
mCurrSolution[(i - 1)*mNumCols + j].y +
mCurrSolution[i*mNumCols + j + 1].y +
mCurrSolution[i*mNumCols + j - 1].y);
}

});
//我们只是用新数据覆盖了先前的缓冲区,所以此数据需要成为当前的解决方案和旧的
//当前解决方案成为新的先前解决方案。
std::swap(mPrevSolution, mCurrSolution);

t = 0.0f;



//计算法线
concurrency::parallel_for(1, mNumRows - 1, [this](int i)
//for(int i = 1; i < mNumRows - 1; ++i)
{
for (int j = 1; j < mNumCols - 1; ++j)
{
float l = mCurrSolution[i*mNumCols + j - 1].y;
float r = mCurrSolution[i*mNumCols + j + 1].y;
float t = mCurrSolution[(i - 1)*mNumCols + j].y;
float b = mCurrSolution[(i + 1)*mNumCols + j].y;
mNormals[i*mNumCols + j].x = -r + l;
mNormals[i*mNumCols + j].y = 2.0f*mSpatialStep;
mNormals[i*mNumCols + j].z = b - t;

XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&mNormals[i*mNumCols + j]));
XMStoreFloat3(&mNormals[i*mNumCols + j], n);

mTangentX[i*mNumCols + j] = XMFLOAT3(2.0f*mSpatialStep, r - l, 0.0f);
XMVECTOR T = XMVector3Normalize(XMLoadFloat3(&mTangentX[i*mNumCols + j]));
XMStoreFloat3(&mTangentX[i*mNumCols + j], T);
}
});

}
}

void Waves::Disturb(int i, int j, float magnitude)
{
assert(i > 1 && i < mNumRows - 2);
assert(j > 1 && j < mNumCols - 2);

float halfMag = 0.5f*magnitude;

// 绕顶第i个顶点高度及其相邻点。
mCurrSolution[i*mNumCols + j].y += magnitude;
mCurrSolution[i*mNumCols + j + 1].y += halfMag;
mCurrSolution[i*mNumCols + j - 1].y += halfMag;
mCurrSolution[(i + 1)*mNumCols + j].y += halfMag;
mCurrSolution[(i - 1)*mNumCols + j].y += halfMag;
}


  • LandAndWaves
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
//***************************************************************************************
// LandAndWavesApp.cpp by Frank Luna (C) 2015 All Rights Reserved.
//
// Hold down '1' key to view scene in wireframe mode.
//***************************************************************************************

#include "../../Common/d3dApp.h"
#include "../../Common/MathHelper.h"
#include "../../Common/UploadBuffer.h"
#include "../../Common/GeometryGenerator.h"
#include "FrameResource.h"
#include "Waves.h"

using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

const int gNumFrameResources = 3;

// Lightweight structure stores parameters to draw a shape. This will
// vary from app-to-app.
struct RenderItem
{
RenderItem() = default;

// World matrix of the shape that describes the object's local space
// relative to the world space, which defines the position, orientation,
// and scale of the object in the world.
XMFLOAT4X4 World = MathHelper::Identity4x4();

// Dirty flag indicating the object data has changed and we need to update the constant buffer.
// Because we have an object cbuffer for each FrameResource, we have to apply the
// update to each FrameResource. Thus, when we modify obect data we should set
// NumFramesDirty = gNumFrameResources so that each frame resource gets the update.
int NumFramesDirty = gNumFrameResources;

// Index into GPU constant buffer corresponding to the ObjectCB for this render item.
UINT ObjCBIndex = -1;

MeshGeometry* Geo = nullptr;

// Primitive topology.
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

// DrawIndexedInstanced parameters.
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;
};

enum class RenderLayer : int
{
Opaque = 0,
Count
};

class LandAndWavesApp : public D3DApp
{
public:
LandAndWavesApp(HINSTANCE hInstance);
LandAndWavesApp(const LandAndWavesApp& rhs) = delete;
LandAndWavesApp& operator=(const LandAndWavesApp& rhs) = delete;
~LandAndWavesApp();

virtual bool Initialize()override;

private:
virtual void OnResize()override;
virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;

virtual void OnMouseDown(WPARAM btnState, int x, int y)override;
virtual void OnMouseUp(WPARAM btnState, int x, int y)override;
virtual void OnMouseMove(WPARAM btnState, int x, int y)override;

void OnKeyboardInput(const GameTimer& gt);
void UpdateCamera(const GameTimer& gt);
void UpdateObjectCBs(const GameTimer& gt);
void UpdateMainPassCB(const GameTimer& gt);
void UpdateWaves(const GameTimer& gt);

void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildLandGeometry();
void BuildWavesGeometryBuffers();
void BuildPSOs();
void BuildFrameResources();
void BuildRenderItems();
void DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems);

float GetHillsHeight(float x, float z)const;
XMFLOAT3 GetHillsNormal(float x, float z)const;

private:

std::vector<std::unique_ptr<FrameResource>> mFrameResources;
FrameResource* mCurrFrameResource = nullptr;
int mCurrFrameResourceIndex = 0;

UINT mCbvSrvDescriptorSize = 0;

ComPtr<ID3D12RootSignature> mRootSignature = nullptr;

std::unordered_map<std::string, std::unique_ptr<MeshGeometry>> mGeometries;
std::unordered_map<std::string, ComPtr<ID3DBlob>> mShaders;
std::unordered_map<std::string, ComPtr<ID3D12PipelineState>> mPSOs;

std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

RenderItem* mWavesRitem = nullptr;

// List of all the render items.
std::vector<std::unique_ptr<RenderItem>> mAllRitems;

// Render items divided by PSO.
std::vector<RenderItem*> mRitemLayer[(int)RenderLayer::Count];

std::unique_ptr<Waves> mWaves;

PassConstants mMainPassCB;

bool mIsWireframe = false;

XMFLOAT3 mEyePos = { 0.0f, 0.0f, 0.0f };
XMFLOAT4X4 mView = MathHelper::Identity4x4();
XMFLOAT4X4 mProj = MathHelper::Identity4x4();

float mTheta = 1.5f*XM_PI;
float mPhi = XM_PIDIV2 - 0.1f;
float mRadius = 50.0f;

float mSunTheta = 1.25f*XM_PI;
float mSunPhi = XM_PIDIV4;

POINT mLastMousePos;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

try
{
LandAndWavesApp theApp(hInstance);
if(!theApp.Initialize())
return 0;

return theApp.Run();
}
catch(DxException& e)
{
MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
return 0;
}
}

LandAndWavesApp::LandAndWavesApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{
}

LandAndWavesApp::~LandAndWavesApp()
{
if(md3dDevice != nullptr)
FlushCommandQueue();
}

bool LandAndWavesApp::Initialize()
{
if(!D3DApp::Initialize())
return false;

// Reset the command list to prep for initialization commands.
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

mWaves = std::make_unique<Waves>(128, 128, 1.0f, 0.03f, 4.0f, 0.2f);

BuildRootSignature();
BuildShadersAndInputLayout();
BuildLandGeometry();
BuildWavesGeometryBuffers();
BuildRenderItems();
BuildRenderItems();
BuildFrameResources();
BuildPSOs();

// Execute the initialization commands.
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

// Wait until initialization is complete.
FlushCommandQueue();

return true;
}

void LandAndWavesApp::OnResize()
{
D3DApp::OnResize();

// The window resized, so update the aspect ratio and recompute the projection matrix.
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
}

void LandAndWavesApp::Update(const GameTimer& gt)
{
OnKeyboardInput(gt);
UpdateCamera(gt);

// Cycle through the circular frame resource array.
mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

// Has the GPU finished processing the commands of the current frame resource?
// If not, wait until the GPU has completed commands up to this fence point.
if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}

UpdateObjectCBs(gt);
UpdateMainPassCB(gt);
UpdateWaves(gt);
}

void LandAndWavesApp::Draw(const GameTimer& gt)
{
auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;

// Reuse the memory associated with command recording.
// We can only reset when the associated command lists have finished execution on the GPU.
ThrowIfFailed(cmdListAlloc->Reset());

// A command list can be reset after it has been added to the command queue via ExecuteCommandList.
// Reusing the command list reuses memory.
if(mIsWireframe)
{
ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque_wireframe"].Get()));
}
else
{
ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get()));
}

mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);

// Indicate a state transition on the resource usage.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

// Clear the back buffer and depth buffer.
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

// Specify the buffers we are going to render to.
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

// Bind per-pass constant buffer. We only need to do this once per-pass.
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(1, passCB->GetGPUVirtualAddress());

DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);

// Indicate a state transition on the resource usage.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

// Done recording commands.
ThrowIfFailed(mCommandList->Close());

// Add the command list to the queue for execution.
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

// Swap the back and front buffers
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

// Advance the fence value to mark commands up to this fence point.
mCurrFrameResource->Fence = ++mCurrentFence;

// Add an instruction to the command queue to set a new fence point.
// Because we are on the GPU timeline, the new fence point won't be
// set until the GPU finishes processing all the commands prior to this Signal().
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}

void LandAndWavesApp::OnMouseDown(WPARAM btnState, int x, int y)
{
mLastMousePos.x = x;
mLastMousePos.y = y;

SetCapture(mhMainWnd);
}

void LandAndWavesApp::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}

void LandAndWavesApp::OnMouseMove(WPARAM btnState, int x, int y)
{
if((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a degree.
float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

// Update angles based on input to orbit camera around box.
mTheta += dx;
mPhi += dy;

// Restrict the angle mPhi.
mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.2 unit in the scene.
float dx = 0.2f*static_cast<float>(x - mLastMousePos.x);
float dy = 0.2f*static_cast<float>(y - mLastMousePos.y);

// Update the camera radius based on input.
mRadius += dx - dy;

// Restrict the radius.
mRadius = MathHelper::Clamp(mRadius, 5.0f, 150.0f);
}

mLastMousePos.x = x;
mLastMousePos.y = y;
}

void LandAndWavesApp::OnKeyboardInput(const GameTimer& gt)
{
if(GetAsyncKeyState('1') & 0x8000)
mIsWireframe = true;
else
mIsWireframe = false;
}

void LandAndWavesApp::UpdateCamera(const GameTimer& gt)
{
// Convert Spherical to Cartesian coordinates.
mEyePos.x = mRadius*sinf(mPhi)*cosf(mTheta);
mEyePos.z = mRadius*sinf(mPhi)*sinf(mTheta);
mEyePos.y = mRadius*cosf(mPhi);

// Build the view matrix.
XMVECTOR pos = XMVectorSet(mEyePos.x, mEyePos.y, mEyePos.z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);
}

void LandAndWavesApp::UpdateObjectCBs(const GameTimer& gt)
{
auto currObjectCB = mCurrFrameResource->ObjectCB.get();
for(auto& e : mAllRitems)
{
// Only update the cbuffer data if the constants have changed.
// This needs to be tracked per frame resource.
if(e->NumFramesDirty > 0)
{
XMMATRIX world = XMLoadFloat4x4(&e->World);

ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));

currObjectCB->CopyData(e->ObjCBIndex, objConstants);

// Next FrameResource need to be updated too.
e->NumFramesDirty--;
}
}
}

void LandAndWavesApp::UpdateMainPassCB(const GameTimer& gt)
{
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);

XMMATRIX viewProj = XMMatrixMultiply(view, proj);
XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view);
XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj);

XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
mMainPassCB.EyePosW = mEyePos;
mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
mMainPassCB.NearZ = 1.0f;
mMainPassCB.FarZ = 1000.0f;
mMainPassCB.TotalTime = gt.TotalTime();
mMainPassCB.DeltaTime = gt.DeltaTime();

auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}

void LandAndWavesApp::UpdateWaves(const GameTimer& gt)
{
// Every quarter second, generate a random wave.
static float t_base = 0.0f;
if((mTimer.TotalTime() - t_base) >= 0.25f)
{
t_base += 0.25f;

int i = MathHelper::Rand(4, mWaves->RowCount() - 5);
int j = MathHelper::Rand(4, mWaves->ColumnCount() - 5);

float r = MathHelper::RandF(0.2f, 0.5f);

mWaves->Disturb(i, j, r);
}

// Update the wave simulation.
mWaves->Update(gt.DeltaTime());

// Update the wave vertex buffer with the new solution.
auto currWavesVB = mCurrFrameResource->WavesVB.get();
for(int i = 0; i < mWaves->VertexCount(); ++i)
{
Vertex v;

v.Pos = mWaves->Position(i);
v.Color = XMFLOAT4(DirectX::Colors::Blue);

currWavesVB->CopyData(i, v);
}

// Set the dynamic VB of the wave renderitem to the current frame VB.
mWavesRitem->Geo->VertexBufferGPU = currWavesVB->Resource();
}

void LandAndWavesApp::BuildRootSignature()
{
// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];

// Create root CBV.
slotRootParameter[0].InitAsConstantBufferView(0);
slotRootParameter[1].InitAsConstantBufferView(1);

// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

// create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

if(errorBlob != nullptr)
{
::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
}
ThrowIfFailed(hr);

ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

void LandAndWavesApp::BuildShadersAndInputLayout()
{
mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");

mInputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
}

void LandAndWavesApp::BuildLandGeometry()
{
GeometryGenerator geoGen;
GeometryGenerator::MeshData grid = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);

//
// Extract the vertex elements we are interested and apply the height function to
// each vertex. In addition, color the vertices based on their height so we have
// sandy looking beaches, grassy low hills, and snow mountain peaks.
//

std::vector<Vertex> vertices(grid.Vertices.size());
for(size_t i = 0; i < grid.Vertices.size(); ++i)
{
auto& p = grid.Vertices[i].Position;
vertices[i].Pos = p;
vertices[i].Pos.y = GetHillsHeight(p.x, p.z);

// Color the vertex based on its height.
if(vertices[i].Pos.y < -10.0f)
{
// Sandy beach color.
vertices[i].Color = XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
}
else if(vertices[i].Pos.y < 5.0f)
{
// Light yellow-green.
vertices[i].Color = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
}
else if(vertices[i].Pos.y < 12.0f)
{
// Dark yellow-green.
vertices[i].Color = XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
}
else if(vertices[i].Pos.y < 20.0f)
{
// Dark brown.
vertices[i].Color = XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);
}
else
{
// White snow.
vertices[i].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
}
}

const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);

std::vector<std::uint16_t> indices = grid.GetIndices16();
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

auto geo = std::make_unique<MeshGeometry>();
geo->Name = "landGeo";

ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);

geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);

geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;

SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;

geo->DrawArgs["grid"] = submesh;

mGeometries["landGeo"] = std::move(geo);
}

void LandAndWavesApp::BuildWavesGeometryBuffers()
{
std::vector<std::uint16_t> indices(3 * mWaves->TriangleCount()); // 3 indices per face
assert(mWaves->VertexCount() < 0x0000ffff);

// Iterate over each quad.
int m = mWaves->RowCount();
int n = mWaves->ColumnCount();
int k = 0;
for(int i = 0; i < m - 1; ++i)
{
for(int j = 0; j < n - 1; ++j)
{
indices[k] = i*n + j;
indices[k + 1] = i*n + j + 1;
indices[k + 2] = (i + 1)*n + j;

indices[k + 3] = (i + 1)*n + j;
indices[k + 4] = i*n + j + 1;
indices[k + 5] = (i + 1)*n + j + 1;

k += 6; // next quad
}
}

UINT vbByteSize = mWaves->VertexCount()*sizeof(Vertex);
UINT ibByteSize = (UINT)indices.size()*sizeof(std::uint16_t);

auto geo = std::make_unique<MeshGeometry>();
geo->Name = "waterGeo";

// Set dynamically.
geo->VertexBufferCPU = nullptr;
geo->VertexBufferGPU = nullptr;

ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);

geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;

SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;

geo->DrawArgs["grid"] = submesh;

mGeometries["waterGeo"] = std::move(geo);
}

void LandAndWavesApp::BuildPSOs()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;

//
// PSO for opaque objects.
//
ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
opaquePsoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
opaquePsoDesc.pRootSignature = mRootSignature.Get();
opaquePsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders["standardVS"]->GetBufferPointer()),
mShaders["standardVS"]->GetBufferSize()
};
opaquePsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["opaquePS"]->GetBufferPointer()),
mShaders["opaquePS"]->GetBufferSize()
};
opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
opaquePsoDesc.SampleMask = UINT_MAX;
opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
opaquePsoDesc.NumRenderTargets = 1;
opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
opaquePsoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));

//
// PSO for opaque wireframe objects.
//

D3D12_GRAPHICS_PIPELINE_STATE_DESC opaqueWireframePsoDesc = opaquePsoDesc;
opaqueWireframePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaqueWireframePsoDesc, IID_PPV_ARGS(&mPSOs["opaque_wireframe"])));
}

void LandAndWavesApp::BuildFrameResources()
{
for(int i = 0; i < gNumFrameResources; ++i)
{
mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
1, (UINT)mAllRitems.size(), mWaves->VertexCount()));
}
}

void LandAndWavesApp::BuildRenderItems()
{
auto wavesRitem = std::make_unique<RenderItem>();
wavesRitem->World = MathHelper::Identity4x4();
wavesRitem->ObjCBIndex = 0;
wavesRitem->Geo = mGeometries["waterGeo"].get();
wavesRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
wavesRitem->IndexCount = wavesRitem->Geo->DrawArgs["grid"].IndexCount;
wavesRitem->StartIndexLocation = wavesRitem->Geo->DrawArgs["grid"].StartIndexLocation;
wavesRitem->BaseVertexLocation = wavesRitem->Geo->DrawArgs["grid"].BaseVertexLocation;

mWavesRitem = wavesRitem.get();

mRitemLayer[(int)RenderLayer::Opaque].push_back(wavesRitem.get());

auto gridRitem = std::make_unique<RenderItem>();
gridRitem->World = MathHelper::Identity4x4();
gridRitem->ObjCBIndex = 1;
gridRitem->Geo = mGeometries["landGeo"].get();
gridRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;

mRitemLayer[(int)RenderLayer::Opaque].push_back(gridRitem.get());

mAllRitems.push_back(std::move(wavesRitem));
mAllRitems.push_back(std::move(gridRitem));
}

void LandAndWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

auto objectCB = mCurrFrameResource->ObjectCB->Resource();

// For each render item...
for(size_t i = 0; i < ritems.size(); ++i)
{
auto ri = ritems[i];

cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress();
objCBAddress += ri->ObjCBIndex*objCBByteSize;

cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
}
}

float LandAndWavesApp::GetHillsHeight(float x, float z)const
{
return 0.3f*(z*sinf(0.1f*x) + x*cosf(0.1f*z));
}

XMFLOAT3 LandAndWavesApp::GetHillsNormal(float x, float z)const
{
// n = (-df/dx, 1, -df/dz)
XMFLOAT3 n(
-0.03f*z*cosf(0.1f*x) - 0.3f*cosf(0.1f*z),
1.0f,
-0.3f*sinf(0.1f*x) + 0.03f*x*sinf(0.1f*z));

XMVECTOR unitNormal = XMVector3Normalize(XMLoadFloat3(&n));
XMStoreFloat3(&n, unitNormal);

return n;
}

自定义资源

新建插件StateMachine,然后手动拷贝一份StateMachineEditor用于编辑器模块 ,把里面的所有名称改为后缀为Editor版本

然后在Editor版本的Build.cs 的Private部分中加入如下模块 "UnrealEd", "AssetTools",Public部分加入 "StateMachine"模块

资源类

创建随意UObject 资源,可以保持默认,本例中为UStateMachineAsset

行为类

创建FAssetTypeActions_StateMachine类继承自FAssetTypeActions_Base,此类可以接管资源双击打开编辑器的动作,这个类需要在模块启动时注册,稍微会讲

此类建议创建在Editor模块

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

#include "Developer/AssetTools/Public/AssetTypeActions_Base.h"

class FAssetTypeActions_StateMachine: public FAssetTypeActions_Base
{
public:
FAssetTypeActions_StateMachine(EAssetTypeCategories::Type AsstCategory);
~FAssetTypeActions_StateMachine();

// IAssetTypeActions Implementation

virtual FText GetName()const override;
virtual FColor GetTypeColor()const override;;
virtual UClass* GetSupportedClass()const override;
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithInLevelEditor = TSharedPtr<IToolkitHost>())override;

virtual UThumbnailInfo* GetThumbnailInfo(UObject* Asset)const override;
virtual uint32 GetCategories()override { return m_AssetCategory; }//自定义的标签,可以用系统自带的EAssetTypeCategories::Type

private:
EAssetTypeCategories::Type m_AssetCategory;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

#include "AssetTypeActions_StateMachine.h"
#include "BlueprintEditorUtils.h"
#include "AssetRegistryModule.h"

#include "StateMachineAsset.h"

#include "ThumbnailRendering/SceneThumbnailInfo.h"

#define LOCTEXT_NAMESPACE "AssetTypeActions"

FAssetTypeActions_StateMachine::FAssetTypeActions_StateMachine(EAssetTypeCategories::Type AssetCategory)
:m_AssetCategory(AssetCategory)
{

}

FAssetTypeActions_StateMachine::~FAssetTypeActions_StateMachine()
{

}

FText FAssetTypeActions_StateMachine::GetName()const
{
// Content里右键菜单创建的显示的可创建类型的名字
return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_StateMachine", "StateMachine Blueprint");
}

FColor FAssetTypeActions_StateMachine::GetTypeColor()const
{

return FColor::Emerald;
}

UClass* FAssetTypeActions_StateMachine::GetSupportedClass()const
{
return UStateMachineAsset::StaticClass();//创建的资源,即之前定义的UObject
}

//打开资源行为,通过这个方法可以打开其他视图
void FAssetTypeActions_StateMachine::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithInLevelEditor)
{
UE_LOG(LogTemp, Log, TEXT("FAssetTypeActions_StateMachine::OpenAssetEditor"));
//内容暂无
}

UThumbnailInfo* FAssetTypeActions_StateMachine::GetThumbnailInfo(UObject* Asset)const
{
return NULL;
}

#undef LOCTEXT_NAMESPACE
工厂类

此类最好创建在Editor模块

此类是让引擎认识我们创建的这个资源和自定义标签,似乎定义了以后引擎就自动会识别,即上述两个类的功能

不创建的话连对应的自定义的标签也无法显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#pragma once

#include "StateMachineAsset.h"
#include "Engine/Blueprint.h"
#include "Factories/Factory.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"

#include "StateMachineFactory.generated.h"

UCLASS(HideCategories = Object,MinimalAPI)
class UStateMachineFactory : public UFactory
{
GENERATED_BODY()

public:
UStateMachineFactory(const FObjectInitializer& ObjectInitializer);
virtual ~UStateMachineFactory();

public:
UPROPERTY(EditAnywhere, Category = "StateMachineFactory")
TEnumAsByte<EBlueprintType> mBlueprintType;

UPROPERTY(EditAnywhere, Category = "StateMachineFactory")
TSubclassOf<UStateMachineAsset> mSupportedClass;

public:
// override UFactory function
virtual bool ConfigureProperties()override;
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags InFlags, UObject* InContext, FFeedbackContext* InWarn,FName InCallingContext)override;
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags InFlags, UObject* InContext, FFeedbackContext* InWarn);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

#include "StateMachineFactory.h"
#include "StateMachineAsset.h"

UStateMachineFactory::UStateMachineFactory(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer), mSupportedClass(UStateMachineAsset::StaticClass())
{
bCreateNew = true;
bEditAfterNew = true;

// 这个Factory托管的类型
SupportedClass = mSupportedClass;
}

UStateMachineFactory::~UStateMachineFactory()
{

}

bool UStateMachineFactory::ConfigureProperties()
{
return true;
}

UObject* UStateMachineFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags InFlags, UObject* InContext, FFeedbackContext* InWarn,FName InCallingContext)
{
return FactoryCreateNew(InClass, InParent, InName, InFlags, InContext, InWarn);
}

UObject* UStateMachineFactory::FactoryCreateNew(UClass* InClass,UObject* InParent,FName InName,EObjectFlags InFlags,UObject* InContext,FFeedbackContext* InWarn)
{
UStateMachineAsset* NewStateMachineAssetItem = NewObject<UStateMachineAsset>(InParent, InClass, InName, InFlags);
return NewStateMachineAssetItem;
}
模块

用于注册自定义标签以及我们之前创建的FAssetTypeActions_StateMachine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once
#include "AssetTypeCategories.h"
#include "AssetToolsModule.h"

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FStateMachineEditorModule : public IModuleInterface
{
public:

/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;


void RegisterAssetType(IAssetTools& AssetTools);
void RegisterSetting();
void UnRegisterSetting();


private:
EAssetTypeCategories::Type m_StateMachineCategoryType;//自定义标签
TArray<TSharedPtr<IAssetTypeActions>> m_CreatedAssetTypeActions;//Action数组
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "StateMachineEditor.h"
#include "AssetTypeActions_StateMachine.h"
#include "ISettingsModule.h"

#define LOCTEXT_NAMESPACE "FStateMachineEditorModule"

void FStateMachineEditorModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
IAssetTools& AssetToolsInstance = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
//创建自定义标签,可以本地化翻译
m_StateMachineCategoryType= AssetToolsInstance.RegisterAdvancedAssetCategory(TEXT("StateMachine"), LOCTEXT("StateMachineAssetCategory", "StateMachine"));
RegisterAssetType(AssetToolsInstance);
RegisterSetting();

}

void FStateMachineEditorModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.

UnRegisterSetting();
}

void FStateMachineEditorModule::RegisterAssetType(IAssetTools& AssetTools)
{
TSharedRef<IAssetTypeActions> StateMachineBlueprint = MakeShareable(new FAssetTypeActions_StateMachine(m_StateMachineCategoryType));
//注册资源行为类
AssetTools.RegisterAssetTypeActions(StateMachineBlueprint);
m_CreatedAssetTypeActions.Add(StateMachineBlueprint);
}

void FStateMachineEditorModule::RegisterSetting()
{
//暂时为空
}

void FStateMachineEditorModule::UnRegisterSetting()
{
//暂时为空
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FStateMachineEditorModule, StateMachineEditor)

然后我们在引擎Content目录下右键点击就出现我们要的效果

image-20200710105049753