Got it — Canvas Render Texture in UE = typically CanvasRenderTarget2D (UCanvasRenderTarget2D) (or a “canvas” you draw on with UCanvas), not RVT.
To “stamp a circle” at an Actor’s world position you need two pieces:
- A way to convert world position → UV/pixel coordinates on the texture
- Draw a circle into the CanvasRenderTarget2D at that pixel
Below are two solid workflows (Blueprint-first), plus the key math.
- Content Browser → Materials & Textures → Canvas Render Target 2D
- Name it
CRT_Paint
- Set size (e.g. 1024×1024)
Make a material M_CircleStamp:
- Material Domain: Surface (works fine for drawing to canvas)
- Blend Mode: Translucent (or Masked)
- Shading Model: Unlit
- Output only Emissive Color and Opacity
Parameters:
Radius01 (Scalar, 0–0.5 typically)
Feather01 (Scalar, small like 0.01)
Color (Vector)
Node logic (in UV space):
UV = TextureCoordinate
d = length(UV - float2(0.5,0.5))
mask = 1 - smoothstep(Radius01, Radius01 + Feather01, d)
Emissive = Color * mask
Opacity = mask
This material assumes you draw it into a square where the circle is centered.
Create BP_PaintCanvas with:
- Variable
CanvasRT (Canvas Render Target 2D)
- Variable
StampMID (Material Instance Dynamic of M_CircleStamp)
- Variable
WorldBounds (Box or 2 vectors Min/Max) defining what area the texture represents in the world
In BeginPlay:
StampMID = CreateDynamicMaterialInstance(M_CircleStamp)
CanvasRT = Create Canvas Render Target 2D (or reference the asset)
- Bind / use the Canvas RT’s OnCanvasRenderTargetUpdate event (in BP, open the CanvasRenderTarget2D asset and implement Receive Update OR call
UpdateResource after setting variables)
Decide what your paint texture “covers” in the world.
Common approach: define a world rectangle (Min, Max) on X/Y plane.
Let:
Min = (MinX, MinY)
Max = (MaxX, MaxY)
P = ActorLocation
Compute UV:
U = (P.X - MinX) / (MaxX - MinX)
V = (P.Y - MinY) / (MaxY - MinY)
Then pixel:
Px = U * Width
Py = (1 - V) * Height (flip if needed depending on how you interpret Y)
Clamp U,V to [0..1].
In the CanvasRenderTarget2D Receive Update event you get Canvas and Width/Height.
To stamp at (Px, Py):
- Decide stamp size in pixels, e.g.
StampSizePx
- Compute top-left:
X = Px - StampSizePx/2
Y = Py - StampSizePx/2
Then call Draw Material:
Canvas->K2_DrawMaterial(StampMID, X, Y, StampSizePx, StampSizePx, 0,0,1,1)
Before drawing, set params on StampMID:
Radius01 (like 0.5 for “fill the quad” circle, or smaller)
Feather01
Color
Finally, when you want to add a stamp:
- Store the Actor position (or a list of stamps)
- Call
CanvasRT->UpdateResource() (Blueprint node: Update Resource)
Because Receive Update redraws the whole texture each time, you usually:
- Keep an array of stamp data (UV + size + color)
- In Receive Update, loop and draw each stamp material call
- (Optional) first clear with
K2_DrawBox or draw a “clear” material
If you want “true accumulation” without redrawing history every update, Canvas RT alone won’t magically preserve unless you do a ping-pong (see section C).
If you only need occasional stamps and can afford redrawing:
- Maintain an array of stamp positions
- Redraw all stamps each update
This is the simplest and most reliable in Blueprint.
CanvasRenderTarget2D update is “paint the frame” style. To accumulate without replaying all stamps:
- Have two render targets:
RT_A, RT_B
- On each stamp:
- Draw previous RT (A) onto canvas first (full-screen)
- Draw your new circle on top
- Output into RT_B
- Swap A/B
You do the “draw previous texture” step by using a material that samples RT_A and outputs it, then draw that material full-screen on the canvas before drawing the new stamp.
If your “canvas” is literally a mesh (like a plane you paint on), you can avoid bounds math by doing a trace:
- LineTrace to the canvas mesh
- Use Find Collision UV on the hit result (requires “Support UV From Hit Results” project setting + mesh collision settings)
- UV → pixel → stamp
That’s often the nicest workflow if the paintable surface isn’t a simple axis-aligned world rectangle.
- CanvasRT ReceiveUpdate:
- (Optionally) draw previous RT (for persistence)
- For each stamp:
- world → UV (bounds or collision UV)
- UV → pixel
K2_DrawMaterial(StampMID, X, Y, Size, Size, …)
If you tell me which mapping you’re using:
- (1) fixed world rectangle (min/max), or
- (2) paint on a specific mesh via trace + collision UV,
…I can give you the exact Blueprint node chain (with variable names) for that case.