This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
基础
专业(SIM)
UnigineEditor
界面概述
资源工作流程
Version Control
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
如何擴展編輯器功能
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
C++
C#
UnigineScript
统一的Unigine着色器语言 UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
创建内容
内容优化
材质
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials
注意! 这个版本的文档是过时的,因为它描述了一个较老的SDK版本!请切换到最新SDK版本的文档。
注意! 这个版本的文档描述了一个不再受支持的旧SDK版本!请升级到最新的SDK版本。

通过代码管理Landscape Terrain

This article describes how to create and modify the Landscape Terrain object via code. But before we get down to coding, let's start with a bit of theory.本文描述了如何通过代码创建和修改Landscape Terrain对象。但在我们开始编码之前,让我们先从一些理论开始。

The surface of Landscape Terrain (ObjectLandscapeTerrain class) is represented by a single or multiple rectangular layers called Landscape Layer Maps (LandscapeLayerMap class). By creating and arranging layer maps you define the look and functionality of the terrain.Landscape Terrain的表面(ObjectLandscapeTerrain类)由一个或多个称为景观层地图(LandscapeLayerMap类)的矩形层表示。通过创建和安排图层地图,你可以定义地形的外观和功能。

To see a terrain, at least one Landscape Layer Map is required. Data of each Landscape Layer Map (heights, albedo, and masks) is stored in an .lmap file. To create such a file the LandscapeMapFileCreator class is used. If you want to load, modify, and apply settings stored in this file later, use the LandscapeMapFileSettings class.要查看地形,至少需要一个Landscape Layer Map 。每个景观层地图的数据(高度,反照率遮罩)存储在一个.lmap文件中。要创建这样一个文件,需要使用LandscapeMapFileCreator类。如果您希望稍后加载、修改和应用存储在这个文件中的设置,请使用LandscapeMapFileSettings类。

Landscape Terrain rendering and modification is managed via the Landscape class.Landscape Terrain的渲染和修改是通过Landscape类管理的。

There is a set of API classes used to manage the Landscape Terrain object:有一组API类用于管理Landscape Terrain对象:

  • ObjectLandscapeTerrain — managing general Landscape Terrain object parameters.ObjectLandscapeTerrain—管理通用Landscape Terrain对象参数。
  • TerrainDetail — managing terrain details that define its appearance. Details are organized into a hierarchy, each of them can have an unlimited number of children. Details are attached to detail masks and are drawn in accordance with their rendering order (the one with the highest order shall be rendered above all others).TerrainDetail—管理定义其外观的地形细节。细节被组织成一个层次结构,每个层次结构可以有无限数量的子节点。细节附加在细节蒙版上,并按照渲染顺序绘制(最高顺序的渲染应高于其他所有渲染)。
  • TerrainDetailMask — managing terrain detail masks. Each detail mask can have an unlimited number of details.TerrainDetailMask -管理地形细节遮罩。每个细节遮罩可以有无限数量的细节。
  • LandscapeFetch — getting terrain data at a certain point (e.g. a height request) or check for an intersection with a traced line.LandscapeFetch -获取某一点的地形数据(例如高度请求)或检查与跟踪线的交点。
  • LandscapeImages — to edit landscape terrain via API.LandscapeImages -通过API编辑风景地形。
  • LandscapeTextures — to edit landscape terrain via API.LandscapeTextures -通过API编辑风景地形。

See Also
另请参阅#

  • C++ Sample set demonstrating various Landscape Terrain features and use cases演示各种景观地形特征和用例的 C++ 示例集

Creating a Terrain
创建一个地形#

To create a Landscape Terrain based on arbitrary height and albedo maps, the following workflow is used:为了创建一个基于任意高度和反照率的景观地形,使用以下工作流:

  1. Create a LandscapeMapFileCreator instance, set necessary parameters (grid size, resolution, etc.) and generate a new .lmap file based on specified albedo and height images (tiles) via the Run() method. Here you can subscribe for events at different stages of creation.创建一个LandscapeMapFileCreator实例,设置必要的参数(网格大小、分辨率等),并通过Run()方法根据指定的反照率和高度图像(tile)生成一个新的.lmap文件。在这里,您可以为创建的不同阶段添加回调。
  2. Create a LandscapeMapFileSettings instance, load target .lmap file for settings, set necessary parameters (opacity and blending for height and albedo data) and apply them.创建一个LandscapeMapFileSettings实例,加载目标.lmap文件进行设置,设置必要的参数(高度和反照率数据的不透明度和混合)并应用它们。
  3. Create a new ObjectLandscapeTerrain object.创建一个新的ObjectLandscapeTerrain对象。
  4. Create a LandscapeLayerMap based on the previously created .lmap file.根据前面创建的.lmap文件创建一个LandscapeLayerMap

Preparing a Project
准备一个项目#

