Assimp for Unreal

From Epic Wiki
Jump to: navigation, search

Template:Rating

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

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 <syntaxhighlight lang=csharp>using System.IO;</syntaxhighlight> to the header of it, then <syntaxhighlight lang=csharp> 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"));         } </syntaxhighlight> 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 : AssimpInterface.h <syntaxhighlight lang=csharp>// Fill out your copyright notice in the Description page of Project Settings.

  1. pragma once

//#include <string>

  1. include <assimp/Importer.hpp>
  2. include <assimp/scene.h>
  3. include <assimp/postprocess.h>
  4. include "Components/ActorComponent.h"
  5. 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); }; </syntaxhighlight>

AssimpInterface.cpp <syntaxhighlight lang=csharp> // Fill out your copyright notice in the Description page of Project Settings.

  1. include "Noxel.h"
  2. include "AssimpInterface.h"
  3. 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[i]];         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[i], 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[i].x;         vertex.Y = mesh->mVertices[i].y;         vertex.Z = mesh->mVertices[i].z;

        normal.X = mesh->mNormals[i].x;         normal.Y = mesh->mNormals[i].y;         normal.Z = mesh->mNormals[i].z;

        // if the mesh contains tex coords         if (mesh->mTextureCoords[0]) {             FVector2D uvs;             uvs.X = mesh->mTextureCoords[0][i].x;             uvs.Y = mesh->mTextureCoords[0][i].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[i];             _indices[_meshCurrentlyProcessed].Add(face.mIndices[2]);             _indices[_meshCurrentlyProcessed].Add(face.mIndices[1]);             _indices[_meshCurrentlyProcessed].Add(face.mIndices[0]);         }     } }</syntaxhighlight>


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.

- Moddingear