Last active
November 10, 2024 02:24
-
-
Save iUltimateLP/baca7aee4b28585b5fd2d0d46b541d95 to your computer and use it in GitHub Desktop.
Implements dynamic textures into Unreal Engine 4, which can be dynamically written at runtime using the fastest way possible: by directly manipulating the pixel buffer of the texture.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DynamicTexture | |
#include "DynamicTexture.h" | |
// UTextures have a BPP of 4 (Red, Green, Blue, Alpha) | |
#define DYNAMIC_TEXTURE_BYTES_PER_PIXEL 4 | |
void UDynamicTexture::Initialize(int32 InWidth, int32 InHeight, FLinearColor InClearColor, TextureFilter FilterMethod/* = TextureFilter::TF_Nearest*/) | |
{ | |
// Store the parameters | |
TextureWidth = InWidth; | |
TextureHeight = InHeight; | |
ClearColor = InClearColor; | |
// Create the UTexture2D to render to | |
Texture = UTexture2D::CreateTransient(TextureWidth, TextureHeight); | |
Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; | |
Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; // The VectorDisplacementMap is a raw RGBA8 format | |
Texture->SRGB = 1; | |
Texture->Filter = FilterMethod; | |
Texture->UpdateResource(); | |
// Create the proxy object for updating the texture region | |
UpdateTextureRegionProxy = MakeUnique<FUpdateTextureRegion2D>(0, 0, 0, 0, TextureWidth, TextureHeight); | |
// Size of the image pixel buffer | |
SIZE_T BufferSize = TextureWidth * TextureHeight * DYNAMIC_TEXTURE_BYTES_PER_PIXEL; | |
PixelBuffer = MakeUnique<uint8[]>(BufferSize); | |
// Initially clear the texture | |
Clear(); | |
} | |
void UDynamicTexture::SetPixel(int32 X, int32 Y, FLinearColor Color) | |
{ | |
// Get the pointer of the specified pixel | |
uint8* Ptr = GetPointerToPixel(X, Y); | |
// Set the pixel (note that linear color uses floats between 0..1, but a uint8 ranges from 0..255) | |
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255); | |
} | |
void UDynamicTexture::Fill(FLinearColor Color) | |
{ | |
// Get the base pointer of the pixel buffer | |
uint8* Ptr = PixelBuffer.Get(); | |
// Iterate over all pixels | |
for (int i = 0; i < TextureWidth * TextureHeight; ++i) | |
{ | |
// Set the pixel | |
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255); | |
// Advance to the next pixel | |
Ptr += DYNAMIC_TEXTURE_BYTES_PER_PIXEL; | |
} | |
} | |
void UDynamicTexture::FillRect(int32 X, int32 Y, int32 Width, int32 Height, FLinearColor Color) | |
{ | |
// Will hold the current pixel | |
uint8* Ptr = NULL; | |
// Loop over the Y and X region | |
for (int y = Y; y < Y + Height; y++) | |
{ | |
for (int x = X; x < X + Width; x++) | |
{ | |
// Get the current pixel pointer | |
Ptr = GetPointerToPixel(x, y); | |
// Set the pixel | |
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255); | |
} | |
} | |
} | |
void UDynamicTexture::DrawLine(int32 X1, int32 Y1, int32 X2, int32 Y2, FLinearColor Color) | |
{ | |
// Bresenham's line algorithm taken from here: http://members.chello.at/~easyfilter/bresenham.html | |
int X = X1; | |
int Y = Y1; | |
int dx = abs(X2 - X1), sx = X1 < X2 ? 1 : -1; | |
int dy = -abs(Y2 - Y1), sy = Y1 < Y2 ? 1 : -1; | |
int err = dx + dy, e2; // error value e_xy | |
for (;;) | |
{ | |
SetPixel(X, Y, Color); | |
if (X == X2 && Y == Y2) break; | |
e2 = 2 * err; | |
if (e2 >= dy) { err += dy; X += sx; } // e_xy+e_x > 0 | |
if (e2 <= dx) { err += dx; Y += sy; } // e_xy+e_y < 0 | |
} | |
} | |
void UDynamicTexture::Clear() | |
{ | |
// Fill with the clear color | |
Fill(ClearColor); | |
} | |
UTexture2D* UDynamicTexture::GetTextureResource() | |
{ | |
return Texture; | |
} | |
void UDynamicTexture::SetPixelInternal(uint8*& Ptr, uint8 Red, uint8 Green, uint8 Blue, uint8 Alpha) | |
{ | |
// Pixels are stored in BGRA format | |
*Ptr = Blue; | |
*(Ptr + 1) = Green; | |
*(Ptr + 2) = Red; | |
*(Ptr + 3) = Alpha; | |
} | |
uint8* UDynamicTexture::GetPointerToPixel(int32 X, int32 Y) | |
{ | |
// The calculation of the pointer address of a given pixel is | |
// base + ((x + (y * width)) * bpp) | |
return (PixelBuffer.Get() + ((X + (Y * TextureWidth)) * DYNAMIC_TEXTURE_BYTES_PER_PIXEL)); | |
} | |
void UDynamicTexture::UpdateTexture() | |
{ | |
// Make sure the proxy and the texture is valid | |
if (UpdateTextureRegionProxy.IsValid() && Texture) | |
{ | |
// Update the texture's regions | |
Texture->UpdateTextureRegions( | |
0, // Mip index | |
1, // Number of regions | |
UpdateTextureRegionProxy.Get(), // Region proxy | |
TextureWidth * DYNAMIC_TEXTURE_BYTES_PER_PIXEL, // Source data pitch | |
DYNAMIC_TEXTURE_BYTES_PER_PIXEL, // Bytes per pixel of source data | |
PixelBuffer.Get() // Buffer of pixels to set | |
); | |
} | |
} | |
int32 UDynamicTexture::GetWidth() | |
{ | |
return TextureWidth; | |
} | |
int32 UDynamicTexture::GetHeight() | |
{ | |
return TextureHeight; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DynamicTexture | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "UObject/NoExportTypes.h" | |
#include "RHI.h" | |
#include "DynamicTexture.generated.h" | |
/* | |
This implements a fast dynamic texture without the use of the | |
UE4 Slate canvas features, which are way to slow for real-time | |
use. Therefore, this writes into the texture's memory directly. | |
*/ | |
UCLASS() | |
class UDynamicTexture : public UObject | |
{ | |
GENERATED_BODY() | |
public: | |
// Initializes the dynamic texture with given dimensions | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void Initialize(int32 InWidth, int32 InHeight, FLinearColor InClearColor, TextureFilter FilterMethod = TextureFilter::TF_Nearest); | |
// Sets a specified pixel to a color | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void SetPixel(int32 X, int32 Y, FLinearColor Color); | |
// Fills the texture with a given color | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void Fill(FLinearColor Color); | |
// Fills a rectangle area of the texture with a given color | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void FillRect(int32 X, int32 Y, int32 Width, int32 Height, FLinearColor Color); | |
// Draws a line between two points | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void DrawLine(int32 X1, int32 Y1, int32 X2, int32 Y2, FLinearColor Color); | |
// Clears the canvas (same as filling with the clear color) | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void Clear(); | |
// Returns the UTexture resource which is used as a canvas | |
UFUNCTION(BlueprintPure, Category = "Dynamic Texture") | |
UTexture2D* GetTextureResource(); | |
// Needs to be called at the end of each drawing operation to update the texture | |
// You can also call this at the end of multiple drawing operations, so the UTexture | |
// does not get updated more than needed. | |
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture") | |
void UpdateTexture(); | |
// Returns the width of this texture | |
UFUNCTION(BlueprintPure, Category = "Dynamic Texture") | |
int32 GetWidth(); | |
// Returns the height of this texture | |
UFUNCTION(BlueprintPure, Category = "Dynamic Texture") | |
int32 GetHeight(); | |
private: | |
// Internal function to set a pixel in the image | |
void SetPixelInternal(uint8*& Ptr, uint8 Red, uint8 Green, uint8 Blue, uint8 Alpha); | |
// Internal function to return the pointer pointing to the specified pixel | |
uint8* GetPointerToPixel(int32 X, int32 Y); | |
private: | |
// Reference to the UTexture2D* were drawing to | |
UPROPERTY() | |
UTexture2D* Texture; | |
// The Dimensions of the image we're drawing | |
int32 TextureWidth; | |
int32 TextureHeight; | |
// The clear color of the canvas | |
FLinearColor ClearColor; | |
// Unique pointer to the raw pixel data of the texture | |
TUniquePtr<uint8[]> PixelBuffer; | |
// Unique pointer to the proxy used to update the texture region (not really using regions, | |
// it's one "big" region) | |
TUniquePtr<FUpdateTextureRegion2D> UpdateTextureRegionProxy; | |
}; |
Thanks!
You just saved me a few hours (plus debug time!) implementing this exact class.
I appreciate it!
fastest way possible ? calling a function for each pixel draw call
meh
if i want to use PF_FloatRGBA Pixelformat, how can I modify ur code?
just GPixelFormats[PF_FloatRGBA ].BlockBytes @PerhapsThat
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi!
Thanks a lot for this gist!
FYI, I just copy pasted it into a UE5 project and it was complaining about missing
#include "Engine/Texture.h"
as a dependency for theTextureFilter
reference :)