.. _doc_drawable_textures: Using DrawableTextures ====================== DrawableTextures are a type of Texture2D with additional functions for modifying the texture via the GPU. This can be used for procedural texturing, real-time effects, and much more. Basic rectangle blitting ------------------------ The most basic operation on a drawable texture is :ref:`blit_rect() `. Blitting (copying) the whole of one texture into the given rectangle on the ``DrawableTexture``. .. tabs:: .. code-tab:: gdscript GDScript texture.blit_rect(Rect2(20, 50, 60, 60), preload("res://circle.svg")) texture.blit_rect(Rect2(20, 50, 60, 60), preload("res://circle.svg"), Color.WHITE, 0) .. code-tab:: csharp texture.BlitRect(new Rect2I(20, 50, 60, 60), GD.Load("res://circle.svg")); texture.BlitRect(new Rect2I(20, 50, 60, 60), GD.Load("res://circle.svg"), Colors.White, 0); The code above blits the circle texture into the rectangle ``(20, 50, 60, 60)`` on the ``DrawableTexture``. ``(20, 50)`` is the top left corner of the rectangle being drawn to, and ``(60, 60)`` is its size. If you wanted to draw over the whole texture, simply match the ``rect`` parameter to the ``DrawableTexture``'s size. The third parameter in ``blit_rect()`` is an optional parameter, ``modulate``. This is a color that the output is multiplied by (in the default behavior). This can be used to recolor, or even mask the drawn output of ``blit_rect()``. For example, using a modulate of ``Color(0, 0, 1, 0)`` will only draw to and update the blue values of each pixel on the ``DrawableTexture``. ``blit_rect()``'s fourth parameter, ``mipmap``, can specify a mipmap level to draw to. You only need to use this if you want fine control over each mipmap layer. Keep in mind, you do not need to adjust the rectangle size; it is converted to a portion of the texture's whole size. If you just want to update all the mipmap layers, you should draw to the texture, then call ``generate_mipmaps()`` on it. Blend modes and texture blit shaders ------------------------------------ The drawing process for ``blit_rect()`` and DrawableTextures is controlled by a :ref:`texture blit shader `. Even when the user does not supply one, the engine has a default texture blit shader it uses. .. code-block:: glsl // Default texture blit shader. shader_type texture_blit; render_mode blend_mix; uniform sampler2D source_texture0 : hint_blit_source0; uniform sampler2D source_texture1 : hint_blit_source1; uniform sampler2D source_texture2 : hint_blit_source2; uniform sampler2D source_texture3 : hint_blit_source3; void blit() { // Copies from each whole source texture to a rect on each output texture. COLOR0 = texture(source_texture0, UV) * MODULATE; COLOR1 = texture(source_texture1, UV) * MODULATE; COLOR2 = texture(source_texture2, UV) * MODULATE; COLOR3 = texture(source_texture3, UV) * MODULATE; } The blend mode specified in ``render_mode`` defines how the output color value is blended with the current color of the pixel on the DrawableTexture. The engine defaults to ``blend_mix`` if no blend mode is specified in ``render_mode``. Texture blit shaders also support ``blend_add`` (additive), ``blend_sub`` (subtractive), ``blend_mul`` (multiply), and ``blend_disabled`` (alpha does not act as transparency and is written as-is). The premultiplied alpha blend mode is *not* supported here. It is also possible to use a different blend mode than the one specified in the shader. To do so, you can instantiate and pass in a new :ref:`class_BlitMaterial` in the script. .. tabs:: .. code-tab:: gdscript GDScript var blit_material = BlitMaterial.new() blit_material.blend_mode = BlitMaterial.BLEND_MODE_DISABLED texture.blit_rect(Rect2(0, 0, 200, 200), load("res://icon.svg"), Color.WHITE, 0, blit_material) .. code-tab:: csharp var blitMaterial = new BlitMaterial { BlendMode = BlitMaterial.BlendModeEnum.Disabled, }; texture.BlitRect(new Rect2I(0, 0, 200, 200), GD.Load("res://icon.svg"), Colors.White, 0, blitMaterial); If you want more complex behavior, you can write your own texture blit shader. Create a new shader with the ``texture_blit`` shader type, write your shader code, and load it into a material to pass to the function. .. note:: The material is passed as a function parameter, rather than bound to the resource. This makes it easier to perform multiple types of draws to the same texture. Using multiple blits in a single texture ---------------------------------------- DrawableTextures also have a :ref:`blit_rect_multi() ` method, which allows for up to 4 inputs and outputs in the same step. .. tabs:: .. code-tab:: gdscript GDScript texture.blit_rect_multi( Rect2(0, 0, 200, 200), [preload("res://icon.svg"), preload("res://circle.svg")], [other_drawable_texture] ) .. code-tab:: csharp texture.BlitRectMulti( new Rect2I(0, 0, 200, 200), [GD.Load("res://icon.svg"), GD.Load("res://circle.svg")], [otherDrawableTexture]); The default behavior of this is to just match each input and output in order. For example, this can be useful for drawing to an albedo, normal, and height texture simultaneously. Of course, this behavior can also be customized via the texture blit shader. In the shader, the extra outputs are written to via ``COLOR1``, ``COLOR2``, and ``COLOR3`` (with ``COLOR0`` being the primary output). The extra inputs can be read as uniforms with ``hint_blit_source1``, ``hint_blit_source2``, and ``hint_blit_source3``. .. _doc_drawable_textures_example_1: Example 1: Simple painting -------------------------- One of the most intuitive uses for DrawableTextures is for, well, drawing! For this example, we're going to start a new project, and create a new UI scene with a Control node at its root. Next, you'll need to create a :ref:`class_TextureRect` node, which is going to be our user's canvas. Size it appropriately for your screen, and then attach a new script to it. The start of this script should initialize the TextureRect's texture to a new DrawableTexture. .. tabs:: .. code-tab:: gdscript GDScript extends TextureRect func _ready(): texture = DrawableTexture2D.new() # Be careful; if the dimensions of the node are not equal to the size set here, # our draw call later will seem to happen at the wrong spot. texture.setup(500, 500, DrawableTexture2D.DRAWABLE_FORMAT_RGBA8, false) .. code-tab:: csharp private DrawableTexture2D _texture = new DrawableTexture2D(); public override void _Ready() { // Be careful; if the dimensions of the node are not equal to the size set here, // our draw call later will seem to happen at the wrong spot. _texture.Setup(500, 500, DrawableTexture2D.DrawableFormat.Rgba8, null, false); Texture = _texture; } Next, we need the TextureRect to respond to the player clicking and dragging as if they are painting. To do this, we can override the ``_gui_input()`` method from the TextureRect in our script, and parse InputMouseButton and InputMouseMotion events: .. tabs:: .. code-tab:: gdscript GDScript var drawing = false func _gui_input(event): if event is InputEventMouseButton: # Mouse click/unclick - start/stop drawing. drawing = not drawing if event is InputEventMouseMotion and drawing: # Calculate rect to center our drawn rectangle on mouse position # instead of mouse at top left. var rect = Rect2(event.position.x - 10, event.position.y - 10, 20, 20) texture.blit_rect(rect, null) .. code-tab:: csharp public bool drawing = false; public override void _GuiInput(InputEvent @event) { if (@event is InputEventMouseButton) { // Mouse click/unclick - start/stop drawing. drawing = !drawing; } if (@event is InputEventMouseMotion eventMouseMotion && drawing) { // Calculate rect to center our drawn rectangle on mouse position // instead of mouse at top left. var rect = new Rect2I( (int)(eventMouseMotion.Position.X - 10), (int)(eventMouseMotion.Position.Y - 10), 20, 20); _texture.BlitRect(rect, null); } } This should now draw black squares as you click and drag around the TextureRect. For more natural drawing, we probably want to be drawing a circle shape, and actually coloring it. We can adjust what's being drawn by using a Texture to copy from, and the modulate parameter. We will use this :download:`plain white circle texture `, which we load as the ``texture`` parameter in ``blit_rect()``, and use a red color as the ``modulate`` parameter. .. tabs:: .. code-tab:: gdscript if event is InputEventMouseMotion and drawing: # Calculate rect to center our drawn rectangle on mouse position # instead of mouse at top left. var rect = Rect2(event.position.x - 10, event.position.y - 10, 20, 20) texture.blit_rect(rect, preload("res://circle.svg"), Color.RED) .. code-tab:: csharp if (@event is InputEventMouseMotion eventMouseMotion && drawing) { // Calculate rect to center our drawn rectangle on mouse position // instead of mouse at top left. var rect = new Rect2I( (int)(eventMouseMotion.Position.X - 10), (int)(eventMouseMotion.Position.Y - 10), 20, 20); _texture.BlitRect(rect, GD.Load("res://circle.svg"), Colors.Red); } The drawing now looks much more natural and colorful. To further customize this, you could connect a :ref:`class_ColorPickerButton` node to the script to store the user's color choice for the ``modulate`` parameter of ``blit_rect()``. You could also store a brush size variable, give the user a way to adjust it, and incorporate it into the rectangle calculation so the user can draw larger or smaller strokes. .. tabs:: .. code-tab:: gdscript GDScript var drawing = false var my_color = Color.RED var my_size = 20.0 func _gui_input(event): if event is InputEventMouseButton: # Mouse click/unclick - start/stop drawing. drawing = not drawing if event is InputEventMouseMotion and drawing: # Calculate rect to center our drawn rectangle on mouse position # instead of mouse at top left. var rect = Rect2(event.position.x - my_size / 2, event.position.y - my_size / 2, my_size, my_size) texture.blit_rect(rect, preload("res://circle.svg"), my_color) func _on_color_picker_button_color_changed(color): my_color = color func _on_h_slider_value_changed(value): my_size = value .. code-tab:: csharp private bool _drawing = false; private Color _myColor = Colors.Red; private int _mySize = 20; public override void _GuiInput(InputEvent @event) { if (@event is InputEventMouseButton) { // Mouse click/unclick - start/stop drawing. _drawing = !_drawing; } if (@event is InputEventMouseMotion eventMouseMotion && _drawing) { // Calculate rect to center our drawn rectangle on mouse position // instead of mouse at top left. var rect = new Rect2I( (int)(eventMouseMotion.Position.X - _mySize / 2), (int)(eventMouseMotion.Position.Y - _mySize / 2), _mySize, _mySize); _texture.BlitRect(rect, GD.Load("res://circle.svg"), _myColor); } } public void OnColorPickerButtonColorChanged(Color color) { _myColor = color; } public void OnHSliderValueChanged(float value) { _mySize = (int)value; }