Before we get to code, perform the following:之前的代码,执行以下:

  1. Open SDK Browser and create a new C# (.NET) or C++ project depending on the programming language selected.打开SDK Browser,根据所选的编程语言创建一个新的C# (.NET)或C++项目
  2. Open your project in UnigineEditor via the Open Editor button in SDK Browser.打开你的项目通过Open Editor按钮SDK Browser UnigineEditor。
  3. Save the following images to your computer:保存以下图像到您的计算机:

    Albedo Map Height Map
    Albedo Map Height Map
  4. Drag the height.png file directly to the Asset Browser window to add it to your project. In the Import Dialog for your height map, set Image Format to R32F and click Yes.height.png文件直接拖到Asset Browser窗口将它添加到您的项目。对你的高度图导入对话框中,设置Image Format R32F并单击Yes
  5. Drag the albedo.png file directly to the Asset Browser window too. In the Import Dialog for your height map, set Texture Preset to Albedo (RGB — color, A — opacity) and click Yes.albedo.png文件直接拖到Asset Browser窗口。在你的高度地图的导入对话框中,设置Texture PresetAlbedo (RGB — color, A — opacity),然后单击Yes

Code
代码#

Copy the source code below and paste it to the corresponding source files of your project.复制下面的代码粘贴到相应的源文件的项目。

In the AppWorldLogic.h file, define all parameters required for terrain creation, as well as the terrain itself with a layer map.AppWorldLogic.h文件中,定义地形创建所需的所有参数,以及带有图层地图的地形本身。

AppWorldLogic.h

