-
Notifications
You must be signed in to change notification settings - Fork 304
New Sketchpane: Alchemancy Mark III
This is the proposed plan for the new Sketchpane / drawing system / painting engine / sketching mechanism, however in this doc, it'll just be referred to as the Sketchpane.
Alchemancy Mark II is the current name of the Sketchpane engine of Storyboarder. Even though, as said above, the Sketchpane would be referred to as Sketchpane, we must talk about the most important aspect of a cutting edge, next generation Sketchpane: the pretentious marketing name.
Storyboarder paints fast. Really fast. For years, Storyboarder has set the bar for performance in creative applications with Alchemancy Mark II, Storyboarder’s exceptionally fluid drawing and painting engine. So how do you improve on already industry-leading performance? Meet Alchemancy Mark III, the next-generation painting engine built with technologies like Javascript, XML, text editors, and probably some PHP. Alchemancy Mark III has been designed to extract the full power of computer to provide creative professionals with incredible color depth, fluid and accurate painting, and more performance than ever before.
If you think we stole that from the Procreate website, no we didn't. We wrote it first. There is probably something wrong with the Github date stamp.
Alchemancy, at its digitally emotional core, means the divination of your ideas into universal elixir of life and beauty. Every stroke you make is seminal. From god's gloryhole, to your storyboards, Alchemancy Mark III transforms the movement of your mortal dirty dickbeaters into a symphony of eternal color, texture, form, and feeling.
But from here on out we'll just call it a Sketchpane.
In simple terms: it's a component that holds a canvas that you can draw onto. There is one canvas inside of the Sketchpane. You can draw on the canvas, but you can't draw outside of it in the Sketchpane. You can change zoom into the canvas and pan around in the Sketchpane. You can also resize the sketchpane itself (Imagine resizing the window).
To avoid ambiguity, the canvas is a general term and shouldn't be confused with Javascript's Canvas Object.
The canvas is the document that contains the imagery. The canvas can also contain layers. The layers are stacked on top of each other in a specific order and composited according to their blend mode. https://threejs.org/examples/webgl_materials_blending.html
Each layer is an image (a bitmap, a texture). Layers should, but don't necessarily have to, be the same size as the canvas. Layer's dimensions should be in the form of pixels – not points, picas, or inches or anything dumb like that.
When you draw, you actually aren't actually drawing directly to a layer on the canvas. The first thing is we determine which layer we want to be drawing on. Then we create a new temporary layer/texture directly above that layer. As we draw the stroke, it is drawn to that temporary layer. When we lift the pointer, we are essentially ending the stroke. When this happens, we draw the temporary layer to the layer we meant to be drawing on. We clear the temporary layer.
It's very possible as you're drawing that we might want to change the way the stroke looks. Imagine you were drawing a line. You were drawing, but then the app realized you wanted a straight line, it could straighten the line you already drew. I know sounds crazy, but it's not.
First, it's important to know that a stroke is not driven directly by a pointer input. Yes, a pointer input can result in a stroke being rendered, however, the pointer input is first processed, and that processor tells the Sketchpane what to do. So imagine you were drawing like a crazy person. Crazy zig zags and such. The app may want to smooth your inputs into smoother strokes, so it doesn't look jagged when it renders.
At a super high level, a stoke can be thought of as thousands of rectangular brush textures along a line.
A stroke is collection of line segments. A line segment is a collection of brush node plots.
A stroke can also change while it's being drawn. WHAT? WHY? OK. Remember when the iPad came out, and everyone was jizzing all over themselves that there is no lag? Well, that's just impossible. There's always lag. Photoshop and Sketchbook pro have a 2 frame delay before they draw what you input, so they can smooth the line and draw it to the screen. But the iPad does something kinda smart, they predict where you will be next, and they are mostly right, unless they aren't, and it fixes itself so fast you don't even notice it. It's the fixing itself part that is dynamic.
So every frame, as the stroke is being updated, the drawing is being updated. This results in many rectangular brush textures as their own polygons, living on the screen. That's cool. But what if my line is like, REALLY REALLY REALLY LONG? Won't that get super slow? That could be like a billion nodes?
Yes - too many nodes is not cool. Those newly added nodes could still be moving around. But the older nodes, at the beginning of the line segment, they are likely static, and will not change. THEREFORE, you can draw those nodes to the temporary texture. THEREFORE, you can have a super duper long line and it's always drawing the last bits to the texture so there aren't that many dynamic brush nodes composited in real time. Pretty sweet, eh?
As you are drawing a stoke, segments are being created of nodes. A node contains information like:
Property |
---|
Position |
Pressure |
Tilt / Rotation |
External Size Parameter |
External Opacity Parameter |
Brush |
A brush is a description of how the brush node should be rendered. It's a collection of attributes. Imagine a very simple brush. It might need only two attributes: size and color. If you specify a brush that is 20px in diameter and yellow, you can draw a fat yellow line. If you specify 3px and blue, you will have a distinctly different skinny blue line.
Brushes can have many more very specific attributes, like how is the opacity of the brush affected by the tilt and pressure of the pen? There are more brush attributes listed below.
Oh, you mean Alchemancy Mark II? It kinda worked as described above. The only problem is how it was built. The layers were rendered at HTML Canvas Objects on top of each other and composited by the DOM. This is OK for small canvases on websites, but for huge canvases that are also being scaled by the DOM, this is really super slow. Also, we used HTML Canvases to render the brush strokes. This wasn't that slow, but we couldn't render brushes like we will propose below.
We did our best to profile the performance and eek out every last optimization we could. But on slower machines, drawing sucks. It's super slow. Users report "jaggies", "lag", and other non specific words that developers hate.
Most computers have a graphics card capable of doing lots of graphics specific operations very quickly and concurrently. For example, using DOM rendering, we struggle to display more than 3 layers and maintain 60 fps. Using OpenGL, we can display 10+ layers at 4K squared resolution, and not miss a single frame.
- Can layers work? 10+ 4K layers composited on top of each other with various blend types maintaining 60fps.
- Can long strokes work? Multiple (1000+) textures rendered to a renderTexture (layer) maintaining 60fps.
- Can strokes be shaders? Multiple (1000+) textures as fragment shaders rendered to a renderTexture maintaining 60fps.
I think the answers to all these questions are yes!
The Sketchpane is a GL context where all the layers are stacked in order.
Layers are renderTextures on a polygon rendered in a specific order.
Brush nodes are polygons with a universal fragment shader in which parameters are set by the definition of the brush.
Brushes have 3 different properties: Node properties, External Brush Properties, and Brush Properties.
Node properties come from the stroke information (Imagine the stylus):
Properties | Notes |
---|---|
Position X, Y | Where the node is placed in X, Y |
Pressure | Pressure of the stylus if applicable (Used by the brush definition) |
Tilt / Rotation | Tilt / Rotation of the stylus if applicable (Used by the brush definition) |
Time | Time that the node was created. Used to calculate speed. |
Speed | Calculated in by the input processor can be used by the brush definition |
External Brush Properties come from the external UI. Imagine an interface where you have distinct brushes you can use, but for each of the brushes, you can pick a color.
Properties | Notes |
---|---|
Color | Hex value definition for the base color for the brush |
Size | Diameter in pixels. However, this is passed into the brush definition only as an attribute. (The size can change i.e. pressure.) |
Opacity | Float from 0-1. However, this is passed into the brush definition only as an attribute. (The size can change i.e. pressure.) |
The following properties define a brush preset. This is where the magic happens.
At its simplest, a brush has a grayscale image that is used as an alpha for the brush, and a grayscale image that is used as an alpha for the grain texture.
So when a brush node is drawn, a node class loads the brush properties and figures out where the polygon should be placed, the size, the rotation, and shader is started for the polygon, and all the attributes are passed into the shader as properties.
It takes the color property uses it as the base of the image. It uses the brush image as an alpha channel of the brush. The alpha is also multiplies by the grain image. Based on the location of the brush node, the grain image is offset, so that as the nodes are being drawn, it appears that the grain texture is that of the canvas, and not of the brush. (It's an illusion.) I wrote a simple shader: https://www.shadertoy.com/view/4tjyRK
Property | Value | Default | Notes |
---|---|---|---|
Brush Image | Texture2D (Grayscale, Square) | Circle | Used as the alpha channel of the brush |
Brush Image Rotation | 0,90,180,270 | 0 | Can rotate the image for the shader |
Brush Image Invert | Boolean | False | Invert the values |
Grain Image | Texture2D (Grayscale, Square, Repeating) | Solid | Used as the alpha channel of the paper grain |
Grain Image Rotation | 0,90,180,270 | 0 | Can rotate the image for the shader |
Grain Image Invert | Boolean | False | Invert the values |
Property | Value | Default | Notes |
---|---|---|---|
Spacing | [0-100%] | 5% | 0 is very close together, 100 is spaced out |
Smoothing | [0-100%] | 0% | 0 is no smoothing, 100 is delayed smoothing |
Jitter | [0-100%] | 0% | 0 is right on line, 100% is randomly scattered |
Falloff | [0-100%] | 0% | 0 is totally solid, 100% fades out the line quickly |
Stroke Taper Start | [0-100%] | 0% | % of the line where the start taper is affected |
Stroke Taper End | [0-100%] | 100% | % of the line where the end taper is affected |
Stroke Taper Opacity | [0-100%] | 0% | % the opacity is decreased for the taper |
Stroke Taper Size | [0-100%] | 100% | % the size is for the taper |
Property | Value | Default | Notes |
---|---|---|---|
Scatter | [0-100%] | 0% | Scatter of the grain |
Rotation | [-100-100%] | 0% | Rotation of the alpha shape |
Azimuth | Boolean | False | Change the size and rotation based on the angle of the stylus |
Property | Value | Default | Notes |
---|---|---|---|
Movement | [0-100%] | 100% | % the grain is offset as the brush moves. 0 static. 100 rolling. 100 is like paper. |
Scale | [0-100%] | 30% | Scale of the grain texture. 0 super tiny, 100 super zoomed. |
Zoom | [0-100%] | 0% | % Scale of the grain texture by the brush size. |
Rotation | [-100-100%] | 0% | % Rotation grain rotation is multiplied by rotation |
Filtered | Boolean | True | Is the texture antialiased or nearest neighbor? |
Property | Value | Default | Notes |
---|---|---|---|
Rendering | Normal, Glazed, Wet Mix | Normal | TBD |
Opacity from speed | [-100% - 100%] | 0% | How much opacity is affected based on speed |
Opacity Jitter | [0 - 100%] | 0% | Randomness of opacity |
Size from Speed | [-100% - 100%] | 0% | How much size is affected based on speed |
Size Jitter | [0 - 100%] | 0% | Randomness of size |
Property | Value | Default | Notes |
---|---|---|---|
Pressure Opacity | [-100% - 100%] | 0% | % Pressure affects opacity |
Pressure Size | [-100% - 100%] | 0% | % Pressure affects size |
Tilt Angle | [0-90] | 0 | Degrees angle altered by the tilt |
Tilt Opacity | [0-100%] | 0% | % opacity altered by the tilt |
Tilt Gradiation | [0-100%] | 0% | How it changes as tilt happens, curve compression |
Tilt Size | [0-100%] | 0% | % size altered by the tilt |
Property | Value | Default | Notes |
---|---|---|---|
Name | String | '' | Name of the brush preset |
Blend Mode | [blend mode] | Copy | https://threejs.org/examples/webgl_materials_blending.html |
Size Limit Max | [0-100%] | 100% | Max brush size settable in UI |
Size Limit Min | [0-100%] | 0% | Min brush size settable in UI |
Opacity Limit Max | [0-100%] | 100% | Max brush opacity settable in UI |
Opacity Limit Min | [0-100%] | 0% | Min brush opacity settable in UI |
Default Layer | Int | 0 | Does brush draw to own layer? |
©2020 Wonder Unit, Inc. https://wonderunit.com
Storyboarder Help
What is Storyboarder?
Previously Answered Questions
Layers in Storyboarder
Shot Generator
How to use Shot Generator
VR Help
Creating a scene in VR
Creating custom 3D Models for Shot Generator
Creating custom Emotions for Characters in Shot Generator
Development Plan
v1.18 Plan
About Wonder Unit
About Wonder Unit