Commit 3596e8e1 authored by Jan Kremer's avatar Jan Kremer

Added some plugin source and content files.

parent b1f86f1f
......@@ -2,5 +2,9 @@
/Saved
/.vs
/Binaries
/Plugins
/ThirdParty
/Plugins/UnrealEnginePython
/Plugins/GPUPointCloudRenderer/Binaries
/Plugins/GPUPointCloudRenderer/Intermediate
/Plugins/PointCloudPlugin/Binaries
/Plugins/PointCloudPlugin/Intermediate
\ No newline at end of file
/Binaries
/Intermediate
*.lib
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"EngineVersion": "4.21.0",
"FriendlyName": "GPU Point Cloud Renderer",
"Description": "A GPU-powered point cloud renderer.",
"Category": "Rendering",
"CreatedBy": "Valentin Kraft",
"CreatedByURL": "http://www.valentinkraft.de",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": true,
"IsBetaVersion": false,
"Installed": false,
"WhitelistPlatforms": [ "Win64" ],
"Modules": [
{
"Name": "GPUPointCloudRenderer",
"Type": "Runtime",
"LoadingPhase": "PreDefault"
},
{
"Name": "GPUPointCloudRendererEditor",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}
\ No newline at end of file
MIT License
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**Unreal Engine GPU Point Cloud Renderer**
This is a GPU-based Plugin for Real-Time rendering of dynamic and massive point cloud data in Unreal. This project is still under development and not (yet) a finalised plugin.
__This plugin is only for rendering point clouds and does NOT load point cloud files or visualise Kinect data.__
__For loading PCD files, processing point clouds with PCL or fetching data from a Kinect, another plugin will follow soon.__
Currently supported Unreal Versions:
* __UE4.21__
* UE4.19 (see other branch)
The ComputeShader-powered version for proper in-place depth-ordering of the points is still in development (see other branch).
__Basics Tutorial:__
[![Tutorial](https://img.youtube.com/vi/95rdEG5H8sI/0.jpg)](https://www.youtube.com/watch?v=95rdEG5H8sI)
__Demo - Huge static point cloud:__
[![Demo](https://img.youtube.com/vi/5LH6IZdmxK4/0.jpg)](https://www.youtube.com/watch?v=5LH6IZdmxK4)
__Demo - Kinect live stream:__
[![Demo](https://img.youtube.com/vi/LZwG054LC4A/0.jpg)](https://www.youtube.com/watch?v=LZwG054LC4A)
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System.IO;
using System;
namespace UnrealBuildTool.Rules
{
public class GPUPointCloudRenderer : ModuleRules
{
private string ModulePath
{
get { return ModuleDirectory; }
}
private string ThirdPartyPath
{
get { return Path.GetFullPath(Path.Combine(ModulePath, "../ThirdParty/")); }
}
private string BinariesPath
{
get { return Path.GetFullPath(Path.Combine(ModulePath, "../../Binaries/")); }
}
public GPUPointCloudRenderer(ReadOnlyTargetRules Target) : base(Target)
{
PrivateIncludePaths.Add("GPUPointCloudRenderer/Private");
PublicIncludePaths.Add("GPUPointCloudRenderer/Public");
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "RHI" });
PrivateDependencyModuleNames.AddRange(new string[] { "Core", "Projects" });
}
}
}
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#include "CoreMinimal.h"
#include "PointCloudStreamingCore.h"
#include "Modules/ModuleManager.h"
#include "IGPUPointCloudRenderer.h"
class FGPUPointCloudRendererPlugin : public IGPUPointCloudRenderer
{
virtual void StartupModule() override
{
}
virtual void ShutdownModule() override
{
}
/**
* Returns a instance of the Point Cloud Core class.
*/
FPointCloudStreamingCore* CreateStreamingInstance(UMaterialInstanceDynamic* pointCloudShaderDynInstance) {
return new FPointCloudStreamingCore(pointCloudShaderDynInstance);
}
};
IMPLEMENT_MODULE(FGPUPointCloudRendererPlugin, GPUPointCloudRenderer)
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#pragma once
#include "CoreMinimal.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
/**
* The public interface to the Point Cloud Renderer module.
* @Author Valentin Kraft
*/
class GPUPOINTCLOUDRENDERER_API IGPUPointCloudRenderer : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline IGPUPointCloudRenderer& Get()
{
return FModuleManager::LoadModuleChecked< IGPUPointCloudRenderer >( "GPUPointCloudRenderer" );
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded( "GPUPointCloudRenderer" );
}
/**
* Returns a instance of the Point Cloud Streaming Core class.
*/
virtual FPointCloudStreamingCore* CreateStreamingInstance(UMaterialInstanceDynamic* pointCloudShaderDynInstance = nullptr) = 0;
};
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#pragma once
#define MAXTEXRES 2048
#include "CoreMinimal.h"
#include "Runtime/Engine/Classes/Engine/Texture2D.h"
DECLARE_STATS_GROUP(TEXT("GPUPointCloudRenderer"), STATGROUP_GPUPCR, STATCAT_Advanced);
class GPUPOINTCLOUDRENDERER_API FPointCloudStreamingCore
{
public:
FPointCloudStreamingCore(UMaterialInstanceDynamic* pointCloudShaderDynInstance = nullptr) { mDynamicMatInstance = pointCloudShaderDynInstance; };
~FPointCloudStreamingCore();
//virtual unsigned int GetInstanceId() const { return _instanceId; };
unsigned int GetPointCount() { return mPointCount; };
FBox GetExtent() { return mExtent; };
void Update(float deltaTime) { UpdateShaderParameter(); mDeltaTime += deltaTime; };
void UpdateDynamicMaterialForStreaming(UMaterialInstanceDynamic* pointCloudShaderDynInstance) { mDynamicMatInstance = pointCloudShaderDynInstance; };
bool SetInput(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors);
bool SetInput(TArray<FLinearColor> &pointPositions, TArray<FColor> &pointColors);
bool SetInput(TArray<FVector> &pointPositions, TArray<FColor> &pointColors);
void SetExtent(FBox extent) { mExtent = extent; };
void AddSnapshot(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors, FVector offsetTranslation = FVector::ZeroVector, FRotator offsetRotation = FRotator::ZeroRotator);
float mStreamCaptureSteps = 0.5f;
unsigned int mGlobalStreamCounter = 0;
private:
void Initialize(unsigned int pointCount);
void ResetPointData(const int32 &pointsPerAxis);
void CreateTextures(const int32 &pointsPerAxis);
void InitColorBuffer();
void InitPointPosBuffer();
bool UpdateTextureBuffer();
void UpdateShaderParameter();
void SortPointCloudData();
void FreeData();
unsigned int GetUpperPowerOfTwo(unsigned int v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
// General variables
class UMaterialInstanceDynamic* mDynamicMatInstance = nullptr;
unsigned int mPointCount = 0;
FBox mExtent = FBox(FVector::ZeroVector, FVector::ZeroVector);
float mDeltaTime = 10.f;
// CPU buffers
TArray<FLinearColor> mPointPosData;
TArray<FLinearColor>* mPointPosDataPointer = &mPointPosData;
TArray<uint8> mPointColorData;
TArray<uint8>* mPointColorDataPointer = &mPointColorData;
TArray<FVector> mPointScalingData;
// GPU texture buffers
struct FUpdateTextureRegion2D* mUpdateTextureRegion = nullptr;
UTexture2D* mPointPosTexture = nullptr;
UTexture2D* mPointScalingTexture = nullptr;
UTexture2D* mPointColorTexture = nullptr;
// Sorting-related variables
class FComputeShader* mComputeShader = nullptr;
class FPixelShader* mPixelShader = nullptr;
class FPixelShader* mPixelShader2 = nullptr;
class UTextureRenderTarget2D* mComputeShaderRT = nullptr;
class UTextureRenderTarget2D* mComputeShaderRT2 = nullptr;
UTexture* mCastedRT = nullptr;
UTexture* mCastedColorRT = nullptr;
};
using UnrealBuildTool;
public class GPUPointCloudRendererEditor : ModuleRules
{
public GPUPointCloudRendererEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
//Type = ModuleType.Editor;
PublicIncludePaths.Add("GPUPointCloudRendererEditor/Public");
PrivateIncludePaths.Add("GPUPointCloudRendererEditor/Private");
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"PropertyEditor",
"BlueprintGraph",
"GPUPointCloudRenderer",
"ShaderCore",
"RenderCore",
"CustomMeshComponent",
"RHI"
}
);
//if (UEBuildConfiguration.bBuildEditor)
// PublicDependencyModuleNames.Add("UnrealEd");
PrivateDependencyModuleNames.AddRange(
new string[]
{
"EditorStyle",
"AssetRegistry"
}
);
// DynamicallyLoadedModuleNames.AddRange(new string[] { "GPUPointCloudRenderer" });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#include "GPUPointCloudRendererComponent.h"
#include "IGPUPointCloudRenderer.h"
#include "PointCloudStreamingCore.h"
#include "ConstructorHelpers.h"
DEFINE_LOG_CATEGORY(GPUPointCloudRenderer);
#define CHECK_PCR_STATUS \
if (!IGPUPointCloudRenderer::IsAvailable() /*|| !FPointCloudModule::IsAvailable()*/) { \
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Point Cloud Renderer module not loaded!")); \
return; \
} \
if (!mPointCloudCore) { \
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Point Cloud Core component not found!")); \
return; \
}
//const float sqrt3 = FMath::Sqrt(3);
UGPUPointCloudRendererComponent::UGPUPointCloudRendererComponent(const FObjectInitializer& ObjectInitializer)
{
/// Set default values
PrimaryComponentTick.bCanEverTick = true;
//this->GetOwner()->AutoReceiveInput = EAutoReceiveInput::Player0;
ConstructorHelpers::FObjectFinder<UMaterial> MaterialRef(TEXT("Material'/GPUPointCloudRenderer/Streaming/DynPCMat.DynPCMat'"));
mStreamingBaseMat = MaterialRef.Object;
mPointCloudMaterial = UMaterialInstanceDynamic::Create(mStreamingBaseMat, this->GetOwner());
if (mPointCloudCore)
delete mPointCloudCore;
mPointCloudCore = IGPUPointCloudRenderer::Get().CreateStreamingInstance(mPointCloudMaterial);
}
UGPUPointCloudRendererComponent::~UGPUPointCloudRendererComponent() {
if (mPointCloudCore)
delete mPointCloudCore;
}
//////////////////////
// MAIN FUNCTIONS ////
//////////////////////
void UGPUPointCloudRendererComponent::SetDynamicProperties(float cloudScaling, float falloff, float splatSize, float distanceScaling, float distanceFalloff, bool overrideColor) {
mSplatFalloff = falloff;
mCloudScaling = cloudScaling;
mSplatSize = splatSize;
mDistanceScaling = distanceScaling;
mDistanceFalloff = distanceFalloff;
mShouldOverrideColor = overrideColor;
}
void UGPUPointCloudRendererComponent::SetInputAndConvert1(TArray<FLinearColor> &pointPositions, TArray<FColor> &pointColors) {
CHECK_PCR_STATUS
if (pointPositions.Num() != pointColors.Num())
UE_LOG(GPUPointCloudRenderer, Warning, TEXT("The number of point positions doesn't match the number of point colors."));
if (pointPositions.Num() == 0 || pointColors.Num() == 0) {
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Empty point position and/or color data."));
return;
}
CreateStreamingBaseMesh(pointPositions.Num());
mPointCloudCore->SetInput(pointPositions, pointColors);
}
void UGPUPointCloudRendererComponent::AddSnapshot(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors, FVector offsetTranslation, FRotator offsetRotation) {
CHECK_PCR_STATUS
if (pointPositions.Num() * 4 != pointColors.Num())
UE_LOG(GPUPointCloudRenderer, Warning, TEXT("The number of point positions doesn't match the number of point colors."));
if (pointPositions.Num() == 0 || pointColors.Num() == 0) {
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Empty point position and/or color data."));
return;
}
CreateStreamingBaseMesh(MAXTEXRES * MAXTEXRES);
// Since the point is later transformed to the local coordinate system, we have to inverse transform it beforehand
FMatrix objMatrix = this->GetComponentToWorld().ToMatrixWithScale();
offsetTranslation = objMatrix.InverseTransformVector(offsetTranslation);
mPointCloudCore->AddSnapshot(pointPositions, pointColors, offsetTranslation, offsetRotation);
}
void UGPUPointCloudRendererComponent::SetInput(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors) {
CHECK_PCR_STATUS
if (pointPositions.Num()*4 != pointColors.Num())
UE_LOG(GPUPointCloudRenderer, Warning, TEXT("The number of point positions doesn't match the number of point colors."));
if (pointPositions.Num() == 0 || pointColors.Num() == 0) {
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Empty point position and/or color data."));
return;
}
CreateStreamingBaseMesh(pointPositions.Num());
mPointCloudCore->SetInput(pointPositions, pointColors);
}
void UGPUPointCloudRendererComponent::SetInputAndConvert2(TArray<FVector> &pointPositions, TArray<FColor> &pointColors) {
CHECK_PCR_STATUS
if (pointPositions.Num() != pointColors.Num())
UE_LOG(GPUPointCloudRenderer, Warning, TEXT("The number of point positions doesn't match the number of point colors."));
if (pointPositions.Num() == 0 || pointColors.Num() == 0) {
UE_LOG(GPUPointCloudRenderer, Error, TEXT("Empty point position and/or color data."));
return;
}
CreateStreamingBaseMesh(pointPositions.Num());
mPointCloudCore->SetInput(pointPositions, pointColors);
}
void UGPUPointCloudRendererComponent::SetExtent(FBox extent) {
CHECK_PCR_STATUS
mPointCloudCore->SetExtent(extent);
mExtent = extent.ToString();
}
//////////////////////////
// STANDARD FUNCTIONS ////
//////////////////////////
void UGPUPointCloudRendererComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Update core
if (mPointCloudCore) {
mPointCloudCore->Update(DeltaTime);
mPointCount = mPointCloudCore->GetPointCount();
}
// Update shader properties
UpdateShaderProperties();
}
void UGPUPointCloudRendererComponent::BeginPlay() {
Super::BeginPlay();
}
////////////////////////
// HELPER FUNCTIONS ////
////////////////////////
void UGPUPointCloudRendererComponent::CreateStreamingBaseMesh(int32 pointCount)
{
CHECK_PCR_STATUS
//Check if update is neccessary
//if (mBaseMesh && mBaseMesh->NumPoints == pointCount)
// return;
if (pointCount == 0 || !mPointCloudCore)
return;
mBaseMesh = NewObject<UPointCloudMeshComponent>(this, FName("PointCloud Mesh"));
// Create base mesh
TArray<FCustomMeshTriangle> triangles;
BuildTriangleStack(triangles, pointCount);
mBaseMesh->SetCustomMeshTriangles(triangles);
mBaseMesh->RegisterComponent();
mBaseMesh->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
mBaseMesh->SetMaterial(0, mStreamingBaseMat);
mBaseMesh->SetAbsolute(false, true, true); // Disable scaling for the mesh - the scaling vector is transferred via a shader parameter in UpdateShaderProperties()
mBaseMesh->bNeverDistanceCull = true;
//mBaseMesh->SetCustomBounds(mPointCloudCore->GetExtent());
// Update material
mPointCloudMaterial = mBaseMesh->CreateAndSetMaterialInstanceDynamic(0);
mPointCloudCore->UpdateDynamicMaterialForStreaming(mPointCloudMaterial);
}
void UGPUPointCloudRendererComponent::BuildTriangleStack(TArray<FCustomMeshTriangle> &triangles, const int32 &pointCount)
{
triangles.SetNumUninitialized(pointCount);
// construct equilateral triangle with x, y, z as center and normal facing z
float a = 1.0f; // side lenght
float sqrt3 = FMath::Sqrt(3);
float r = sqrt3 / 6 * a; // radius of inscribed circle
//float h_minus_r = a / sqrt3; // from center to tip. height - r
float x = 0;
float y = 0;
for (int i = 0; i < pointCount; i++) {
double z = i / 10.0f;
FCustomMeshTriangle t;
t.Vertex2 = FVector(x - a / 2.f, y - r, z);
t.Vertex1 = FVector(x + a / 2.f, y - r, z);
t.Vertex0 = FVector(x, y + a / sqrt3, z);
triangles[i] = t;
}
}
void UGPUPointCloudRendererComponent::UpdateShaderProperties()
{
if (!mPointCloudMaterial)
return;
auto streamingMeshMatrix = this->GetComponentToWorld().ToMatrixWithScale();
mPointCloudMaterial->SetVectorParameterValue("ObjTransformMatrixXAxis", streamingMeshMatrix.GetUnitAxis(EAxis::X));
mPointCloudMaterial->SetVectorParameterValue("ObjTransformMatrixYAxis", streamingMeshMatrix.GetUnitAxis(EAxis::Y));
mPointCloudMaterial->SetVectorParameterValue("ObjTransformMatrixZAxis", streamingMeshMatrix.GetUnitAxis(EAxis::Z));
mPointCloudMaterial->SetVectorParameterValue("ObjScale", this->GetComponentScale() * mCloudScaling);
mPointCloudMaterial->SetScalarParameterValue("FalloffExpo", mSplatFalloff);
mPointCloudMaterial->SetScalarParameterValue("SplatSize", mSplatSize);
mPointCloudMaterial->SetScalarParameterValue("DistanceScaling", mDistanceScaling);
mPointCloudMaterial->SetScalarParameterValue("DistanceFalloff", mDistanceFalloff);
mPointCloudMaterial->SetScalarParameterValue("ShouldOverrideColor", (int)mShouldOverrideColor);
}
\ No newline at end of file
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#include "CoreMinimal.h"
#include "UObject/Class.h"
#include "PropertyEditorModule.h"
#include "Modules/ModuleManager.h"
#include "GPUPointCloudRendererComponent.h"
#include "IGPUPointCloudRendererEditorPlugin.h"
class FGPUPointCloudRendererEditorPlugin : public IGPUPointCloudRendererEditorPlugin
{
/** IModuleInterface implementation */
virtual void StartupModule() override
{
UE_LOG(GPUPointCloudRenderer, Log, TEXT("//////////////////////////////////////////// \n"));
UE_LOG(GPUPointCloudRenderer, Log, TEXT("// Initializing GPU Point Cloud Renderer... \n"));
UE_LOG(GPUPointCloudRenderer, Log, TEXT("//////////////////////////////////////////// \n"));
#ifdef HAVE_CUDA
UE_LOG(GPUPointCloudRenderer, Log, TEXT("Found CUDA installation. \n"));
#endif
#ifdef WITH_PCL
UE_LOG(GPUPointCloudRenderer, Log, TEXT("Found PCL installation. \n"));
#endif
}
virtual void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(FGPUPointCloudRendererEditorPlugin, GPUPointCloudRendererEditor)
/*************************************************************************************************
* Written by Valentin Kraft <valentin.kraft@online.de>, http://www.valentinkraft.de, 2018
**************************************************************************************************/
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CustomMeshComponent.h"
<