源代码 (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineImage.h>

class AppWorldLogic : public Unigine::WorldLogic
{
	// Layer map parameters
	// position, rotation and scaling factor along the Z axis 
	Unigine::Math::Vec3 lmapPosition = Unigine::Math::Vec3_zero;
	float lmapRotation = 0.0f;
	float lmapHeightScale = 1.0f;

	// size of the layer map (in units)
	Unigine::Math::Vec2 lmapSize = Unigine::Math::Vec2(20.0f, 20.0f);

	// landscape layer map grid size (number of cells along X and Y axes)
	int lmapGridSizeX = 2;
	int lmapGridSizeY = 2;

	// resolution of a single tile of the layer map 
	// (tile images to be used must have the same resolution)
	int lmapTileResolutionX = 512;
	int lmapTileResolutionY = 512;

	// layer map name
	Unigine::String lmapName = "map";

	// set of images for a single tile
	struct TileImages
	{
		Unigine::String albedoImagePath;
		Unigine::String heightImagePath;
	};

	// vector for the tileset data
	Unigine::Vector<TileImages> tiles;

	// pointers to the terrain and landscape map (at least one is required)
	Unigine::ObjectLandscapeTerrainPtr terrain;
	Unigine::LandscapeLayerMapPtr lmap;

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;
	int postUpdate() override;
	int updatePhysics() override;

	int shutdown() override;

	int save(const Unigine::StreamPtr &stream) override;
	int restore(const Unigine::StreamPtr &stream) override;

	void makeTileset();
	void applySettings();
	void createTerrain();
	Unigine::EventConnections connections;
	// event handlers for different stages of landscape layer map creation 
	void OnCreatorCreate(const Unigine::LandscapeMapFileCreatorPtr &creator, const Unigine::LandscapeImagesPtr &images, int x, int y);
	void OnCreatorBegin(const Unigine::LandscapeMapFileCreatorPtr &creator);
	void OnCreatorProgress(const Unigine::LandscapeMapFileCreatorPtr &creator);
	void OnCreatorEnd(const Unigine::LandscapeMapFileCreatorPtr &creator);
};

#endif // __APP_WORLD_LOGIC_H__

Insert the following code implementing terrain creation into the AppWorldLogic.cpp file.下面的代码实现地形创建插入AppWorldLogic.cpp文件。

注意
Unchanged methods of the AppWorldLogic class are not listed here, so leave them as they are.这里没有列出AppWorldLogic类的未更改的方法,所以让它们保持原样。

AppWorldLogic.cpp

源代码 (C++)
#include "AppWorldLogic.h"
#include <UnigineConsole.h>
#include <UnigineFileSystem.h>
#include <UnigineInput.h>
#include <UnigineGame.h>
#include <UnigineWorld.h>
using namespace Unigine;
using namespace Math;

// function to be fired on creating a tile
void AppWorldLogic::OnCreatorCreate(const LandscapeMapFileCreatorPtr &creator, const LandscapeImagesPtr &images, int x, int y)
{
	// get number of the current tile
	int tileNumber = x * lmapGridSizeY + y;
	Log::message("Create tile %d\n", tileNumber);

	// set albedo for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].albedoImagePath))
	{
		ImagePtr albedoImage = Image::create(tiles[tileNumber].albedoImagePath);
		if (albedoImage && (albedoImage->getWidth() == lmapTileResolutionX) && (albedoImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr albedo = images->getAlbedo();
			albedo->create2D(albedoImage->getWidth(), albedoImage->getHeight(), albedoImage->getFormat(), albedoImage->getNumMipmaps());
			albedo->copy(albedoImage, 0, 0, 0, 0, albedoImage->getWidth(), albedoImage->getHeight());
		}
		else
			Log::error("The albedo image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Albedo file does not exist.\n");

	// set height for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].heightImagePath))
	{
		ImagePtr heightImage = Image::create(tiles[tileNumber].heightImagePath);
		if (heightImage && (heightImage->getWidth() == lmapTileResolutionX) && (heightImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr height = images->getHeight();
			height->create2D(heightImage->getWidth(), heightImage->getHeight(), heightImage->getFormat(), heightImage->getNumMipmaps());
			height->copy(heightImage, 0, 0, 0, 0, heightImage->getWidth(), height->getHeight());
		}
		else
			Log::error("The height image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Height file does not exist.\n");
}

// function to be executed on beginning the landscape map file generation process
void AppWorldLogic::OnCreatorBegin(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("--------------------\n");
	Log::message("--- %s creation started ---\n", creator->getPath());
	Log::message("lmap creator begin\n");
}

// function to be used for visualizing landscape map file generation progress
void AppWorldLogic::OnCreatorProgress(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("lmap creator progress: %d\n", creator->getProgress());
}

// function to be executed on beginning the landscape map generation process
void AppWorldLogic::OnCreatorEnd(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("lmap creator end\n");
	Log::message("--- %s created ---\n", creator->getPath());
	Log::message("--------------------\n");

	// after creating .lmap file apply settings
	applySettings();

	// and create terrain
	createTerrain();
}

// function applying .lmap file settings
void AppWorldLogic::applySettings()
{
	// load target .lmap file for settings
	LandscapeMapFileSettingsPtr settings = LandscapeMapFileSettings::create();
	settings->load(FileSystem::getGUID(String::format("%s.lmap", lmapName.get())));

	// set parameters and apply them
	if (settings->isLoaded())
	{
		// set alpha blend for height and albedo
		settings->setHeightBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);
		settings->setAlbedoBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);

		settings->setEnabledHeight(true);
		settings->setEnabledAlbedo(true);

		// disable opacity for height and albedo
		settings->setEnabledOpacityAlbedo(false);
		settings->setEnabledOpacityHeight(false);

		settings->apply();
	}
}
// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::createTerrain()
{
	// create new terrain
	terrain = ObjectLandscapeTerrain::create();
	terrain->setActiveTerrain(true);
	terrain->setCollision(true, 0);

	// create layer map based on created .lmap file
	lmap = LandscapeLayerMap::create();
	lmap->setParent(Landscape::getActiveTerrain());
	lmap->setPath(String::format("%s.lmap", lmapName.get()));
	lmap->setName(lmapName.get());
	lmap->setSize(lmapSize);
	lmap->setHeightScale(lmapHeightScale);
	lmap->setWorldPosition(lmapPosition);
	lmap->setWorldRotation(quat(vec3_up, lmapRotation));
}

// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::makeTileset()
{
	// filling
	TileImages timg;
	timg.albedoImagePath = "albedo.png";
	timg.heightImagePath = "height.png";
	int n_tiles = lmapGridSizeX * lmapGridSizeY;
	while (tiles.size() < n_tiles)
		tiles.append(timg);
}
AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

int AppWorldLogic::init()
{
	// set the camera
	Game::getPlayer()->setPosition(Vec3(0.0f, lmapSize.y, 8.0f));
	Game::getPlayer()->worldLookAt(Vec3(lmapSize.x / 2, lmapSize.y / 2, 0.0f));

	// disable nodes created in the template world by default
	NodePtr content_root = World::getNodeByName("content");
	if (content_root)
		content_root->setEnabled(false);
	//enable displaying console messages
	Console::run("console_onscreen 1");

	// collect the tileset
	makeTileset();

	// create .lmap file based on tiles with albedo and height images
	LandscapeMapFileCreatorPtr lmapCreator = LandscapeMapFileCreator::create();
	lmapCreator->setGrid(ivec2(lmapGridSizeX, lmapGridSizeY));
	lmapCreator->setResolution(ivec2(lmapTileResolutionX * lmapGridSizeX, lmapTileResolutionY * lmapGridSizeY));

	lmapCreator->setPath(String::format("%s.lmap", lmapName.get()));

	// subscribe for different stages of *.lmap file creation
	lmapCreator->getEventCreate().connect(connections, this, &AppWorldLogic::OnCreatorCreate);
	lmapCreator->getEventBegin().connect(connections, this, &AppWorldLogic::OnCreatorBegin);
	lmapCreator->getEventProgress().connect(connections, this, &AppWorldLogic::OnCreatorProgress);
	lmapCreator->getEventEnd().connect(connections, this, &AppWorldLogic::OnCreatorEnd);

	// start the creation process
	lmapCreator->run();
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// start of the main loop
////////////////////////////////////////////////////////////////////////////////
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(3)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
int AppWorldLogic::update()
{
	return 1;
}
int AppWorldLogic::postUpdate()
{
	// The engine calls this function after updating each render frame: correct behavior after the state of the node has been updated.
	return 1;
}

int AppWorldLogic::updatePhysics()
{
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
	// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// end of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::shutdown()
{
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is saving its state: save custom user data to a file.
	UNIGINE_UNUSED(stream);
	return 1;
}

int AppWorldLogic::restore(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is restoring its state: restore custom user data to a file here.
	UNIGINE_UNUSED(stream);
	return 1;
}

// ...

Modifying Terrain By Adding New Layer Maps
通过添加新层地图修改地形#

Spawn new Landscape Layer Maps (LandscapeLayerMap) to modify terrain surface, these layer maps can represent vehicle tracks, chunks of trenches, or pits. This way is similar to using Decals: each layer is a separate node, so you can control each modification separately. Moreover, using Landscape Layer Map implies no data density limits, enabling you to achieve realistic results with high-quality insets.生成新Landscape Layer Maps (LandscapeLayerMap)修改地形表面,这些层地图可以代表车辆跟踪、大块的战壕,或者坑。这种方式类似于使用贴纸:每一层是一个独立的节点,所以你可以控制每一个单独修改。此外,使用Landscape Layer Map意味着没有数据密度限制,使您能够实现现实结果与高质量的insets。

Adding Assets
增加资源#

Let's create a new layer map for a crater, to do so, perform the following actions:让我们为一个陨石坑创建一个新的图层地图,执行以下操作:

  1. Save the following images to be used for the crater to your computer:保存以下图片用于坑你的电脑:

    火山口反照率地图 陨石坑高度图
    火山口反照率地图 陨石坑高度图
  2. Switch to UnigineEditor and Drag the crater_height.png file directly to the Asset Browser window to add it to your project (just like you did before for the terrain's Height map). In the Import Dialog for your height map, set Image Format to R32F and click Yes.切换到UnigineEditor并将crater_height.png文件直接拖到Asset Browser窗口,将其添加到您的项目中(就像您之前为地形的高度地图所做的那样)。在你的高度地图的导入对话框中,设置Image FormatR32F,然后单击Yes
  3. Drag the crater_albedo.png file directly to the Asset Browser window (just like you did before for the terrain's Albedo map). In the Import Dialog for your albedo map set Texture Preset to Albedo (RGB — color, A — opacity) and click Yes.crater_albedo.png 文件直接拖到 Asset Browser 窗口(就像您之前为 地形的 Albedo 地图)。 在反照率贴图的导入对话框中,将 Texture Preset 设置为 Albedo (RGB — 颜色,A — 不透明度),然后单击 Yes
  4. Select Create -> Create Landscape Layer Map in the Asset Browser.Asset Browser中选择Create -> Create Landscape Layer Map

  5. Enter a name for the layer map: crater.输入层地图的名称:crater
  6. Select your new crater.lmap asset and adjust its settings as shown below (assign our images, select blending mode and adjust heights range for the crater).选择你的新的crater.lmap资源,并调整其设置如下所示(分配我们的图像,选择混合模式和调整火山口的高度范围)。

  7. Click Reimport and process to the Code section below.单击Reimport并处理到下面的 Code部分。

Code
代码#

So, we have a layer map file representing a crater (crater.lmap). To add a crater to the landscape surface at the desired location we simply create a new LandscapeLayerMap using our file and set its size and transformation.因此,我们有一个表示陨石坑(crater.lmap)的图层映射文件。要在景观表面的理想位置添加一个陨石坑,我们只需使用我们的文件创建一个新的LandscapeLayerMap,并设置它的大小和转换。

Let's implement a method spawning a crater at a specified location (AddCrater()):让我们实现一个方法生成一个陨石坑在指定位置(AddCrater()):

源代码 (C++)
// method spawning a new crater at the specified location
LandscapeLayerMapPtr AddCrater(float x, float y) {
	// create a new layer map
	LandscapeLayerMapPtr crater_lmap = LandscapeLayerMap::create();

	// add the layer map as a child to the active terrain
	crater_lmap->setParent(Landscape::getActiveTerrain());

	// set a path to the crater.lmap file representing a crater
	crater_lmap->setPath(String::format("crater.lmap"));
	crater_lmap->setName("crater");

	// set the size of the crater layer to 5x5 units
	crater_lmap->setSize(Vec2(5.0f, 5.0f));

	// set the height scale multiplier 
	crater_lmap->setHeightScale(0.5f);

	// set the position and rotation of the new layer
	crater_lmap->setWorldPosition(Vec3(x, y, 0.0f));
	crater_lmap->setWorldRotation(quat(vec3_up, 35.0f));

	// set the order of the new layer to place is above the first one (basic)
	crater_lmap->setOrder(2);

	return crater_lmap;
}
注意
Although layer maps are relatively light and fast, genereating too many of them may drop performance.虽然图层映射相对来说比较轻和快,但是生成太多的图层可能会降低性能。

This approach is non-destructive (i.e., it does not irreversibly change initial terrain data). To see the underlaying layer again in its initial state simply disable an overlaying layer map:这种方法是无损(即。,它不不可逆转地改变初始地形数据)。再次见到下垫层在其初始状态只是禁用一个覆盖层地图:

源代码 (C++)
crater_lmap->setEnabled(false);

For example, after disabling a layer map representing a crater, you'll have the surface looking just the way it was before the explosion.例如,在禁用一个表示陨石坑的图层地图后,你将看到表面与爆炸前的样子一样。

To illustrate that let's add the AddCrater() method described above to our AppWorldLogic.cpp file, declare a vector to store all spawned craters, and add handlers for Enter key (spawn a new crater) and Backspace key (show/hide all craters) in the AppWorldLogic::update() method:为了说明这一点,让我们将上面描述的AddCrater()方法添加到我们的AppWorldLogic.cpp文件中,声明一个向量来存储所有生成的陨石坑,并在AppWorldLogic::update()方法中为Enter key(生成一个新的陨石坑)和Backspace(显示/隐藏所有陨石坑)添加处理程序:

源代码 (C++)
// declare a vector to store craters
Unigine::Vector<Unigine::LandscapeLayerMapPtr> craters;

int AppWorldLogic::update()
{
	if (Input::isKeyDown(Input::KEY_BACKSPACE))// spawning a new crater at a random point
		craters.append(AddCrater(Game::getRandomFloat(0.0f, lmapSize.x), Game::getRandomFloat(0.0f, lmapSize.y)));
	else if (Input::isKeyDown(Input::KEY_ENTER))// hiding all craters to show initial landscape state
		for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
			it->get()->setEnabled(!it->get()->isEnabled());
	return 1;
}

int AppWorldLogic::shutdown()
{
	// delete all spawned craters
	for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
		(*it).deleteLater();
	return 1;
}

GPU-Based Terrain Modification
基于GPU的地形修改#

Terrain modification is performed in asynchronous mode on GPU side by calling the asyncTextureDraw method of the Landscape class, that commences a drawing operation. The operation itself is to be implemented inside an event handler.在GPU侧,通过调用Landscape类的asyncTextureDraw方法,在异步模式下进行地形修改,开始绘图操作。操作本身将在回调处理程序中实现。

The workflow here is as follows:这里的工作流程如下:

  1. Implement your GPU-based terrain modification logic in a function.在回调函数中实现基于gpu的地形修改逻辑。
  2. Set this handler function when subscribing for the Texture Draw event (when GPU-based terrain modification operation is performed) via getEventTextureDraw().设置回调函数被解雇时Texture Draw(基于gpu的地形修改)操作是通过调用getEventTextureDraw()方法来执行的。
  3. Commence a GPU drawing operation by calling the asyncTextureDraw() method. Here you should specify the GUID of an .lmap file of the landscape layer map to be modified, the coordinates of the top-left corner and the resolution of the segment of data to be modified, you should also define which data layers are to be affected (heights, albedo, masks) via a set of flags.通过调用 asyncTextureDraw() 方法开始 GPU 绘图操作。 此处应指定要修改的景观图层地图.lmap文件的GUID、左上角坐标和分辨率。 在要修改的数据部分中,您还应该传递一组标志,这些标志 定义您想要影响的数据层(高度、反照率、掩码)。

    注意
    If your modification requires additional data beyond the specified area as well as data of other landscape layer maps (e.g. a copy brush), you can enable force loading of required data. In this case you should use this overload of the asyncTextureDraw() method.如果您的修改需要超出指定区域的额外数据以及其他景观图层地图的数据(例如复制画笔),您可以启用强制加载所需数据。 在这种情况下,您应该使用 asyncTextureDraw() 方法的重载

Adding Assets
增加资源#

Let us modify the Heights and Albedo data of the terrain, so we need two custom maps for that. Save the following images to your computer and drag them to the Asset Browser window to add them to your project (just like you did before for the terrain's Albedo and Height maps):让我们修改地形的高度反照率数据,为此我们需要两个自定义地图。保存以下图片到你的电脑,并将它们拖到Asset Browser窗口,将它们添加到你的项目中(就像你之前对地形的反照率高度地图所做的那样):

自定义反照率地图 自定义高度图
自定义反照率地图 自定义高度图

Don't forget to set Image Format to R32F for your height map and set Texture Preset to Albedo (RGB — color, A — opacity) for your albedo map and reimport them with new settings.不要忘记设置Image Format R32F高度图和设置Texture Preset Albedo (RGB — color, A — opacity)反照率图和再输入新设置。

Code
代码#

Insert the following code into the AppWorldLogic.cpp file.将下面的代码插入到AppWorldLogic.cpp文件。

注意
Unchanged methods of the AppWorldLogic class are not listed here, so leave them as they are.这里没有列出AppWorldLogic类的未更改的方法,所以让它们保持原样。
源代码 (C++)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(1)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// images to be used for terrain modification
Unigine::ImagePtr custom_albedo_image1;
Unigine::ImagePtr custom_height_image1;

// function to be executed on commencing a texture draw operation
void my_texture_draw(const UGUID &guid, int id, const LandscapeTexturesPtr &buffer, const ivec2 &coord, int data_mask)
{
	// preparing images for terrain modification 
	// Note: image preparation block can be moved outside the event handler, not to repeat these operations every time,
	// e.g. you can move this block to a place before calling the asyncTextureDraw() method 
	{
		// create a new image to load a custom albedo map to
		custom_albedo_image1 = Image::create("custom_albedo.png");

		// set the format required for the albedo map - RGBA8
		custom_albedo_image1->convertToFormat(Image::FORMAT_RGBA8);

		// create a new image to load a custom height map to
		custom_height_image1 = Image::create("custom_height.png");

		// set the format required for the heightmap - R32F
		custom_height_image1->convertToFormat(Image::FORMAT_R32F);
	}
	// resize albedo and height images to fit the area to be modified
	custom_albedo_image1->resize(buffer->getResolution().x, buffer->getResolution().y);
	custom_height_image1->resize(buffer->getResolution().x, buffer->getResolution().y);

	// setting our custom image to the albedo buffer
	buffer->getAlbedo()->setImage(custom_albedo_image1);

	// setting our custom image to the height buffer
	buffer->getHeight()->setImage(custom_height_image1);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(2)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

int AppWorldLogic::init()
{
	// add a function to be executed on a Texture Draw operation
	Landscape::getEventTextureDraw().connect(connections, my_texture_draw);
	return 1;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(3)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

int AppWorldLogic::update()
{
	if (Input::isKeyPressed(Input::KEY_SPACE)) {
		// get the first layermap that we're going to modify
		LandscapeLayerMapPtr lmap = checked_ptr_cast <LandscapeLayerMap> (terrain->getChild(0));

		// generate new ID for the draw operation
		int id = Landscape::generateOperationID();

		// user's code (bounding to ID)
		// commence a Texture Draw operation for the selected landscape map at (10, 10) with the size of [512 x 512]
		Landscape::asyncTextureDraw(id, lmap->getGUID(), ivec2(10, 10), ivec2(512, 512), (int)(Landscape::FLAGS_FILE_DATA_HEIGHT | Landscape::FLAGS_FILE_DATA_ALBEDO));
	}
	return 1;
}

int AppWorldLogic::shutdown()
{
	// remove all event subscriptions on shutdown
	connections.disconnectAll();
	return 1;
}

Resulting Demonstration Code
结果演示代码#

Copy the source code below and paste it to the corresponding source files of your project.复制下面的代码粘贴到相应的源文件的项目。

AppWorldLogic.h

源代码 (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineImage.h>

class AppWorldLogic : public Unigine::WorldLogic
{
	// Layer map parameters
	// position, rotation and scaling factor along the Z axis 
	Unigine::Math::Vec3 lmapPosition = Unigine::Math::Vec3_zero;
	float lmapRotation = 0.0f;
	float lmapHeightScale = 1.0f;

	// size of the layer map (in units)
	Unigine::Math::Vec2 lmapSize = Unigine::Math::Vec2(20.0f, 20.0f);

	// landscape layer map grid size (number of cells along X and Y axes)
	int lmapGridSizeX = 2;
	int lmapGridSizeY = 2;

	// resolution of a single tile of the layer map 
	// (tile images to be used must have the same resolution)
	int lmapTileResolutionX = 512;
	int lmapTileResolutionY = 512;

	// layer map name
	Unigine::String lmapName = "map";

	// set of images for a single tile
	struct TileImages
	{
		Unigine::String albedoImagePath;
		Unigine::String heightImagePath;
	};

	// vector for the tileset data
	Unigine::Vector<TileImages> tiles;

	// pointers to the terrain and landscape map (at least one is required)
	Unigine::ObjectLandscapeTerrainPtr terrain;
	Unigine::LandscapeLayerMapPtr lmap;

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;
	int postUpdate() override;
	int updatePhysics() override;

	int shutdown() override;

	int save(const Unigine::StreamPtr &stream) override;
	int restore(const Unigine::StreamPtr &stream) override;

	void makeTileset();
	void applySettings();
	void createTerrain();
	Unigine::EventConnections connections;
	// event handlers for different stages of landscape layer map creation 
	void OnCreatorCreate(const Unigine::LandscapeMapFileCreatorPtr &creator, const Unigine::LandscapeImagesPtr &images, int x, int y);
	void OnCreatorBegin(const Unigine::LandscapeMapFileCreatorPtr &creator);
	void OnCreatorProgress(const Unigine::LandscapeMapFileCreatorPtr &creator);
	void OnCreatorEnd(const Unigine::LandscapeMapFileCreatorPtr &creator);
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cpp

源代码 (C++)
#include "AppWorldLogic.h"
#include <UnigineConsole.h>
#include <UnigineFileSystem.h>
#include <UnigineInput.h>
#include <UnigineGame.h>
#include <UnigineWorld.h>
using namespace Unigine;
using namespace Math;

// images to be used for terrain modification
Unigine::ImagePtr custom_albedo_image1;
Unigine::ImagePtr custom_height_image1;

// function to be executed on commencing a texture draw operation
void my_texture_draw(const UGUID &guid, int id, const LandscapeTexturesPtr &buffer, const ivec2 &coord, int data_mask)
{
	// resize albedo and height images to fit the area to be modified
	custom_albedo_image1->resize(buffer->getResolution().x, buffer->getResolution().y);
	custom_height_image1->resize(buffer->getResolution().x, buffer->getResolution().y);

	// setting our custom image to the albedo buffer
	buffer->getAlbedo()->setImage(custom_albedo_image1);

	// setting our custom image to the height buffer
	buffer->getHeight()->setImage(custom_height_image1);
}
// declare a vector to store craters
Unigine::Vector<Unigine::LandscapeLayerMapPtr> craters;
// method spawning a new crater at the specified location
LandscapeLayerMapPtr AddCrater(float x, float y) {
	// create a new layer map
	LandscapeLayerMapPtr crater_lmap = LandscapeLayerMap::create();

	// add the layer map as a child to the active terrain
	crater_lmap->setParent(Landscape::getActiveTerrain());

	// set a path to the crater.lmap file representing a crater
	crater_lmap->setPath(String::format("crater.lmap"));
	crater_lmap->setName("crater");

	// set the size of the crater layer to 5x5 units
	crater_lmap->setSize(Vec2(5.0f, 5.0f));

	// set the height scale multiplier 
	crater_lmap->setHeightScale(0.5f);

	// set the position and rotation of the new layer
	crater_lmap->setWorldPosition(Vec3(x, y, 0.0f));
	crater_lmap->setWorldRotation(quat(vec3_up, 35.0f));

	// set the order of the new layer to place is above the first one (basic)
	crater_lmap->setOrder(2);

	return crater_lmap;
}

// function to be fired on creating a tile
void AppWorldLogic::OnCreatorCreate(const LandscapeMapFileCreatorPtr &creator, const LandscapeImagesPtr &images, int x, int y)
{
	// get number of the current tile
	int tileNumber = x * lmapGridSizeY + y;
	Log::message("Create tile %d\n", tileNumber);

	// set albedo for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].albedoImagePath))
	{
		ImagePtr albedoImage = Image::create(tiles[tileNumber].albedoImagePath);
		if (albedoImage && (albedoImage->getWidth() == lmapTileResolutionX) && (albedoImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr albedo = images->getAlbedo();
			albedo->create2D(albedoImage->getWidth(), albedoImage->getHeight(), albedoImage->getFormat(), albedoImage->getNumMipmaps());
			albedo->copy(albedoImage, 0, 0, 0, 0, albedoImage->getWidth(), albedoImage->getHeight());
		}
		else
			Log::error("The albedo image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Albedo file does not exist.\n");

	// set height for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].heightImagePath))
	{
		ImagePtr heightImage = Image::create(tiles[tileNumber].heightImagePath);
		if (heightImage && (heightImage->getWidth() == lmapTileResolutionX) && (heightImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr height = images->getHeight();
			height->create2D(heightImage->getWidth(), heightImage->getHeight(), heightImage->getFormat(), heightImage->getNumMipmaps());
			height->copy(heightImage, 0, 0, 0, 0, heightImage->getWidth(), height->getHeight());
		}
		else
			Log::error("The height image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Height file does not exist.\n");
}

// function to be executed on beginning the landscape map file generation process
void AppWorldLogic::OnCreatorBegin(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("--------------------\n");
	Log::message("--- %s creation started ---\n", creator->getPath());
	Log::message("lmap creator begin\n");
}

// function to be used for visualizing landscape map file generation progress
void AppWorldLogic::OnCreatorProgress(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("lmap creator progress: %d\n", creator->getProgress());
}

// function to be executed on beginning the landscape map generation process
void AppWorldLogic::OnCreatorEnd(const LandscapeMapFileCreatorPtr &creator)
{
	Log::message("lmap creator end\n");
	Log::message("--- %s created ---\n", creator->getPath());
	Log::message("--------------------\n");

	// after creating .lmap file apply settings
	applySettings();

	// and create terrain
	createTerrain();
}

// function applying .lmap file settings
void AppWorldLogic::applySettings()
{
	// load target .lmap file for settings
	LandscapeMapFileSettingsPtr settings = LandscapeMapFileSettings::create();
	settings->load(FileSystem::getGUID(String::format("%s.lmap", lmapName.get())));

	// set parameters and apply them
	if (settings->isLoaded())
	{
		// set alpha blend for height and albedo
		settings->setHeightBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);
		settings->setAlbedoBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);

		settings->setEnabledHeight(true);
		settings->setEnabledAlbedo(true);

		// disable opacity for height and albedo
		settings->setEnabledOpacityAlbedo(false);
		settings->setEnabledOpacityHeight(false);

		settings->apply();
	}
}
// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::createTerrain()
{
	// create new terrain
	terrain = ObjectLandscapeTerrain::create();
	terrain->setActiveTerrain(true);
	terrain->setCollision(true, 0);

	// create layer map based on created .lmap file
	lmap = LandscapeLayerMap::create();
	lmap->setParent(Landscape::getActiveTerrain());
	lmap->setPath(String::format("%s.lmap", lmapName.get()));
	lmap->setName(lmapName.get());
	lmap->setSize(lmapSize);
	lmap->setHeightScale(lmapHeightScale);
	lmap->setWorldPosition(lmapPosition);
	lmap->setWorldRotation(quat(vec3_up, lmapRotation));
}

// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::makeTileset()
{
	// filling
	TileImages timg;
	timg.albedoImagePath = "albedo.png";
	timg.heightImagePath = "height.png";
	int n_tiles = lmapGridSizeX * lmapGridSizeY;
	while (tiles.size() < n_tiles)
		tiles.append(timg);
}
AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

int AppWorldLogic::init()
{
	// set the camera
	Game::getPlayer()->setPosition(Vec3(0.0f, lmapSize.y, 8.0f));
	Game::getPlayer()->worldLookAt(Vec3(lmapSize.x / 2, lmapSize.y / 2, 0.0f));

	// disable nodes created in the template world by default
	NodePtr content_root = World::getNodeByName("content");
	if (content_root)
		content_root->setEnabled(false);
	//enable displaying console messages
	Console::run("console_onscreen 1");

	// collect the tileset
	makeTileset();

	// create .lmap file based on tiles with albedo and height images
	LandscapeMapFileCreatorPtr lmapCreator = LandscapeMapFileCreator::create();
	lmapCreator->setGrid(ivec2(lmapGridSizeX, lmapGridSizeY));
	lmapCreator->setResolution(ivec2(lmapTileResolutionX * lmapGridSizeX, lmapTileResolutionY * lmapGridSizeY));

	lmapCreator->setPath(String::format("%s.lmap", lmapName.get()));

	// subscribe for different stages of *.lmap file creation
	lmapCreator->getEventCreate().connect(connections, this, &AppWorldLogic::OnCreatorCreate);
	lmapCreator->getEventBegin().connect(connections, this, &AppWorldLogic::OnCreatorBegin);
	lmapCreator->getEventProgress().connect(connections, this, &AppWorldLogic::OnCreatorProgress);
	lmapCreator->getEventEnd().connect(connections, this, &AppWorldLogic::OnCreatorEnd);

	// start the creation process
	lmapCreator->run();
	// preparing images for terrain modification 
	// create a new image to load a custom albedo map to
	custom_albedo_image1 = Image::create("custom_albedo.png");

	// set the format required for the albedo map - RGBA8
	custom_albedo_image1->convertToFormat(Image::FORMAT_RGBA8);

	// create a new image to load a custom height map to
	custom_height_image1 = Image::create("custom_height.png");

	// set the format required for the heightmap - R32F
	custom_height_image1->convertToFormat(Image::FORMAT_R32F);
	// add a function to be executed on a Texture Draw operation
	Landscape::getEventTextureDraw().connect(connections, my_texture_draw);
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// start of the main loop
////////////////////////////////////////////////////////////////////////////////
int AppWorldLogic::update()
{
	if (Input::isKeyDown(Input::KEY_BACKSPACE))// spawning a new crater at a random point
		craters.append(AddCrater(Game::getRandomFloat(0.0f, lmapSize.x), Game::getRandomFloat(0.0f, lmapSize.y)));
	else if (Input::isKeyDown(Input::KEY_ENTER))// hiding all craters to show initial landscape state
		for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
			it->get()->setEnabled(!it->get()->isEnabled());
	if (Input::isKeyPressed(Input::KEY_SPACE)) {
		// get the first layermap that we're going to modify
		LandscapeLayerMapPtr lmap = checked_ptr_cast <LandscapeLayerMap> (terrain->getChild(0));

		// generate new ID for the draw operation
		int id = Landscape::generateOperationID();

		// user's code (bounding to ID)
		// commence a Texture Draw operation for the selected landscape map at (10, 10) with the size of [512 x 512]
		Landscape::asyncTextureDraw(id, lmap->getGUID(), ivec2(10, 10), ivec2(512, 512), (int)(Landscape::FLAGS_FILE_DATA_HEIGHT | Landscape::FLAGS_FILE_DATA_ALBEDO));
	}
	return 1;
}
int AppWorldLogic::postUpdate()
{
	// The engine calls this function after updating each render frame: correct behavior after the state of the node has been updated.
	return 1;
}

int AppWorldLogic::updatePhysics()
{
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
	// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// end of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::shutdown()
{
	// delete all spawned craters
	for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
		(*it).deleteLater();
	// remove all event subscriptions on shutdown
	connections.disconnectAll();
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is saving its state: save custom user data to a file.
	UNIGINE_UNUSED(stream);
	return 1;
}

int AppWorldLogic::restore(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is restoring its state: restore custom user data to a file here.
	UNIGINE_UNUSED(stream);
	return 1;
}
最新更新: 2024-08-16
Build: ()