[TUTORIAL] Using Assimp with Unreal Engine

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


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.

3 Likes

Great tutorial, very detailed!

I want to add a few things. It’s possible to use Assimp without needing to compile it, if you’re willing to use an older version (3.1.1 instead of the current 4.0.1). The steps are the following (note that I’m consering the 64 bit version, but it’s the same for the 32 bit, just replace the numbers when needed):

  1. Download Assimp version 3.1.1 from here: https://sourceforge.net/projects/assimp/files/assimp-3.1/(the binaries version, called “assimp-3.1.1-win-binaries.zip”)

  2. Extract the folder somewhere where it’s safe (for instance “C:\assimp-3.1.1-win-binaries”)

  3. Go to “assimp-3.1.1-win-binaries\lib64” and copy the “assimp.lib” file to this folder inside your UE4 project: “YourProject/Binaries/Win64/”

  4. Open the Build.cs file in your project and add these lines:



    PublicIncludePaths.Add("YourPathToAssimpFolder\\assimp-3.1.1-win-binaries\\include");
    PublicAdditionalLibraries.Add("YourPathToAssimpFolder\\assimp-3.1.1-win-binaries\\lib64\\assimp.lib");
    

It should look something this:


    public class MyProject : ModuleRules
{
    public MyProject(TargetInfo Target)
    {
        MinFilesUsingPrecompiledHeaderOverride = 1;
        bFasterWithoutUnity = true;

        PublicDependencyModuleNames.AddRange(new string] { "Core",
        "CoreUObject", "Engine", "InputCore",
        "HeadMountedDisplay",               // For HMD
        "SteamVR", "SteamVRController",     // For SteamVR functions access
        "ProceduralMeshComponent",          // For using UProceduralMeshComponent ojbects to create mesh at runtime
        "UMG", "Slate", "SlateCore",        // For using UMG in C++
        "APPFRAMEWORK" });                  // For extending UE4 Color Picker to UMG

        // This is for Assimp version 3.1.1
        PublicIncludePaths.Add("C:\\assimp-3.1.1-win-binaries\\include");
        PublicAdditionalLibraries.Add("C:\\assimp-3.1.1-win-binaries\\lib64\\assimp.lib");
    }
}

    

  1. All done, now you can use Assimp.

Last thing: I want to add a code sample (not made by me) which would be useful too: GitHub - LaP0573/ue4-fbx-importer

I actually based myself off of that code sample to create the one I shared.

Also, I used to use the prebuilt 3.1.1 version, but it has one bug that isn’t particularly awesome : The 32-bit version of the library references assimp.exe instead of assimp.dll, so you can’t really use it for 32-bit.
Also, this isn’t really much longer to do than using the prebuilt, when you know how to do it.

**It is very helpful.I have been not able to understand **where to create AssimpInterface.h and AssimpInterface.cpp files.

You just need to create them in the C++ code of your project.

Thanks for Reply…

I had tried as per your suggetion with unreal version 4.17 and visual studio 2017.

But gets below errors:

Yes, you need to replace all instance of Noxel with the name of your game in cpp files provided as example.

Thanks!
This was vvery usefull, created a model loader for use with my level loader for in my game.
VS2017 is quite new for me so this really helped!

Nice.
I recommend VS 2015 though if you want to have 32-bit.

Would love to use 2015, but i thought it wasnt supported anymore with newer UE4 versions?

That’s VS 2013 you’re talking about, VS 2015 is still supported because VS 2017 doesn’t allow 32-bit compiles or Linux/MacOS afaik (Last time I checked was on 4.16).

Oh, well then i will try to set it up with vs2015 tomorrow, thanks.

Hi Moddingear and thank you for this great tutorial.

I made an empty actor and I’ve added the AssimpInterface component to it. It’s all working fine for me and it’s very solid.
The actor is placed at 0,0,0 coordinates.

The problem I have is that it loads the external 3d models without taking into account their original pivot location. It basically loads the models as if their pivots were centred to the model and not placed where I need them to be. I am running a system where coordinates and precise placement is important.

To overcome this, I have a simple UI for a location offset but it’s not ideal to change it for every single load. Do you have any idea of what to do and where (code, 3d authoring program, UE4 editor)?

Many Thanks,

Very helpful tutorial, thank you. It works best in windows, how can I compile this for linux? Is there a way to assimp linux library to the compile path in c#? tried this, but it doesn’t work.
PublicAdditionalLibraries.Add(System.IO.Path.GetFullPath( System.IO.Path.Combine(ModuleDirectory, “…/…/libassimp.so.4.0.1”)));

To compile it for Linux, find the correct VS compiler for it, using


cmake -G

it’ll list them all for you. Find the one that is Linux and that should generate the correct project for it

try using


FPaths::ProjectConfigDir()

and to combine pathes, since they’re juste FString, just use


path1 + "/" + path2

To do that i’d recommend using the RuntimeMeshComponent, since loading a mesh at runtime makes it non-static. here’s the discord, i’ll be there : Runtime Mesh Component

Same issue here.
I open a file with AssimpInterface, add a procedural mesh component for each section and all of the objects end up at the actor’s origin ignoring the transform values they may have had before importing.
It would be great if the getSection node could give you something like its local transform but implementing that is really hard to do for someone that has 0 knowledge of C++
Maybe Moddingear can give us a hint towards some documentation or something to look into.

“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.”

Somebody can help me understand what does it mean and how can I follow this step?
I mean, where I can find the Build.cs file?

It means that if you’re using a “blueprint only” project, you have to turn it into a “C++” project. Then, the “Build.cs” files will appear in the following path:
<PathToYourProjectDirectory>/Source/<NameOfYourProject>/

If your project is called “HelloProject” then the path to the Build.cs will look like this:
<PathToYourProjectDirectory>/Source/HelloProject/HelloProject.Build.cs

Thank you for helping me, I was a bit lost…
Now I’m trying to use the example code provided by Moddingear, but I don’t really understand how to import a mesh through blueprint:

  • I created an actor that has a scene/root component, a Procedural Mesh, and the “AssimpImporter” component
  • I created (on begin play, just for the sake of testing) the Open Asset node, wrote the path to the folder where the fbx is
  • Created a “Get Section” node and then a Create Mesh Section node
    But nothing happens, I imagine that I’m missing something or I’m not understanding something
    Also, how can I find the triangles from the imported mesh?

–screenshot for reference

Hey, guys, is there anyone know how to use Assimp on mobile(Android/ios) &UE4?
Struggle with it few days, can anyone help me?