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
**************************************************************************************************/
#include "CoreMinimal.h"
#include "PointCloudStreamingCore.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "App.h"
#include "Runtime/Engine/Classes/Materials/MaterialInstanceDynamic.h"
//#include "ComputeShaderUsageExample.h"
//#include "PixelShaderUsageExample.h"
using namespace std;
#undef UpdateResource
DECLARE_CYCLE_STAT(TEXT("Update Texture Regions"), STAT_UpdateTextureRegions, STATGROUP_GPUPCR);
DECLARE_CYCLE_STAT(TEXT("Sort Point Cloud Data"), STAT_SortPointCloudData, STATGROUP_GPUPCR);
DECLARE_CYCLE_STAT(TEXT("Update Shader Textures"), STAT_UpdateShaderTextures, STATGROUP_GPUPCR);
//////////////////////
// MAIN FUNCTIONS ////
//////////////////////
void FPointCloudStreamingCore::AddSnapshot(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors, FVector offsetTranslation, FRotator offsetRotation) {
check(pointPositions.Num() * 4 == pointColors.Num());
if (mGlobalStreamCounter + pointPositions.Num() >= MAXTEXRES * MAXTEXRES)
return;
if (mDeltaTime < mStreamCaptureSteps)
return;
Initialize(MAXTEXRES * MAXTEXRES);
InitPointPosBuffer();
InitColorBuffer();
FVector tempPos;
for (int i = 0; i < pointPositions.Num(); ++i) {
// Transform point
tempPos = FVector(pointPositions[i].G, pointPositions[i].B, pointPositions[i].R);
tempPos = offsetRotation.RotateVector(tempPos);
tempPos += offsetTranslation;
// Add current data to buffer
if (mPointPosData.IsValidIndex(i)) {
mPointPosData[mGlobalStreamCounter + i].A = tempPos.Z;
mPointPosData[mGlobalStreamCounter + i].G = tempPos.X;
mPointPosData[mGlobalStreamCounter + i].B = tempPos.Y;
mPointPosData[mGlobalStreamCounter + i].R = tempPos.Z;
}
if (mPointColorData.IsValidIndex(mGlobalStreamCounter + i)) {
mPointColorData[(mGlobalStreamCounter + i) * 4] = pointColors[i * 4];
mPointColorData[(mGlobalStreamCounter + i) * 4 + 1] = pointColors[i * 4 + 1];
mPointColorData[(mGlobalStreamCounter + i) * 4 + 2] = pointColors[i * 4 + 2];
mPointColorData[(mGlobalStreamCounter + i) * 4 + 3] = pointColors[i * 4 + 3];
}
}
mGlobalStreamCounter += pointPositions.Num();
mPointCount = mGlobalStreamCounter;
UpdateTextureBuffer();
mDeltaTime = 0.f;
}
bool FPointCloudStreamingCore::SetInput(TArray<FLinearColor> &pointPositions, TArray<uint8> &pointColors) {
check(pointPositions.Num() * 4 == pointColors.Num());
if (pointColors.Num() < pointPositions.Num() * 4)
pointColors.SetNumZeroed(pointPositions.Num() * 4);
Initialize(pointPositions.Num());
mPointPosDataPointer = &pointPositions;
mPointColorDataPointer = &pointColors;
// Resize arrays with zero values if neccessary
if (pointPositions.Num() < (int32)mPointCount)
pointPositions.SetNumZeroed(mPointCount);
if (pointColors.Num() < (int32)mPointCount*4)
pointColors.SetNumZeroed(mPointCount*4);
return UpdateTextureBuffer();
}
bool FPointCloudStreamingCore::SetInput(TArray<FLinearColor> &pointPositions, TArray<FColor> &pointColors) {
ensure(pointPositions.Num() == pointColors.Num());
Initialize(pointPositions.Num());
InitColorBuffer();
for (int i = 0; i < pointColors.Num(); ++i) {
mPointColorData[i * 4] = pointColors[i].R;
mPointColorData[i * 4 + 1] = pointColors[i].G;
mPointColorData[i * 4 + 2] = pointColors[i].B;
mPointColorData[i * 4 + 3] = pointColors[i].A;
}
mPointPosDataPointer = &pointPositions;
// Resize arrays with zero values if neccessary
if (pointPositions.Num() < (int32)mPointCount)
pointPositions.SetNumZeroed(mPointCount);
return UpdateTextureBuffer();
}
bool FPointCloudStreamingCore::SetInput(TArray<FVector> &pointPositions, TArray<FColor> &pointColors) {
ensure(pointPositions.Num() == pointColors.Num());
Initialize(pointPositions.Num());
InitPointPosBuffer();
InitColorBuffer();
for (int i = 0; i < pointPositions.Num(); ++i) {
mPointPosData[i].A = pointPositions[i].Z; //#ToDo: Improve (bottleneck!?)
mPointPosData[i].G = pointPositions[i].X;
mPointPosData[i].B = pointPositions[i].Y;
mPointPosData[i].R = pointPositions[i].Z;
}
for (int i = 0; i < pointColors.Num(); ++i) {
mPointColorData[i * 4] = pointColors[i].R;
mPointColorData[i * 4 + 1] = pointColors[i].G;
mPointColorData[i * 4 + 2] = pointColors[i].B;
mPointColorData[i * 4 + 3] = pointColors[i].A;
}
return UpdateTextureBuffer();
}
void FPointCloudStreamingCore::InitColorBuffer()
{
if (mPointColorData.Num() != mPointCount * 4) {
mPointColorData.Empty();
mPointColorData.AddUninitialized(mPointCount * 4); // 4 as we have bgra
mPointColorDataPointer = &mPointColorData;
}
}
void FPointCloudStreamingCore::InitPointPosBuffer()
{
if (mPointPosData.Num() != mPointCount) {
mPointPosData.Empty();
mPointPosData.AddUninitialized(mPointCount);
mPointPosDataPointer = &mPointPosData;
}
}
void FPointCloudStreamingCore::SortPointCloudData() {
SCOPE_CYCLE_COUNTER(STAT_SortPointCloudData);
}
void FPointCloudStreamingCore::Initialize(unsigned int pointCount)
{
if (pointCount == 0)
return;
int32 pointsPerAxis = FMath::CeilToInt(FMath::Sqrt(pointCount));
// Ensure even-sized, power-of-two textures to avoid inaccuracies
if (pointsPerAxis % 2 == 1) pointsPerAxis++;
pointsPerAxis = GetUpperPowerOfTwo(pointsPerAxis);
// Check if update is neccessary
if (mPointPosTexture && mPointColorTexture && mPointScalingTexture && mUpdateTextureRegion)
if (mPointPosTexture->GetSizeX() == pointsPerAxis && mPointColorTexture->GetSizeX() == pointsPerAxis && mPointScalingTexture->GetSizeX() == pointsPerAxis)
return;
mPointCount = pointsPerAxis * pointsPerAxis;
ResetPointData(pointsPerAxis);
CreateTextures(pointsPerAxis);
mGlobalStreamCounter = 0;
}
void FPointCloudStreamingCore::ResetPointData(const int32 &pointsPerAxis)
{
mPointPosData.Empty();
mPointPosData.AddUninitialized(mPointCount);
mPointPosDataPointer = &mPointPosData;
mPointColorData.Empty();
mPointColorData.AddUninitialized(mPointCount * 4);
mPointColorDataPointer = &mPointColorData;
mPointScalingData.Empty();
mPointScalingData.Init(FVector::OneVector, mPointCount);
if (mUpdateTextureRegion) delete mUpdateTextureRegion; mUpdateTextureRegion = nullptr;
mUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, pointsPerAxis, pointsPerAxis);
}
void FPointCloudStreamingCore::CreateTextures(const int32 &pointsPerAxis)
{
// create point cloud positions texture
mPointPosTexture = UTexture2D::CreateTransient(pointsPerAxis, pointsPerAxis, EPixelFormat::PF_A32B32G32R32F);
mPointPosTexture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
mPointPosTexture->SRGB = 0;
mPointPosTexture->AddToRoot();
mPointPosTexture->UpdateResource();
#if WITH_EDITOR
mPointPosTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
#endif
// create point cloud scalings texture
mPointScalingTexture = UTexture2D::CreateTransient(pointsPerAxis, pointsPerAxis, EPixelFormat::PF_A32B32G32R32F);
mPointScalingTexture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
mPointScalingTexture->SRGB = 0;
mPointScalingTexture->AddToRoot();
mPointScalingTexture->UpdateResource();
#if WITH_EDITOR
mPointScalingTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
#endif
// create color texture
mPointColorTexture = UTexture2D::CreateTransient(pointsPerAxis, pointsPerAxis, EPixelFormat::PF_B8G8R8A8);
mPointColorTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
mPointColorTexture->SRGB = 1;
mPointColorTexture->AddToRoot();
mPointColorTexture->UpdateResource();
#if WITH_EDITOR
mPointColorTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
#endif
mPointPosData.Empty();
mPointPosData.AddUninitialized(mPointCount);
if (!mPointPosDataPointer)
mPointPosDataPointer = &mPointPosData;
if (!mPointColorDataPointer)
mPointColorDataPointer = &mPointColorData;
mPointPosTexture->WaitForStreaming();
mPointColorTexture->WaitForStreaming();
mPointScalingTexture->WaitForStreaming();
mPointScalingData.Empty();
mPointScalingData.Init(FVector::OneVector, mPointCount);
if (mUpdateTextureRegion) delete mUpdateTextureRegion; mUpdateTextureRegion = nullptr;
mUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, pointsPerAxis, pointsPerAxis);
mGlobalStreamCounter = 0;
}
bool FPointCloudStreamingCore::UpdateTextureBuffer()
{
SCOPE_CYCLE_COUNTER(STAT_UpdateTextureRegions);
if (!mPointColorDataPointer || !mPointPosDataPointer || !mPointPosTexture || !mPointColorTexture || !mPointScalingTexture)
return false;
if (mPointPosDataPointer == nullptr || mPointColorDataPointer == nullptr)
return false;
if (mPointColorDataPointer->Num() == 0 || mPointPosDataPointer->Num() == 0)
return false;
if (mPointColorDataPointer->Num() > mPointColorTexture->GetSizeX() * mPointColorTexture->GetSizeY() * 4 || mPointPosDataPointer->Num() > mPointPosTexture->GetSizeX()*mPointPosTexture->GetSizeY())
return false;
if (mUpdateTextureRegion == nullptr)
return false;
mPointPosTexture->WaitForStreaming();
mPointColorTexture->WaitForStreaming();
//mPointScalingTexture->WaitForStreaming();
mPointPosTexture->UpdateTextureRegions(0, 1, mUpdateTextureRegion, mPointPosTexture->GetSizeX() * sizeof(FLinearColor), sizeof(FLinearColor), (uint8*)mPointPosDataPointer->GetData());
mPointColorTexture->UpdateTextureRegions(0, 1, mUpdateTextureRegion, mPointColorTexture->GetSizeX() * sizeof(uint8) * 4, 4, mPointColorDataPointer->GetData());
//if(mHasSurfaceReconstructed)
// mPointScalingTexture->UpdateTextureRegions(0, 1, mUpdateTextureRegion, mPointPosTexture->GetSizeX() * sizeof(FVector), sizeof(FVector), (uint8*)mPointScalingData.GetData());
mPointPosTexture->WaitForStreaming();
mPointColorTexture->WaitForStreaming();
return true;
}
void FPointCloudStreamingCore::UpdateShaderParameter()
{
SCOPE_CYCLE_COUNTER(STAT_UpdateShaderTextures);
if (!mPointPosTexture || !mPointColorTexture || !mPointScalingTexture)
return;
if (!mDynamicMatInstance)
return;
mDynamicMatInstance->SetTextureParameterValue("PositionTexture", mPointPosTexture);
mDynamicMatInstance->SetTextureParameterValue("ColorTexture", mPointColorTexture);
//if (mHasSurfaceReconstructed)
// mDynamicMatInstance->SetTextureParameterValue("ScalingTexture", mPointScalingTexture);
mDynamicMatInstance->SetScalarParameterValue("TextureSize", (float)mPointPosTexture->GetSizeX());
mDynamicMatInstance->SetVectorParameterValue("minExtent", mExtent.Min);
mDynamicMatInstance->SetVectorParameterValue("maxExtent", mExtent.Max);
}
void FPointCloudStreamingCore::FreeData()
{
mGlobalStreamCounter = 0;
mPointPosData.Empty();
mPointPosDataPointer = nullptr;
mPointColorData.Empty();
mPointColorDataPointer = nullptr;
mPointScalingData.Empty();
if (mUpdateTextureRegion) delete mUpdateTextureRegion; mUpdateTextureRegion = nullptr;
}
FPointCloudStreamingCore::~FPointCloudStreamingCore() {
FreeData();
//if (mPointPosTexture)
// delete mPointPosTexture;
//if (mPointScalingTexture)
// delete mPointScalingTexture;
//if (mColorTexture)
// delete mColorTexture;
}
\ No newline at end of file
/*************************************************************************************************
* 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)
{