Introduction

 «2D/3D Paint» - a universal solution for Unity that allows to paint on 2D and 3D objects! You can also create a modern paint app with incredible features and outstanding performance!
 You need to add a prefab, one component, and configure a few parameters to use an asset! With «2D/3D Paint» you will be able to paint on 2D and 3D components such as MeshRenderer, SkinnedMeshRenderer, SpriteRenderer and RawImage.
 Asset supports Standard, URP, and HDRP pipelines, works with Input Manager (Old) and Input System Package (New). It also works with custom shaders and with VR. No need to add any colliders for painting.

Requirements

 For the correct work, «2D/3D Paint» requires:

  • Unity 2019.4 or newer;
  • GameObject with supported component: MeshRenderer, SkinnedMeshRenderer, SpriteRenderer or RawImage with material;
  • If you are using the MeshRenderer or SkinnedMeshRenderer component, make sure that their model has a UV map.
     Unity can do it for you if your model doesn't have a UV map. To generate a UV-map, enable Generate Lightmap UVs in the model settings. Generated UV map will be stored in the second UV channel. Ensure that your shader works with it, as most default Unity shaders only support the first UV channel. Your UV data shouldn't have overlapping areas (no mirroring) to get the best results. I recommend creating a UV map in a 3D modeling program to get better results.
  • How do I upgrade?

     First, back up your project.
    Next, update the asset to the latest from Asset Store using Package Manager (Windows -> Package Manager).
    The asset has a comparison list of APIs that were changed from the last release. To check it, open the v.3.0_API_Changes.pdf file.
    If you have any issues or errors, contact me. I'll be happy to help!

    Quick Start

     Add a prefab to the scene from path Assets/XDPaint/Prefabs/[XDPaintContainer].prefab. This prefab contains singleton components, let’s look at the parameters of the PaintController component: PaintController

    • Override Camera - set the camera for the InputController and RaycastController objects, singleton objects. If unchecked, Camera.main will be used;
    • Use Shared Settings - whether to use settings of PaintController (Brush, Paint Tool, and Paint Mode) for all PaintManager instances;
    • Paint Tool - selected tool. Supported tools:
      • Brush - brush tool for painting on the texture;
      • Erase - erase tool;
      • Bucket - tool for filling texture areas with the selected color;
      • Eyedropper - eyedropper tool for picking the color of the brush;
      • BrushSampler - tool for sampling brush texture;
      • Clone - tool for cloning parts of the texture;
      • Blur - tool for blurring parts of the texture;
      • GaussianBlur - tool for blurring parts of the texture;
      • Grayscale - tool for discolorating parts of the texture.
    • Paint Mode - mode of painting can be Default or Additive. Default mode bakes draw results into Layer Texture each frame, Additive mode provides more accurate color and alpha blending, renders lines into Input Texture and bakes draw results in Layer Texture on Mouse Up event;
    • Preset - brush preset saved in Assets/XDPaint/Resources/XDPaintBrushPresets.asset. It stores brushes with different parameters to easily switch between them. See Brush and Presets paragraph for details.
     Brush Parameters:
    • Name - name of the brush, must be unique;
    • Source Texture - brush texture. When you add a new brush, make sure that «Wrap Mode» is set as «Clamp» in the settings of the brush texture;
    • Filter Mode - FilterMode for brush RenderTexture;
    • Color - brush color;
    • Size - brush size;
    • Hardness - brush hardness. Rounds brush when a value is less than 1;
    • Render Angle - brush render angle in degrees;
    • Preview - to show a preview of the brush while the user hovers over the object to be painted;
     After that, create the GameObject with the PaintManager component using the Unity menu «GameObject -> 2D/3D Paint», or add the PaintManager component to your GameObject.
    Then select the GameObject to paint in the Object For Painting field. The object to paint must contain one of the supported components:
  • MeshRenderer
  • SkinnedMeshRenderer
  • SpriteRenderer
  • RawImage
  •  If any child of PaintManager GameObject contains an object with one of the supported components, it can automatically assign it to the Object For Painting field by clicking on the Auto Fill button:
    Auto Fill Button
     After clicking on the Auto Fill button, the Object For Painting field will be filled in when one of the supported components is found:
    PaintManager component
     If the material or paint object cannot be found automatically, make sure that there is a GameObject with the supported component among the child objects, otherwise, Object For Painting and Material can be assigned manually.
     Other component settings will appear if Object For Painting and Material have values. Consider the existing settings:
    • Object For Painting - GameObject for painting, must contain one of the supported components;
    • Material - the material of the object whose texture field (Shader Texture Name) will be used for drawing;
    • Shader Texture Name - the name of the material texture on which it will perform the painting;
    • Texture Width - the default texture width is used for objects that do not have the source texture;
    • Texture Height - the default texture height is used for objects that do not have the source texture;
    • Paint Mode - mode of painting: default or additive;
    • Filter Mode - FilterMode for painting RenderTextures;
    • Override Camera - asset overrides the camera to determine the intersection of the ray with triangles and to work with user input. If the flag is set to false, the camera is obtained from the Camera.main;
    • Camera - camera for determining ray intersections with the triangles and for working with user input;
    • Copy Source Texture To Layer - copy source texture from material to new layer;
    • Use Source Texture as Background Layer - copy source texture from material to background layer;
    • Use Neighbors Vertices For Raycasts - asset uses neighboring vertices to find the intersection of rays with triangles while lines are drawing. If the flag is unchecked, the results of the calculations can be inaccurate with non-convex objects. When we are using objects with many vertices, the performance of searching the intersection of rays with triangles is degrading. I recommend setting the flag as true. After setting a flag a window opens with confirmation:
    Triangles Data
    After pressing Fill triangles data progress bar shows while filling in the progress. Wait until it finishes searching all neighboring triangles, then the window will be closed, the drawing lines will work properly. You can save generated triangles data to ScriptableObject using Save Triangles Data to Asset button;
    • Triangles Container - link to TrianglesContainer ScriptableObject, may be empty - in that case triangles data will be stored in component field;
    • Paint Tool - painting tool, if PaintController has checked Use Shared Settings flag, the tool from PaintController will be used as Paint Tool for all PaintManagers;
    • Brush - brush parameters. When selected «Custom» - PaintManager has unique brush parameters. You can select a ready-to-use preset of the brush from the list. Note that if PaintController has the flag Use Shared Settings, brush parameters will be used from PaintController;
    • Layers Container - a reference to the ScriptableObject of LayersContainer, that contains information about layers, their textures, and parameters. Used for loading previously saved layers.
    • Clone Material - button to copy the source material of the object to the new file, can be used only in the Unity Editor;
    • Clone Texture - button to copy the source texture of the object to the new file, can be used only in the Unity Editor;
    • Undo - button to undo action with the object;
    • Redo - button to redo action with the object;
    • Bake - button to save the painting results to the source file. Note that texture is stored in RAM and is not written on the disk;
    • Save Texture - button to save the texture of the painting results to the file, can only be used in the Unity Editor;
    • Save Layers - button to save layers data to ScriptableObject.
    Let's set up the object to paint on the emission texture: Set _EmissionMap texture for painting
     The texture of shader with the name «_EmissionMap» is selected. After switching to the game mode, the user will be able to paint on the object «LightHouse». The material and the source texture «_EmissionMap» will be cloned, RenderTexture will be assigned as the new texture. Users can use input devices for painting: a computer mouse, touch device, pen (tablet stylus), or VR controller.

    Settings

     «2D/3D Paint» has global settings. The configuration file is located at: Assets/XDPaint/Resources/XDPaintSettings.asset. The settings file is ScriptableObject.
    Consider the fields in the settings file: Settings
    • Default Brush - default brush texture;
    • Default Circle Brush - default brush texture for BrushSampler/Clone tools;
    • Default Pattern Texture - default pattern texture for Brush/Bucket tools;
    • Is VR Mode - whether to use VR input;
    • Pressure Enabled - pressure force is applied to the brush size;
    • Check Canvas Raycasts - prevent painting on components if any other canvas component lies above the paint object. This also requires setting InputController settings: Canvas and Ignore For Raycasts (optional) fields;
    • Use Jobs For Raycasts - whether to use Job System for raycasts calculations. Can increase painting performance for mesh components (MeshRenderer and SkinnedMeshRenderer) and requires Burst package that can be installed using Package Manager (Windows -> Package Manager).
    • Brush Duplicate Part Width - the width of the duplicated part of the brush, the value affects the number of vertices and smoothness of the line while lines are drawing. The higher the value, the less the number of vertices will be drawn;
    • Pixel Per Unit - pixelPerUnit field for sprites. Used for objects that do not have the source sprite;
    • Container Game Object Name - the name of the GameObject, for the container object with InputController and RaycastController components;

    States Settings

     «2D/3D Paint» has undo/redo system and its setting which is called «States Settings». The configuration file is located at: Assets/XDPaint/Resources/XDPaintStatesSettings.asset. The settings file is ScriptableObject.
    Consider the fields in the settings file: States settings
    • Undo Redo Enabled - undo and redo functionality is enabled. If the application doesn't have undo/redo functionality, it is recommended to turn this flag off to reduce memory usage;
    • Enable Undo Redo For Properties And Actions - save undo/redo actions to change layer parameters such as opacity, blending mode, name, layer index, etc. If this flag is disabled, only layers RenderTextures will be saved on actions performed;
    • Undo Redo Max Actions Count - the maximum amount of actions that store undo/redo;

    How it works

    General description

     «2D/3D Paint» clones the source material and replaces the source texture with the RenderTexture. The asset passes input data from InputController to BasePaintObject to calculate UV coordinates to determine the position of the painting on the texture. When the user interacts with the input, for example, by moving a finger on the touch device, the asset gets the UV position of the previous frame and position of the UV in the current frame. Using two UV-positions, the asset draws brush N-times on the layer texture. The painting takes place on the previously created RenderTexture (Layer) and is stored in GPU memory, which provides high performance.

    Paint Modes

     The asset has two paint modes: Default and Additive. Default mode bakes draw results into Paint Texture each frame, Additive mode provides more accurate color and alpha blending, bakes draw results in Input Texture and bakes it into Layer Texture on Mouse Up event.

    DefaultMode AdditiveMode

    Painting

     «2D/3D Paint» creates a RenderTexture (ARGB32 format) for each Layer. The size of the RenderTexture is equal to the size of the source texture. If there is no source texture, the size for RenderTexture will be taken from the PaintManager.Material settings (DefaultTextureWidth and DefaultTextureHeight fields). For objects such as MeshRenderer and SkinnedMeshRenderer, the asset uses ray-surface intersection to determine the intersection of the model triangle with the ray. The use of models with many vertices can lead to performance loss. Finding and using neighboring triangles for raycasts was implemented to solve this issue.
     «2D/3D Paint» creates RenderTexture for each PaintManager:

    • Active Layer Texture - texture of the layer, as there must be at least one layer for painting;
    • Active Layer Temp Texture (only in Additive Paint Mode) - temp texture of the current layer, used for painting;
    • Input Texture - store user drawing results for the current frame (in case the user uses Default Paint Mode) or drawing results from mouse down event to mouse up event (in case the user uses Additive Paint Mode);
    • Combined Texture - textures that contain combined active layers;
    • Combined Temp Texture - temp texture containing combined active layers (used with two or more Layers or brush preview enabled).

    Active Layer Texture and Active Layer Temp Texture are used for painting, Input Texture blends into Active Layer Texture / Active Layer Temp Texture.
    Combined Texture / Combined Temp Texture - represents the current drawing results and preview of the brush.
    Depending on the selected tool, the count of RenderTextures may vary.

    Brush

     A brush is a texture that renders into Active Layer Texture using brush parameters. When a user changes brush parameters like Texture, Opacity, Color, or Hardness, the brush invokes the Render method that renders the brush into RenderTexture.
     Brush size value means that brush source texture will be scaled by that value. If you have a brush texture of 100x100px and brush size 0.5, your brush texture will be 50x50px.
     Displaying brush size on an image depends on brush texture size, brush size value and source image size. When a user draws a dot with the brush of 100x100px with a brush scale of 1 on a layer of 1000x1000px, it will fit 10% of the layer width/height.

    Undo/Redo

     «2D/3D Paint» has undoing/redoing functionality that stores layers data changes. It can store layer texture changes (save every OnMouseUp event) and layer parameters changes such as name, index, opacity, etc (optional). Note that after layer texture change (paintning on the layer), its state will be saved as texture and consume memory. For example, using a 1024x1024 texture will take 4MB (32 bytes per pixel). Undo/redo functionality can be turned off or limited using States Settings.

    Input

     «2D/3D Paint» processes input using the InputController class. Asset supports Input Manager (Old) and Input System Package (New). Input can work with the mouse, touch device, pen (tablet stylus like Apple Pen) or VR controller.

    Raycast

     For MeshRenderer and SkinnedMeshRenderer objects, finding the intersection of the ray with the triangle is used to calculate the UV coordinates. These calculations are performed on the CPU, the time of which depends on the number of vertices of the model. To avoid high CPU loads, it should be noted that the more vertices the model contains, the more CPU time it will take to find the intersection.
     To optimize the calculations and drawing lines, a method was added based on the data on neighboring triangles. In order to draw a line from triangle «A» to triangle «B» the asset uses neighbor's triangles data to find the entry and exit positions of the triangles. This method significantly improves performance for objects with many vertices, but can draw a line with inaccuracy for non-convex objects, because it does not check data about other triangles that may lie «closer» to the camera and close the verifiable (neighboring) vertices.

    Tools

     Tool - is an instrument for processing layer texture when the user paints on it. The asset has 8 built-in tools: brush, erase, bucket, eyedropper, brush sampler, clone, blur, gaussian blur, and grayscale tool. User can switch between tools to get different drawing results. Let’s look at tools functional:

    • Brush - default tool for painting. Tool parameters:
      • Can Paint Lines - whether to paint lines. If this flag is disabled, only dots will be used for painting;
      • Draw On Brush Move Only - render brush only when a user moves the input source (mouse, finger, pen, etc.).
      • Use Pattern - whether to draw using pattern texture instead of solid color. Note that drawing using pattern textures is supported only in PaintMode Additive mode;
      • Pattern Texture - reference to the pattern texture;
      • Pattern Scale - scale of pattern texture;
      • Pattern Angle - angle of drawing pattern texture;
      • Pattern Offset - offset of pattern texture drawing;
    • Erase - erase layer texture using the brush, opposite for drawing;
    • Bucket - tool for filling parts of the texture with selected color. Tool parameters:
      • Tolerance - tolerance value from 0 to 1. Tolerance means how many pixels it will cover. A bigger value means that more pixels will be filled with brush color.
      • Use Pattern - whether to fill using pattern texture instead of solid color;
      • Pattern Texture - reference to the pattern texture;
      • Pattern Scale - scale of pattern texture;
      • Pattern Angle - angle of drawing pattern texture;
      • Pattern Offset - offset of pattern texture drawing;
    • Eyedropper - pick a color and set it as a brush color. Tool parameters:
      • Use All Active Layers - use all layers for color picking. If disabled, only an active layer will be used;
      • Sample Alpha - whether to sample pixels alpha.
    • BrushSampler - copies a part of the drawing area to a brush texture;
    • Clone - tool for cloning parts of the texture. Clone tool works the same way as Photoshop or any other painting software. The first click on the object will take the starting position of cloning, all follow-up clicks will clone parts of texture. Note that the clone tool works in UV-space. Tool parameters:
      • Copy Texture On Press Down - whether to copy source texture on press down;
      • Use All Active Layers - use all active layers for cloning. If disabled only active layer will be used.
    • Blur - tool for blurring parts of the texture. Blur implementation using a simplified Gaussian blur algorithm that works in two Shader Passes and can be configured to change blur strength. Blur tool has such parameters:
      • Iterations - iterations count for blurring. A higher value means strengthening the blur. It is recommended to use parameters in a range of 1 to 5. A higher value also means that the blur will use more GPU resources and the draw call count will be increased (two draw calls per iteration);
      • BlurStrength - the strength of the blur. Zero means minimal blur, higher value - strengthen blur;
      • DownscaleRatio - value for the number of times to downscale the texture size for blurring;
      • Use All Active Layers - use all active layers for blurring. If disabled only the active layer will be used.
    • Gaussian Blur - tool for blurring parts of the texture. Blur implementation using Gaussian blur algorithm works in one Shader Pass and can be configured to change blur strength. Blur tool has such parameters:
      • Kernel size - iterations count for blurring, a higher value means strengthening the blur. Recommended to use parameters ranging from 3 to 7;
      • Spread - the spread of blur. Zero means minimal blur, higher value - strengthen blur. Recommended to use parameters in the range from 0 to 5;
      • Use All Active Layers - use all active layers for blurring. If disabled only the active layer will be used.
    • Grayscale - tool for layer texture discoloration. The area above the brush discolors when a user draws with the grayscale tool (saturation set to 0).

    Brushes and Presets

     The asset has a preset system that stores brush parameters in ScriptableObject, which allows them to be reused with PaintManagers/PaintController. Presets located in the file at: Assets/XDPaint/Resources/XDPaintBrushPresets.asset. You can save brush parameters by name and apply them to other objects from the list. If you change the non-custom parameters of brush in PaintManager/PaintController inspector window, data won’t be written to Presets automatically.
     Asset have built-in presets that can be reused or modified. Presets can be re-saved using PaintController/PaintManager Inspector window or changed by selecting XDPaintBrushPresets asset in Project window and editing in Inspector window.

    Brushes BrushPreset

     Preset can be re-saved to a new preset, for this click on Save As button.
    Preset can be renamed, for this click on Rename button and choose a new name and click on Save button.
     Preset can be removed, for this click on Remove button and confirm it using the dialogue box.
    You can use one brush parameters for all PaintManagers, for that check Use Shared Settings in PaintController, otherwise, each PaintManager will have unique brush parameters.

    PaintController brush

    How to add a new brush?

     To add a new brush, select [XDPaintContainer] GameObject in Hierarchy tab, then in Inspector tab set a unique brush name, select brush texture, and brush parameters, and save it as preset using Save As button. It also works the same way in PaintManager component.
     A new brush will be added to Brush Presets (XDPaintBrushPresets.asset) and can be chosen in PaintController/PaintManager components.

    New brush

    Layers and Blending Modes

     «2D/3D Paint» supports work with layers and blending modes. Each layer represents RenderTexture that can be used for painting. Layers are used to perform tasks such as compositing multiple images. Note that each layer consumes memory. For example, using a 1024x1024 texture will take 4MB (32 bytes per pixel). The blending mode defines how a given layer blends with the layers below it.
     Asset supports the following blending modes:
  • Normal - opaque pixels will cover the pixels directly below them without applying any math or algorithm applied to them. You can, of course, reduce the opacity of the layer to reveal the pixels below;
  • Darken - Darken Blending Mode looks at the luminance values in each of the RGB channels and selects either the base color or blend color depending on which is darker. Simply put, this Blending Mode does not blend pixels, it only compares the base and blends colors, and it keeps the darkest of the two. If the blend layer and the base layer color are the same, then there is no change;
  • Multiply - multiplies the luminosity of the base color by the blend color. The resulting color is always a darker color. White produces no change, while black pixels remain;
    This Blending Mode multiplies the luminosity of the base color by the blend color. The resulting color is always a darker color. White produces no change, while the black pixels remain.
  • ColorBurn - this mode gives you a darker result than Multiply by increasing the contrast between the base and the blend colors resulting in more highly saturated mid-tones and reduced highlights.
    Color Burn Blending Mode gives you a darker result than Multiply by increasing the contrast between the base and the blend colors resulting in more highly saturated mid-tones and reduced highlights.
  • LinearBurn - decreases the brightness of the base color based on the value of the blend color. The result is darker than Multiply but less saturated than Color Burn.
  • DarkerColor - this Blending Mode does not blend pixels. It only compares the base and blends colors, and it keeps the darkest of the two.
  • Lighten - this mode takes a look at the base color and blends color, and it keeps whichever one of the two is the lightest. If the blend colors and the base colors are the same, then no change is applied.
  • Screen - the resulting color is always a brighter color. Black produces no change, while the brighter pixels remain.
  • ColorDodge - gives you a brighter effect than Screen by decreasing the contrast between the base and the blend colors, resulting in saturated mid-tones and blown highlights.
    Color Dodge Blending Mode gives you a brighter effect than Screen by decreasing the contrast between the base and the blend colors, resulting in saturated mid-tones and blown highlights.
  • LinearDodge - this Blending Mode looks at the color information in each channel and brightens the base color to reflect the blend color by increasing the brightness. Blending with black produces no change.
  • LighterColor - this Blending Mode does not blend pixels. It only compares the base and blends colors, and it keeps the brightest of the two.
  • Overlay - is a combination of Multiply and Screen with the base layer always shining through. Overlay uses the Screen Blending Mode at half strength on colors lighter than 50% gray. And the Multiply Blending Mode at half strength on colors darker than 50% gray. 50% gray itself becomes transparent.
  • SoftLight - it applies either a darkening or lightening effect depending on the luminance values, but in a much more subtle way.
  • HardLight - combines the Multiply and Screen Blending Modes using the brightness values of the Blend layer to make its calculations. Overlay uses the base layer.
  • VividLight - anything darker than 50% gray is darkened, and anything lighter than 50% gray is Lighten.
  • LinearLight - it uses a combination of Linear Dodge Blending on lighter pixels and a Linear Burn on darker pixels.
  • PinLight - it is an extreme Blending Mode that performs Darken and Lighten Blending Mode simultaneously. It can result in patches or blotches, and it completely removes all mid-tones.
  • HardMix - this applies the blend by adding the value of each RGB channel into the blend layer to the corresponding RGB channel in the base layer.
    The resulting image loses a lot of detail, and the colors can only be black, white, or any of the six primary colors. Red, green, blue, cyan, magenta, or yellow.
  • Difference - this mode uses the difference of the base and blend pixels as the resulting blend. White inverts the colors of the base layer.
  • Exclusion - is very similar to Difference. Blending with white inverts the base color values, while blending with black produces no change. However, Blending with 50% gray produces 50% gray.
  • Subtract - subtracts pixel values from the base layer. This Blending Mode drastically darkens pixels by subtracting brightness.
    Black has no effect. Only as the blend values get brighter, does the result get darker.
  • Divide - produces the opposite effect as Subtract. White has no effect. Only as the blend values get darker, does the result get brighter.
  • Hue - preserves the luminosity and saturation of the base pixels while adopting the hue of the blend pixels.
    Hue can be used to change hues in a layer while maintaining the tones and saturation of the original.
  • Saturation - preserves the luminosity and hue of the base layer while adopting the saturation of the blend layer.
    A black-and-white blend layer also turns the image into grayscale because none of the pixels in the luminosity layer have saturation.
  • Color - preserves the luminosity of the base layer while adopting the hue and saturation of the blend layer. Color is the ideal Blending Mode for coloring monochromatic images.
    Also, Color, along with the Luminosity Blending Mode, is the second pair of Commuted Blending Modes.
  • Luminosity - preserves the hue and saturation of the base layer while adopting the luminosity of the blend layer.
  • Layers Container

     Layers Data (layers textures and their parameters) can be stored in ScriptableObject which is called LayersContainer.
    LayersContainer contains all data about layers that PaintManager has: layers textures, names, opacity, blending options, masks data, and layers order. To save layers data to LayersContainer, you can use Save Layers button in PaintManager component:
    Save layers

    After choosing the path, layers data will be saved to LayersContainer asset, layers textures will be saved to the asset folder automatically. LayersContainer asset has an array of LayerData that contains all layers parameters (right part of the image).

    LayerContainer

     If PaintManager has a reference to LayersContainer asset in LayersContainer field, during initialization, it will load layers from the asset:

    Loaded layers

    Masks

     Asset supports layers masks. Applying masks to a layer is a reversible way to hide part of a layer. This method allows for more editing flexibility instead of permanently erasing or deleting part of a layer.
     Mask in asset represents RenderTexture in R8 format and can be created/set from code or by setting the reference to the Mask Texture in LayersContainer.

    Mask texture Mask texture

    Mask result

    How to work with layers?

    PaintManager component has buttons to handle layers:

    • To add a new layer, click on Add Layer button;
    • To remove the active layer, click on Remove Layer button;
    • To remove the active layer mask, click on Remove Layer Mask button;
    • To merge the active layer with the layer below, click on Merge Layers button;
    • To merge all active layers into the active layer, click on Merge All Layers button;
    • To switch to the active layer, click on Set Next Active button. You can switch to an active layer by clicking on the drag area on the left part of the layer UI;
    • To change the layer's order, drag and drop the left layer area up or down:
    Layers

    API Help

    PaintController

     Class singleton that stores all PaintManagers. Use shared settings for all PaintManagers when the flag Use Shared Settings is checked. Otherwise, each PaintManager will use its own settings.
    Main public fields, properties, and methods:
    public bool OverrideCamera { … } - whether to override Camera that will be used for raycasts;
    public Camera Camera { … } - reference to the camera;
    public bool UseSharedSettings - whether to use settings of PaintController (Brush, PaintMode and Tool) for all PaintManagers;
    public PaintMode PaintMode { … } - paint mode;
    public PaintTool Tool { … } - current tool;
    public Brush Brush { … } - return brush instance;
    public void RegisterPaintManager(PaintManager paintManager) - register PaintManager;
    public void UnRegisterPaintManager(PaintManager paintManager) - unregister PaintManager;
    public IPaintMode GetPaintMode(PaintMode mode) - return instance of paint mode;
    public PaintManager[] ActivePaintManagers() - return active PaintManagers;
    public PaintManager[] AllPaintManagers() - return all registered PaintManagers.

    InputController

     Class singleton is a component for user input management.
    Main public fields, properties, and methods:
    public bool IsVRMode - whether to use VR mode instead of Mouse / Touch input;
    public Transform PenTransform - Transform of Pen/Hand for painting, used for VR device;
    public int MaxTouchesCount - maximum number of touches for simultaneous drawing;
    public Canvas Canvas - Canvas to ignore raycasts (children objects of Canvas will prevent painting);
    public GameObject[] IgnoreForRaycasts - GameObjects to ignore raycasts (children objects of Canvas that won't prevent painting);
    public event Action OnUpdate - on input update;
    public event Action<int, Vector3> OnMouseHover - mouse hover event on objects;
    public event Action<int, Vector3, float> OnMouseDown - left mouse click event on objects;
    public event Action<int, Vector3, float> OnMouseButton - left mouse button pressing event on objects;
    public event Action<int, Vector3> OnMouseUp - event of releasing the left key of the mouse.

    RaycastController

     Class singleton is a controller for data checks of ray intersections with the triangles and the keeper of the data about all available objects to make a raycast checks.
    Main public fields, properties, and methods:
    public bool UseDepthTexture - whether to use Depth Texture for checking raycasts. This is useful in cases when more than one PaintManager is active on the scene, because in this case, it will paint on the nearest PaintManager to the ray origin. Note that if you use SRP, you need to enable DepthTexture in the URP/HDRP settings;
    public void InitObject(IPaintManager paintManager, Component paintComponent, Component renderComponent) - initializes a new mesh object;
    public void DestroyMeshData(IPaintManager paintManager) - destroys previously created mesh data for raycasts;
    public Triangle Raycast(IPaintManager sender, Ray ray, Vector3 screenPosition) - check the intersection of the ray with the triangles objects, returns Triangle of raycast;
    public Triangle RaycastLocal(IPaintManager paintManager, Ray ray) - check the intersection of the ray with the triangles of the Transform objectTransform, returns Triangle of raycast;
    public Triangle NeighborsRaycast(IPaintManager sender, Triangle triangle, Ray ray) - check the intersection of the ray with the neighboring triangles of Triangle triangle, returns Triangle of raycast.

    PaintManager

     The manager for the paint object. It combines the main component for painting on objects, containing instances of Paint, BasePaintObject.
    Main events, fields, properties and methods:
    public event Action<PaintManager> OnInitialized - event of initialization finish, returns PaintManager instance;
    public event Action OnDisposed - event of the resources disposed of PaintManager;
    public GameObject ObjectForPainting - GameObject of the object to be painted;
    public FilterMode FilterMode { ... } - property of the FilterMode of RenderTextures;
    public Brush Brush { ... } - property of the brush of PaintManager;
    public ToolsManager ToolsManager { ... } - property of the ToolsManager, contains and manages all the tools for painting;
    public PaintTool Tool { ... } - property of the current paint tool;
    public BasePaintObject PaintObject { … } - property of the painted object;
    public IStatesController StatesController { … } - StatesController instance;
    public bool UseSourceTextureAsBackground { … } - property of using source texture as background texture for the resulting image;
    public bool UseNeighborsVerticesForRaycasts { … } - property of using neighbors vertices for raycasts when drawing lines;
    public bool HasTrianglesData { … } - whether component contains any triangles data;
    public Triangles[] Triangles { … } - get/set triangles array;
    public bool Initialized { … } - property of the initialization status of the object;
    public int SubMesh { … } - property of the sub mesh of the PaintObject mesh;
    public int UVChannel { … } - property of the UV-channel of the mesh;
    public void Init() - initialize PaintManager. If PaintManger was initialized before, it will re-create its internal data: RenderTextures, Meshes and Materials;
    public void DoDispose() - destroy all PaintManager created RenderTextures, Meshes and Materials, restores source material and texture;
    public void Render() - invoke object rendering;
    public void SetPaintMode(PaintMode paintMode) - set paint mode;
    public IPaintMode PaintMode - return paint mode instance;
    public void FillTrianglesData(bool fillNeighbors = true) - fill model data, argument - to fill data about neighbors triangles;
    public void ClearTrianglesData() - remove filled information about triangles;
    public void ClearTrianglesNeighborsData() - remove filled information about the neighboring triangles;
    public void SetTrianglesContainer(TrianglesContainer trianglesContainerData) - set triangles data directly from code using TrianglesContainer ScriptableObject;
    public RenderTexture GetPaintTexture() - return RenderTexture of combined texture of painting;
    public RenderTexture GetPaintInputTexture() - return RenderTexture of input (current frame dot/line for Default PaintMode or texture that was drawn between mouse down and mouse up events for Additive PaintMode);
    public RenderTexture GetResultRenderTexture() - return result RenderTexture (combined layers);
    public Texture2D GetResultTexture(bool hideBrushPreview = false) - return the resulting texture (combined layers), argument - whether to hide brush preview. Note that it should be destroyed as texture creation is definitely a possibility for memory leaks in Unity if you're not disposing of the old ones properly;
    public SetLayersData (LayersContainer container) - set layers data;
    public LayerData[] GetLayersData() - return current layers data;
    public void InitBrush() - initialize brush settings.

    BasePaintObject

     Base class for painting on RenderTexture. It can be declared as CanvasRendererPaint, MeshRendererPaint, or SpriteRendererPaint. The derived classes CanvasRendererPaint, MeshRendererPaint, and SpriteRendererPaint contain logic to check the painting position based on the data from the InputController and return the UV texture position for further work according to the base class logic.
    Main public fields, properties, and methods:
    public event Action<PointerData> OnPointerHover - mouse hover event. Arguments: PointerData that contains paint object local position, screen position, UV position, texture position, pressure, finger id;
    public event Action<PointerData> OnPointerDown - mouse down event. Arguments: PointerData that contains paint object local position, screen position, UV position, texture position, pressure, finger id;
    public event Action<PointerData> OnPointerPress - mouse press event. Arguments: PointerData that contains paint object local position, screen position, UV position, texture position, pressure, finger id;
    public event Action<PointerUpData> OnPointerUp - mouse up event. Arguments: PointerUpData that contains screen position, is mouse in object bounds, finger id;
    public event Action<DrawPointData> OnDrawPoint - draw point event, can be used by the developer to obtain data about painting. Arguments: DrawPointData that contains texture position, pressure, finger id;
    public event Action<DrawLineData> OnDrawLine - draw line event, can be used by the developer to obtain data about painting. Arguments: DrawLineData that contains line start texture position, line end texture position, line start pressure, line end pressure, finger id;
    public bool IsPainting { … } - property, whether user is painting;
    public bool IsPainted { … } - property, whether user is painting (in current frame vertices were drawn);
    public bool ProcessInput - whether input processing for current paint object;
    public new Camera Camera { … } - camera property used to determine the position of the painting on the texture;
    public void Init(Camera camera, Transform objectTransform, Paint paint, IRenderTextureHelper renderTextureHelper, IStatesController statesController) - initialization of the object;
    public void DoDispose() - destroy previously created RenderTextures and Meshes;
    public void OnMouseHover(int fingerId, Vector3 position, Triangle triangle = null) - on mouse hover method;
    public void OnMouseDown(int fingerId, Vector3 position, float pressure = 1f, Triangle triangle = null) - on mouse down method;
    public void OnMouseButton(int fingerId, Vector3 position, float pressure = 1f, Triangle triangle = null) - on mouse button method;
    public Vector2? GetPaintPosition(int fingerId, Vector3 position, Triangle triangle = null) - return paint position from screen position if ray intersects an object;
    public void OnMouseUp(int fingerId, Vector3 position) - on mouse up method;
    public void DrawPoint(DrawPointData drawPointData) - draw point from code using DrawPointData;
    public void DrawPoint(Vector2 position, float pressure = 1f, int fingerId = 0) - draw point from code using texture position;
    public void DrawLine(DrawLineData drawLineData) - draw line from code using DrawLineData;
    public void DrawLine(Vector2 positionStart, Vector2 positionEnd, float pressureStart = 1f, float pressureEnd = 1f, int fingerId = 0) - draw line from code using texture positions;
    public void FinishPainting(int fingerId = 0) - force end painting;
    public void OnRender() - render to combined RenderTexture;
    public void Render() - combine all RenderTextures to Combined RenderTexture;
    public void ClearTexture(bool writeToUndo = false) - clear the texture to paint, to display changed data call paintManager.Render() method;
    public void RenderToTextureWithoutPreview(RenderTexture resultTexture) - render to Combined Render Texture without brush preview.

    StatesController

     A class that manages states of undo/redo. Contains and manages changed data, the previous object states.
    Main public fields, properties, and methods:
    public int ChangesCount { ... } - changes count;
    public event Action<RenderTexture> OnClearTextureAction - event for RenderTexture clear action;
    public event Action OnRenderTextureAction - event for RenderTexture undo/redo, invokes object render;
    public event Action OnChangeState - event to undo/redo;
    public event Action OnResetState - event to undo, used to clear Input Texture;
    public event Action OnUndo - event to undo action performed;
    public event Action OnRedo - event to redo action performed;
    public event Action<bool> OnUndoStatusChanged - event to undo status change, returns flag that user can perform undo action;
    public event Action<bool> OnRedoStatusChanged - event to redo status change, returns flag that user can perform redo action;
    public void DoDispose() - destroy state data;
    public void Enable() - enable undo/redo functionality;
    public void Disable() - disable undo/redo functionality;
    public void AddState(Action action) - add action as a state;
    public void AddState(object entity, string property, RenderTexture oldValue, RenderTexture newValue, Texture source) - add RenderTexture changes states;
    public void AddState(object entity, string property, object oldValue, object newValue) - add property change state;
    public void AddState(IList collection, NotifyCollectionChangedEventArgs rawEventArg) - add list change state;
    public void Undo() - undo action;
    public void Redo() - redo action;
    public int GetUndoActionsCount() - return count of undo actions;
    public int GetRedoActionsCount() - return count of redo actions;
    public bool CanUndo() - return if user can undo action;
    public bool CanRedo() - return if user can redo action;
    public void EnableGrouping() - enable grouping of the actions. All actions will be recorded as one action until DisableGrouping() is invoked;
    public void DisableGrouping() - disable grouping of the actions.


    LayersController

     A class that contains layers instances and manages them.
    Main public fields, properties, and methods:
    public event Action<ObservableCollection<ILayer>, NotifyCollectionChangedEventArgs> OnLayersCollectionChanged - event on layers collection changed;
    public event Action<ILayer> OnLayerChanged - event for layer OnRenderPropertyChanged action, used for Render object after layer parameters changed;
    public event Action<ILayer> OnActiveLayerSwitched - event to change active layer index;
    public event Action<bool> OnCanRemoveLayer - event to get flag is layer can be removed, as asset requires at least one layer for painting;
    public ReadOnlyCollection<ILayer> Layers { ... } - layers collection;
    public ILayer ActiveLayer { ... } - instance of active layer;
    public bool CanDisableLayer { ... } - can disable layer or not, used to prevent disabling all layers;
    public bool CanRemoveLayer { ... } - can remove the active layer, used to prevent the removal of the last layer;
    public bool CanMergeLayers { ... } - can merge layers. Check if the active layer and layer below are enabled;
    public bool CanMergeAllLayers { ... } - can merge all layers. Check if the active layer and at least one different layer is enabled;
    public int ActiveLayerIndex { ... } - active layer index;
    public void DoDispose() - release all layers data;
    public void Init(Texture sourceTexture) - initialize layers;
    public void CreateBaseLayers(Texture sourceTexture, bool useSourceTextureAsBackground) - create default layers using source texture;
    public void SetFilterMode(FilterMode mode) - set filter mode for layers textures;
    public ILayer AddNewLayer() - create a new layer;
    public ILayer AddNewLayer(string name) - create a new layer with name;
    public ILayer AddNewLayer(string name, Texture sourceTexture) - create a new layer with the name from the source texture;
    public void AddLayerMask(ILayer layer, Texture source) - create a layer mask using source texture;
    public void AddLayerMask(ILayer layer) - create a layer mask;
    public void AddLayerMask(Texture source) - create a layer mask for the active layer using the source texture;
    public void AddLayerMask() - create a layer mask for the active layer;
    public void RemoveActiveLayerMask() - remove active layer mask;
    public ILayer GetActiveLayer() - return active layer;
    public void SetActiveLayer(ILayer layer) - set layer as active;
    public void SetActiveLayer(int index) - set active layer by layer index;
    public void SetLayerOrder(ILayer layer, int index) - set layer order;
    public void RemoveActiveLayer() - remove active layer;
    public void RemoveLayer(ILayer layer) - remove layer;
    public void RemoveLayer(int index) - remove layer by index;
    public void MergeLayers() - merge the active layer with the layer below into the layer below. Invokes when the active layer and layer below are enabled;
    public void MergeAllLayers() - merge all enabled layers into an active layer. Invokes when at least 2 layers are enabled;
    public void SetLayerTexture(int index, Texture texture) - set layer texture.
    public Texture2D GetActiveLayerTexture() - return Texture2D of the active layer. Note that it should be destroyed as texture creation is definitely a possibility for memory leaks in Unity if you're not disposing of the old ones properly;
    public Texture2D GetLayerTexture(int layerIndex) - return Texture2D of the layer by layer index. Note that it should be destroyed as texture creation is definitely a possibility for memory leaks in Unity if you're not disposing of the old ones properly;


    Layer

     A class that contains and manages layer data.
    Main public fields, properties, and methods:
    public event Action<ILayer> OnLayerChanged - event for layer properties change;
    public Action<Layer> OnRenderPropertyChanged - event for layer properties change. Invokes when any of the properties that affect the rendering layer are changed;
    public bool Enabled { ... } - enable flag of layer;
    public bool CanBeDisabled { ... } - return flag is layer can be disabled;
    public bool MaskEnabled { ... } - enable flag for mask;
    public string Name { ... } - layer name;
    public float Opacity { ... } - opacity value of the layer;
    public Texture SourceTexture { ... } - layer source texture;
    public RenderTexture RenderTexture { ... } - layer render texture;
    public RenderTargetIdentifier RenderTarget { ... } - render texture identifier;
    public Texture MaskSourceTexture { ... } - mask source texture;
    public RenderTexture MaskRenderTexture { ... } - mask render texture;
    public RenderTargetIdentifier MaskRenderTarget { ... } - mask render texture identifier;
    public BlendingMode BlendingMode { ... } - layer blending mode;
    public void Create(string layerName, int width, int height, RenderTextureFormat format, FilterMode filterMode) - create layer render texture;
    public void Create(string layerName, Texture source, RenderTextureFormat format, FilterMode filterMode) - create layer render texture;
    public void Init(CommandBufferBuilder bufferBuilder, Func<bool> canDisableLayer) - initialize layer;
    public void AddMask(RenderTextureFormat format) - create layer mask;
    public void AddMask(Texture maskTexture, RenderTextureFormat format) - create a layer mask based on texture;
    public void RemoveMask() - remove layer mask;
    public void SaveState() - save layer render texture using StatesController;
    public void DoDispose() - release layer data.


    Paint

     A class that stores and manages painting material data and its parameters.
    Main public fields, properties, and methods:
    public Material Material { … } - paint material;
    public string ShaderTextureName { … } - shader texture name for painting;
    public int DefaultTextureWidth { … } - texture width, in case the object doesn't have a source texture;
    public int DefaultTextureHeight { … } - texture height, in case the object doesn't have a source texture;
    public int MaterialIndex { … } - index of material in the object;
    public Texture SourceTexture { … } - source texture;
    public void Init(IRenderComponentsHelper renderComponentsHelper) - paint material initialization;
    public void DoDispose() - destroy previously created materials;
    public void RestoreTexture() - set source texture to material;
    public void SetObjectMaterialTexture(Texture texture) - set new texture of the object;
    public void SetPreviewTexture(Texture texture) - set preview texture;
    public void SetPaintTexture(Texture texture) - set Layer texture to material;
    public void SetInputTexture(Texture texture) - set Input texture to material;
    public void SetPaintPreviewVector(Vector4 brushOffset) - set data to display brush preview.

    Brush

     A class that stores and manages brush material data and its parameters.
    Main public fields, properties, and methods:
    public string Name { … } - brush name, must be unique;
    public Material Material { … } - brush material;
    public FilterMode FilterMode { … } - FilterMode of the RenderTexture of the brush;
    public Color Color { … } - brush color;
    public Texture SourceTexture { … } - source texture of the brush;
    public RenderTexture RenderTexture { … } - render texture of the brush;
    public float MinSize { … } - minimal size of the brush;
    public float Size { … } - brush size;
    public float RenderAngle { … } - brush render angle in degrees;
    public Quaternion RenderQuaternion { … } - brush render quaternion;
    public float Hardness { … } - brush hardness;
    public bool Preview { … } - brush preview;
    public event Action<Color> OnColorChanged; - event of changing the brush color;
    public event Action<Texture> OnTextureChanged; - event of changing the texture of the brush;
    public event Action<bool> OnPreviewChanged; - event of changing preview flag of the brush;
    public void Init(IPaintMode mode) - initialize the brush Material, Mesh and RenderTexture;
    public void DoDispose() - destroy previously created RenderTexture, Mesh and Material;
    public void SetValues(Brush brush) - set values for the brush from the other brush;
    public void Render() - render brush into RenderTexture;
    public void RenderFromTexture(Texture texture) - render brush from texture without changing SourceTexture;
    public void SetColor(Color colorValue, bool render = true, bool sendToEvent = true) - set the brush color;
    public void SetTexture(Texture texture, bool render = true, bool sendToEvent = true, bool canUpdateRenderTexture = true) - set the brush texture;
    public void SetPaintTool(PaintTool paintTool) - set current tools for changing shader params;
    public void SetPaintMode(IPaintMode mode) - set paint mode.

    ToolsManager

     A class that contains and manages all tools.
    Main public fields, properties, and methods:
    public IPaintTool CurrentTool - return current tool;
    public void Init(PaintManager paintManager) - initialize for PaintManager, subscribe for PaintManager events;
    public void DoDispose() - release tools resources;
    public void SetTool(PaintTool paintTool) - set tool.

    BasePaintTool

     Base class for every tool. Handles the layer texture using input events from PaintManager.
    Main public fields, properties, and methods:
    public virtual PaintTool Type { … } - tool type;
    protected IPaintData Data - data that the tool has to process an image. Used to get information about layers, RenderTextures, Brush, painting states;
    public virtual bool ShowPreview { … } - whether to show preview or not;
    public virtual bool RenderToLayer { … } - whether to render to Layer Texture result;
    public virtual bool RenderToInput { … } - whether to render to Input Texture result;
    public virtual bool AllowRender { … } - whether to allow any render or not;
    public virtual bool CanDrawLines { … } - whether to draw lines or not. If this flag is disabled, the user can only draw dots;
    public virtual bool ConsiderPreviousPosition { … } - whether to render brush if previous paint position wasn't changed;
    public virtual bool RenderToTextures { … } - whether to render to any RenderTextures;
    public virtual bool DrawPreProcess { … } - whether to draw pre process data or not;
    public virtual bool DrawProcess { … } - whether to draw process data or not;
    public virtual bool BakeInputToPaint { … } - whether to bake Input texture to Layer texture or not;
    public virtual bool ProcessingFinished { … } - returns if tool finished processing;
    public virtual bool RequiredCombinedTempTexture { … } - returns if tool requires Combined Temp Texture;
    public virtual bool IsLayersProcessing { … } - returns if layers are processing by LayersController or StatesController;
    public BasePaintToolSettings BaseSettings - reference to the base class settings of the tool;
    public T Settings - reference to the settings class of the tool;
    public void FillWithColor(Color color) - fill active layer with color;
    public virtual void OnDrawPreProcess(RenderTargetIdentifier combined) - draw a preprocess method for changing texture before layers are combined into Combined texture;
    public virtual void OnDrawProcess(RenderTargetIdentifier combined) - draw method for combining layers into Combined texture.

    BasePaintToolSettings

     Base class for every tool settings, each tool can have derived class.
    Main public properties:
    public bool CanPaintLines - whether to draw lines. If this flag is disabled, the user can only draw dots;
    public bool DrawOnBrushMoveOnly - whether to render brush if previous paint position wasn't changed;
     Tools parameters can be displayed in the Inspector tab if they have [PaintToolSettings] attribute:
    Tools

    AverageColorCalculator

     A component to get an average color of texture.
    Main public fields and methods:
    public PaintManager PaintManager - PaintManager to get the average color;
    public PaintRenderTexture PaintRenderTexture - texture to check average color;
    public bool SkipTransparentPixels - whether to skip source texture transparent pixels;
    public event Action<Color> OnGetAverageColor - event to get the average color. Invokes when the user paints;
    public void SetAccuracy(int accuracy) - set sampling accuracy.
     AverageColorCalculator gets an average color using the shader «XD Paint/Average Color» that samples texture. There are two parameters: main texture and accuracy - divider to get samples count. Smaller quantity means better accuracy, and as a result - more GPU resources will be used. As an example, if texture has size 2048x1024 and accuracy has a value of 64, it will sample texture 2048 / 64 = 32 times per horizontal x 1024 / 64 = 16 times per vertical, in sum 32 x 16 = 512 samples. All of this works with GPU, which provides the best performance.

    ColliderPainter

     A component to paint on PaintObject using collisions. This component gets UV-position on OnCollisionEnter and OnCollisionStay and paints on the object using the brush.
    To use this component, add it to an object that will collide with the PaintObject.
    Main public fields and methods:
    public event Action<PaintManager>, Collision> OnCollide - event to get painting information, first argument - PaintManager for painting, second - Collision data;
    public Color Color - color of painting;
    public float Pressure - ratio to scale brush size on painting;

    Frequently used methods

    Check if you can undo/redo: paintManager.StatesController.CanUndo(); paintManager.StatesController.CanRedo(); Subscribe to change state events:
    paintManager.StatesController.OnUndoStatusChanged += OnUndoStatusChanged; paintManager.StatesController.OnRedoStatusChanged += OnRedoStatusChanged; private void OnUndoStatusChanged(bool canUndo) { Debug.Log("Can undo: " + canUndo); } private void OnRedoStatusChanged(bool canRedo) { Debug.Log("Can redo: " + canRedo); }
    Undo/Redo action:
    paintManager.StatesController.Undo(); paintManager.StatesController.Redo();
    Remove undo/redo states:
    paintManager.StatesController.DoDispose();
    Render (repaint) object:
    paintManager.Render();
    Set default texture size for objects without source texture:
    paintManager.Material.DefaultTextureWidth = …; paintManager.Material.DefaultTextureHeight = …;
    Fill layer texture with a color (in this case, the layer texture will be cleared):
    paintManager.Tool = PaintTool.Brush; paintManager.ToolsManager.CurrentTool.FillWithColor(Color.clear); paintManager.Render();
    Set brush size:
    paintManager.Brush.Size = value;
    Set brush hardness:
    paintManager.Brush.Hardness = value;
    Change brush color:
    var brushColor = paintManager.Brush.Color; brushColor = new Color(color.r, color.g, color.b, brushColor.a); paintManager.Brush.SetColor(brushColor);
    Set brush opacity:
    var color = paintManager.Brush.Color; color.a = value; paintManager.Brush.SetColor(color);
    Set brush angle:
    paintManager.Brush.RenderAngle = value;
    Change tool:
    if (PaintController.Instance.UseSharedSettings) { //in case when PaintController has checked UseSharedSettings PaintController.Instance.Tool = PaintTool...; } else { //otherwise: paintManager.Tool = PaintTool...; }
    Set blur tool parameters:
    if (paintManager.ToolsManager.CurrentTool is BlurTool blurTool) { blurTool.Settings.Iterations = 3; blurTool.Settings.BlurStrength = 2f; blurTool.Settings.DownscaleRatio = 1; }
    Set gaussian blur tool parameters:
    if (paintManager.ToolsManager.CurrentTool is GaussianBlurTool gaussianBlurTool) { gaussianBlurTool.Settings.KernelSize = 3; gaussianBlurTool.Settings.Spread = 5f; }
    Enable/Disable input for all PaintManagers:
    InputController.Instance.enabled = value;
    Enable/Disable input for PaintManager instance:
    paintManager.PaintObject.ProcessInput = value;
    Getting the average color of PaintManager:
    public AverageColorCalculator AverageColorCalculator; … AverageColorCalculator.OnGetAverageColor += color => Debug.Log("Average Color: " + color);
    Save painting result to file:
    var path = … ; //path for texture var texture2D = paintManager.GetResultTexture(); var pngData = texture2D.EncodeToPNG(); if (pngData != null) { File.WriteAllBytes(path, pngData); } Destroy(texture2D);

    Save Result Texture

    private void SaveResultToFile(PaintManager paintManager, string fileName) { var texture2D = paintManager.GetResultTexture(); var pngData = texture2D.EncodeToPNG(); if (pngData != null) { var filePath = System.IO.Path.Combine(Application.persistentDataPath, fileName); System.IO.File.WriteAllBytes(filePath, pngData); } Destroy(texture2D); }

    Load From Texture

    public void LoadResultTextureFromFile(PaintManager paintManager, string fileName) { var filePath = System.IO.Path.Combine(Application.persistentDataPath, fileName); var textureData = System.IO.File.ReadAllBytes(filePath); var texture = new Texture2D(1, 1); texture.LoadImage(textureData); var shaderTextureName = paintManager.Material.ShaderTextureName; var previousTexture = paintManager.Material.SourceMaterial.GetTexture(shaderTextureName); paintManager.Material.SourceMaterial.SetTexture(shaderTextureName, texture); var spriteRenderer = paintManager.ObjectForPainting.GetComponent<SpriteRenderer>(); if (spriteRenderer != null) { var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); spriteRenderer.sprite = sprite; } var rawImage = paintManager.ObjectForPainting.GetComponent<RawImage>(); if (rawImage != null) { rawImage.texture = texture; } //copy texture to new layer: paintManager.CopySourceTextureToLayer = true; //copy texture to background layer: //paintManager.UseSourceTextureAsBackground = true; paintManager.Init(); paintManager.Material.SourceMaterial.SetTexture(shaderTextureName, previousTexture); }

    Add a new layer

    var layerName = "SomeLayerName"; paintManager.LayersController.AddNewLayer(layerName);

    In case you need to create a layer from the existing texture:

    var layerName = "SomeLayerName"; var layerTexture = ... //reference to Texture2D, note that the Texture dimensions must be as the source texture paintManager.LayersController.AddNewLayer(layerName, layerTexture);

    Remove layer

    var layerIndex = 0; paintManager.LayersController.RemoveLayer(layerIndex);

    Drawing from code

     Asset supports drawing from code. To draw in the texture space, invoke one of these methods:
    // draw a point where argument - position on the texture paintManager.PaintObject.DrawPoint(new Vector3(512, 512)); // invoke FinishPainting() when drawing lines are finished. FinishPainting() method also saves the result to undo/redo system. paintManager.PaintObject.FinishPainting(); // draw a line where arguments - start and end position on the texture paintManager.PaintObject.DrawLine(new Vector2(400, 612), new Vector2(100, 100)); // invoke FinishPainting() when drawing dots are finished. FinishPainting() method also saves the result to undo/redo system. paintManager.PaintObject.FinishPainting();
    To draw in screen space for MeshRenderer/SkinnedMeshRenderer components you need to invoke these methods:
    // screen position in pixels var screenPos = new Vector3(...); var ray = Camera.main.ScreenPointToRay(screenPos); RaycastController.Instance.Raycast(ray, out var triangle); // then use methods for painting: paintManager.PaintObject.OnMouseDown(screenPos, 1f, triangle); paintManager.PaintObject.OnMouseButton(screenPos, 1f, triangle); paintManager.Render(); paintManager.PaintObject.OnMouseUp(screenPos, 1f, triangle);
    To draw in screen space for SpriteRenderer/RawImage components you need to invoke methods for painting:
    // screen position in pixels var screenPos = new Vector3(…); // then use methods for painting: paintManager.PaintObject.OnMouseDown(screenPos); paintManager.PaintObject.OnMouseButton(screenPos); paintManager.Render(); paintManager.PaintObject.OnMouseUp(screenPos);

    Creating PaintManager from code

    PaintManager can be created from code. Here is an example of creating PaintManager using this method:
    public void AddPaintManager(GameObject objectForPainting, Material material, string shaderTextureName = "_MainTex", TrianglesContainer triangles = null) { var paintManager = objectForPainting.AddComponent<PaintManager>(); paintManager.ObjectForPainting = objectForPainting; paintManager.Material.SourceMaterial = material; paintManager.Material.ShaderTextureName = shaderTextureName; if (objectForPainting.GetComponent<MeshRenderer>() != null || objectForPainting.GetComponent<SkinnedMeshRenderer>() != null) { paintManager.Triangles = triangles; } paintManager.Init(); }
    PaintManager can be re-initialized with one line of code. Note that afterward, previously created resources (RenderTextures, Meshes, Materials) will be re-created:
    paintManager.Init();
     Change the texture for the painting using code:
    public void ChangeTexture(Texture texture) { var material = paintManager.Material.SourceMaterial; material.SetTexture(paintManager.Material.ShaderTextureName, texture); paintManager.Material.SourceMaterial = material; paintManager.Init(); }

    VR Support

     «2D/3D Paint» supports work with VR. Asset uses Unity XR System and support for any other VR plugins can be easily implemented and require minimal code changes to properly work with VR input devices.
    To enable VR support, perform the following steps:

  • Select XDPaintSettings asset (Assets/XDPaint/Resources/XDPaintSettings.asset). In the Inspector tab, check Is VR Mode. This action will add define «XDPAINT_VR_ENABLE» for the current platform:
    VR Settings VR Define
    Alternatively, you can uncomment the first line of InputController (#define XDPAINT_VR_ENABLE).
  • (Optional) Configure VR input devices in InputController.InitVR() method, current body of the method can be replaced;
  • (Optional) Replace XR input line in InputController to change VR input trigger for painting, as an example:
  • //next line can be changed for VR device input if (leftHandedControllers.Count > 0 && leftHandedControllers[0].TryGetFeatureValue(CommonUsages.triggerButton, out var triggerValue) && triggerValue) Replace it with:
    //next line can be changed for VR device input if (leftHandedControllers.Count > 0 && leftHandedControllers[0].TryGetFeatureValue(CommonUsages.primaryButton, out var triggerValue) && triggerValue)
  • Set Pen Transform as your pen in the InputController component.
  • That’s all! Your VR device is ready to paint!

    Tips

     Here are some tips that will help in its configuration and use «2D/3D Paint»:

  • To disable the rounding of the brush, set the Brush.Hardness parameter to 1;
  • Use the Use Neighbors Vertices For Raycasts for PaintManager if it is possible. Using this flag allows you to draw lines for objects such as MeshRenderer and SkinnedMeshRenderer using less CPU time, but there may be inaccuracies with painting on non-convex objects. Please note that the generation of data may take a few seconds and depends on the count of vertices;
  • Field Triangles of PaintManager contains data about the model's vertices. Data can take up disk space and contain:
    • The indices of vertices;
    • The number (ID) of the triangle;
    • The indices of the vertices of the neighboring triangles.
    Use the context menu to add and/or delete data. If you set the flag Use Neighbors Vertices For Raycasts to false, it will delete the data of neighboring triangles. The triangles data field is hidden in Inspector but can be displayed using Debug mode in the Inspector window of Unity Editor;
  • «2D/3D Paint» supports Universal Render Pipeline (URP) / High Definition Render Pipeline (HDRP), for pipeline-compatible shaders for objects for painting;
  • To paint on Sprite with transparent areas, use Material with Shader XD Paint/Alpha Mask and set Texture as Main Texture and Mask Texture:
  • Alpha Mask shader
    After, choose the Shader Texture Name as _MainTex: Shader Texture name
  • If you use Skinned Mesh for painting, it is recommended to use default transforms: root animation and mesh objects position as 0:0:0, rotation as 0:0:0, and scale 1:1:1:
  • Blender export settings Blender export settings
  • You can clear triangles data for PaintObject that works with MeshRenderer/SkinnedMeshRenderer and add triangles data again using ContextMenu:
  • Clear Triangles Data Fill Triangles Data

    Triangles Data Window
  • To work with pixel art graphics, set Filter Mode to Point in PaintController and PaintManager components, and set BrushDuplicatePartWidth to 1 in Settings:
  • PaintController Point Filter Mode PaintManager Point Filter Mode

    Setting Point Mode

    Contacts

    Please let me know if you have any questions, ideas, or suggestions.
    E-mail: unitymedved@gmail.com