What is Assimp ?
Assimp is an open-source library that allows you to import and export 3D models at runtime, using C++, though you can write a Blueprint interface for it quite easily.
It supports a lot of formats.
This tutorial will mostly cover the setup-part of it, as I found nearly no good tutorial on it, so this will go into details about the installation, afterwards, you can just look for the documentation. Installation
Required files
- The Assimp source code; you can get it here : Releases · assimp/assimp · GitHub
- CMake, to build the Visual Studio project from the source for your specific Visual Studio, you can get it here : Download | CMake
- A Visual Studio version
Building the Visual Studio Project
Now that you have downloaded and installed the required things, extract the Assimp zip somewhere in a folder, that we’ll call “Assimp”, so it should be “Assimp/Version”, in my case “Version” is “assimp-4.0.1”.
Now, create two new folder in “Assimp”, naming them “Win32” and “Win64”, and copy “assimp-4.0.1” into both of them.
Open a command prompt, and type "cmake ", then drag&drop the CMakeLists.txt from “Assimp/Win32/assimp-4.0.1”, it’ll create the path in CMD for you. Then, press enter, it’ll build the VS project file, that may take a while depending on you PC.
Once that is done, type :
“cmake -G”. That will give you a list of compilers that you can build you project with. Then, do the same thing as before, but entering the “-G” followed by your compiler name and " Win64", in my case this would be "cmake -G “Visual Studio 14 2015 Win64” ", but this time, drop the CmakeLists.txt from Win64.
Compiling Assimp
The procedure will now be the same for both Win32 and Win64 folders, so no need to repeat myself. Just change Win32 by Win64
Go into “Win32/assimp-4.0.1” and open Assimp.sln
At the top left of Visual Studio, select “Release”, next to Win32, otherwise you’ll have a hard time when deploying your game.
Now you can just build it (CTRL+SHIFT+B).
Adding it to Unreal Engine
Note that the name of the libraries and dlls will change according to you Visual Studio version. For VS 14 it’s assimp-vs140-mt, etc.
Under “assimp-4.0.1/bin/release” you’ll find “assimp-vc140-mt” for both Win32 and Win64. You will need to add it to “YourGame/Binaries/Win32 or Win64/” and under “WindowsNoEditor/YourGame/Binaries/Win32 or Win64/” (for your packaged builds). Don’t rename it.
Now under “assimp-4.0.1/lib/release” you will find “assimp-vc140-mt.lib”. You can put it anywhere, but I recommend putting it next to the DLLs in your project files (“YourGame/Binaries/Win32 or Win64”), and you don’t need to add it to your packaged builds.
Next step is to add the include. Just copy it from any of the assimps (You can find it in “assimp-4.0.1/include”) and copy it next to your source code (Consider putting it into an “include” folder)
Now, we will need to modify the Build.cs of Unreal Engine. If your game happens to be pure Blueprint, create an ActorComponent, we will use it as our interface with Assimp, and that will create the Build.cs for you.
Add
using System.IO;
to the header of it, then
if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32))
{
string PathToProject = "D:\\Documents\\Unreal Projects\\Noxel";
PublicIncludePaths.Add(Path.Combine(PathToProject, "Source\\Noxel\\include"));
string PlatformString = (Target.Platform == UnrealTargetPlatform.Win64) ? "Win64" : "Win32";
string LibrariesPath = Path.Combine(PathToProject, "Binaries", PlatformString);
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "assimp-vc140-mt.lib"));
}
inside of the ReadOnlyTargetRules, so that it looks a bit like this :
and that’s it !
You can now use Assimp with Unreal.
Bonus code to get you started : [SPOILER]
AssimpInterface.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
//#include <string>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "Components/ActorComponent.h"
#include "AssimpInterface.generated.h"
/**
*
*/
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class NOXEL_API UAssimpInterface : public UActorComponent
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Assimp")
bool openMesh(FString path, int32& SectionCount, FString& ErrorCode);
UFUNCTION(BlueprintCallable, Category = "Assimp")
bool getSection(int32 index, TArray<FVector>& Vertices, TArray<int32>& Faces, TArray<FVector>& Normals, TArray<FVector2D>& UV, TArray<FVector>& Tangents);
UFUNCTION(BlueprintCallable, Category = "Assimp")
void clear();
private:
int32 _selectedVertex;
int32 _meshCurrentlyProcessed;
bool _addModifier;
int _lastModifiedTime;
bool _requiresFullRecreation;
TArray<TArray<FVector>> _vertices;
TArray<TArray<int32>> _indices;
TArray<TArray<FVector>> _normals;
TArray<TArray<FVector2D>> _uvs;
TArray<TArray<FVector>> _tangents;
TArray<TArray<FColor>> _vertexColors;
void processMesh(aiMesh* mesh, const aiScene* scene);
void processNode(aiNode* node, const aiScene* scene);
};
AssimpInterface.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Noxel.h"
#include "AssimpInterface.h"
#include "../Public/AssimpInterface.h"
bool UAssimpInterface::openMesh(FString path, int32& SectionCount, FString& ErrorCode)
{
Assimp::Importer importer;
std::string filename(TCHAR_TO_UTF8(*path));
const aiScene* scene = importer.ReadFile(filename, aiProcessPreset_TargetRealtime_MaxQuality);
if (!scene)
{
ErrorCode = importer.GetErrorString();
return false;
}
_meshCurrentlyProcessed = 0;
processNode(scene->mRootNode, scene);
SectionCount = _meshCurrentlyProcessed;
return true;
}
bool UAssimpInterface::getSection(int32 index, TArray<FVector>& Vertices, TArray<int32>& Faces, TArray<FVector>& Normals, TArray<FVector2D>& UV, TArray<FVector>& Tangents)
{
if (index>=_meshCurrentlyProcessed)
{
return false;
}
Vertices = _vertices[index];
Faces = _indices[index];
Normals = _normals[index];
UV = _uvs[index];
Tangents = _tangents[index];
return true;
}
void UAssimpInterface::clear()
{
_vertices.Empty();
_indices.Empty();
_normals.Empty();
_uvs.Empty();
_tangents.Empty();
_vertexColors.Empty();
_meshCurrentlyProcessed = 0;
}
void UAssimpInterface::processNode(aiNode* node, const aiScene* scene)
{
for (uint32 i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes*];
processMesh(mesh, scene);
++_meshCurrentlyProcessed;
}
uint32 nodes = node->mNumMeshes;
// do the same for all of its children
for (uint32 i = 0; i < node->mNumChildren; i++) {
processNode(node->mChildren*, scene);
}
}
void UAssimpInterface::processMesh(aiMesh* mesh, const aiScene* scene)
{
// the very first time this method runs, we'll need to create the empty arrays
// we can't really do that in the class constructor because we don't know how many meshes we'll read, and this data can change between imports
if (_vertices.Num() <= _meshCurrentlyProcessed) {
_vertices.AddZeroed();
_normals.AddZeroed();
_uvs.AddZeroed();
_tangents.AddZeroed();
_vertexColors.AddZeroed();
_indices.AddZeroed();
}
// we check whether the current data to read has a different amount of vertices compared to the last time we generated the mesh
// if so, it means we'll need to recreate the mesh and resupply new indices.
if (mesh->mNumVertices != _vertices[_meshCurrentlyProcessed].Num())
_requiresFullRecreation = true;
// we reinitialize the arrays for the new data we're reading
_vertices[_meshCurrentlyProcessed].Empty();
_normals[_meshCurrentlyProcessed].Empty();
_uvs[_meshCurrentlyProcessed].Empty();
// this if actually seems useless, seeing what it does without it
//if (_requiresFullRecreation) {
_tangents[_meshCurrentlyProcessed].Empty();
_vertexColors[_meshCurrentlyProcessed].Empty();
_indices[_meshCurrentlyProcessed].Empty();
//}
for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
FVector vertex, normal;
// process vertex positions, normals and UVs
vertex.X = mesh->mVertices*.x;
vertex.Y = mesh->mVertices*.y;
vertex.Z = mesh->mVertices*.z;
normal.X = mesh->mNormals*.x;
normal.Y = mesh->mNormals*.y;
normal.Z = mesh->mNormals*.z;
// if the mesh contains tex coords
if (mesh->mTextureCoords[0]) {
FVector2D uvs;
uvs.X = mesh->mTextureCoords[0]*.x;
uvs.Y = mesh->mTextureCoords[0]*.y;
_uvs[_meshCurrentlyProcessed].Add(uvs);
}
else {
_uvs[_meshCurrentlyProcessed].Add(FVector2D(0.f, 0.f));
}
_vertices[_meshCurrentlyProcessed].Add(vertex);
_normals[_meshCurrentlyProcessed].Add(normal);
}
if (_requiresFullRecreation) {
// process indices
for (uint32 i = 0; i < mesh->mNumFaces; i++) {
aiFace face = mesh->mFaces*;
_indices[_meshCurrentlyProcessed].Add(face.mIndices[2]);
_indices[_meshCurrentlyProcessed].Add(face.mIndices[1]);
_indices[_meshCurrentlyProcessed].Add(face.mIndices[0]);
}
}
}
[/SPOILER]
BTW, this is the first tutorial I make, so tell me how I did. I’d have really liked to have it when I started implementing Assimp into my game, so tell me when you used it.