Skip to content

Commit

Permalink
Ratio interval half way 2.
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenCiao committed Aug 30, 2024
1 parent f47c8e0 commit 55c4a78
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 17 deletions.
14 changes: 7 additions & 7 deletions docs/Basics/Basics/Basics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The vanilla and stamp strokes share the exact same vertex placement method.
For your better understanding, avoid jumping right into the stamp part.

Maybe the stamp strokes with various styles don't interest you, feel free to ignore it and learn the vanilla stroke only.
Knowing how to render a line is very handy when drawing UIs or debugging your 3D scenes,
Knowing how to render a line is very handy when drawing GUIs or debugging your 3D scenes,
and our method may be one of the simplest and most elegant ways of line rendering.

While you may recognize a brush stroke by its stylization, another crucial property could be ignored:
Expand All @@ -38,8 +38,8 @@ The one with variable width on the right has more expressive appearance.
</figcaption>

The width values are typically generated from the pressure values as a stylus presses and moves on a tablet.
After the artists install a new painting program,
one of the highest priorities is to configure the mapping function from pen pressure to brush radius.
After the artists install a new paint program,
one of the highest priorities is to configure the mapping function from pen pressure to stroke width.

<details>
<summary>In case you don't know about tablets and styluses</summary>
Expand All @@ -56,8 +56,8 @@ If you're unfamiliar with tablets and styluses, you can watch the video below fo
To store the variable radius in brush stroke, we will render an uncommon type of vector curve:
An ordered list of points (polyline) with radius values assigned to each point.
As a user presses a stylus on a tablet and moves, a paint program generates a sequence of points to record the trace of movement.
Meanwhile, the pen pressure is transformed into the radius value assigned to each point.
After rendering the stroke on the polyline, the user feels like drawing on canvas.
Meanwhile, the pen pressure is transformed into the stroke width value assigned to each point.
After rendering the stroke on the polyline, the user feels like drawing on a canvas.

![Monkey](./monkey.png)

Expand All @@ -66,7 +66,7 @@ The monkey Suzanne in Blender (Grease Pencil), the orange dots on the right side
</figcaption>

We can approximate any type of curve by increasing the number of points in a polyline, whether freehand-drawn or mathematically defined.
Try to change the `maxRadius` and `segmentCount` values in the code editor below to see how the vanilla stroke changes.
Try to change the `maxRadius` (radius is half width) and `segmentCount` values in the code editor below to see how the vanilla stroke changes.
I will elaborate on how to render this stroke in the next section.
Feel free to change any other parts of the code as long as the function returns the `position` and `radius` array correctly.

Expand All @@ -76,4 +76,4 @@ import { ArticulatedLine2D } from "@site/src/components/ArticulatedLine2D";

Blender Grease Pencil team has developed many novel tools to edit polylines.
I recommend that everyone researching digital painting techniques learn about them.
https://www.youtube.com/watch?v=nZyB30-xZFs
Here is a tutorial video on how to use blender grease pencil: https://www.youtube.com/watch?v=nZyB30-xZFs
12 changes: 6 additions & 6 deletions docs/Basics/Stamp/Stamp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Therefore, we will focus on the most critical technique, how to place the footpr

A naive solution is to place a footprint at each vertex.
![naive](./dotted-monkey.png)
_Place a dot texture at each vertex._
<figcaption> Place a dot texture at each vertex. </figcaption>

But it's not good enough for the most usages.
The rendering result of strokes depends on polylines' vertex density.
Expand Down Expand Up @@ -79,7 +79,7 @@ Given the pixel $p$, we can get this segment by solving the geometry shown in th

I label the segment can cover the pixel $p$ with the thicker black line.
The two dashed circles intersect at $p$.
I label their centers with vertical line ticks, which are the start and end points of the segment.
Their centers are labeled with vertical line ticks, which are the start and end points of the segment.
The centers are the farthest points where a stamp placed can cover the pixel $p$.
Any stamps outside the segment have no chance to affect the pixel's color.

Expand All @@ -100,9 +100,9 @@ $$

It's not hard to derive $r(x)$ from the figure below.
$$
r(x) = \frac{x}{l}r_1 + \left(1-\frac{x}{l}\right)r_0
r(x) = \frac{x}{L}r_1 + \left(1-\frac{x}{L}\right)r_0
$$
$l$ is the length of the current edge.
$L$ is the length of the current edge.
![xradius](./stamp-xradius.png)
By replacing $r(x)$, we get a quadratic equation $ax^2 + bx + c = 0$:

Expand All @@ -112,7 +112,7 @@ b = - 2 * (r_0\cos\theta + x_p);
c = x_p^2 + y_p^2 - r_0^2
$$

Remind that $\cos\theta = (r_0 - r_1)/l$.
Remind that $\cos\theta = (r_0 - r_1)/L$.
Applying the formula for solving quadratic equations,
two roots of the equation are the X value of min and max points of the segment.
Therefore, we know the range in the fragment shader and pixels only loop through stamps that can cover it.
Expand All @@ -129,7 +129,7 @@ we set RGB values with users' brush setting and sample alpha values from a monoc
whose pixel's gray scale determines the opacity.
Here is the pseudocode in the fragment shader:

```
``` glsl
uniform vec3 RGB; // Users' brush setting
void main(){
float A = 0.0;
Expand Down
Binary file removed docs/Proportional-Interval-Stamp/Artboard 1.png
Binary file not shown.
52 changes: 48 additions & 4 deletions docs/Proportional-Interval-Stamp/Proportional-Interval-Stamp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,22 @@ This content is under construction and not peer-reviewed. Learn it under your ow

</details>


<figcaption>

© 2024 Shen Ciao. All rights reserved on this webpage.

Unauthorized use and/or duplication of this material without express and written permission from this site’s author and/or owner is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Shen Ciao with appropriate and specific direction to the original content.

</figcaption>

:::

## Introduction to stamp patterns

### Fixed interval
In the [Stamp](../Basics/Stamp) section, I introduced stamp brush and methods to render it.
In the [Stamp](../Basics/Stamp) section, we learned stamp brush and methods to render it.
We assumed the interval between stamp is a fixed value along a stroke.

> While a user paints on a canvas, we render the texture onto the canvas **equidistantly** along the drawing trace.
Expand Down Expand Up @@ -121,7 +131,7 @@ I'll soon explain it with precise mathematical formulas, so don't worry about it

![interval animation](./interval-ratio-animation.gif)
<figcaption>
Proportional interval stamp stroke animation by reducing the interval ratio $\eta$.
Proportional interval animation by reducing the interval ratio $\eta$.
</figcaption>

Proportional interval has another benefit: Strokes have more consistent appearance!
Expand All @@ -132,6 +142,40 @@ Actually, it's a [fractal](https://en.wikipedia.org/wiki/Fractal) like mandelbro

Krita supports the proportional interval.
We can find the "Auto" toggle button under the "Spacing" setting, as shown in the figure below.
When enabled, the brush draws proportional interval strokes; when disabled, it draws fixed interval stroke.
When enabled, the brush draws proportional interval strokes; when disabled, it draws fixed interval strokes.

![spacing auto setting](./krita-spacing-auto.png)

Implementing the proportional interval on the CPU is straightforward.
The interval can be computed dynamically using the current drawn footprint.
Here's the pseudocode for the process:

``` cpp
vec2 currPos = firstStampPosition;
float currRadius = firstStampRadius;
while (currPos != endPos){
drawFootprint(currPos, currRadius);
// highlight-next-line
float interval = currRadius * interval_ratio;
vec2 nextPos = addAlongThePolyline(currPos, interval);
vec2 nextRadius = radius(nextPos);
currPos = nextPos; currRadius = nextRadius;
}
```

![spacing auto setting](./krita-spacing-auto.png)
The `nextPos` depends on `currPos`, so the process cannot be GPU-accelerated.
To overcome this, we need to eliminate this dependency while maintaining the appearance of the stroke.
It's a significant challenge and will require some calculus to solve it.

## Theory
We want the interval to be proportional to radius.
Let's define this in a calculus way!

Given a polyline and pick one of its edges, cut the edge into infinite small segments with length $\Delta L$.
Each segment has its local coordinate $x$, and we know its radius $r(x) = r_0 - \cos\theta x$.
Within a small segment, define the stamp interval as $\eta r(x)$.
So the number of stamps $\Delta n$ in this segment is $\frac{\Delta L}{\eta r(x)}$.
Sum $\Delta n$ over the edge, we get the number of stamps $n(x)$ on this edge;
sum the number on all edges, we get the count of stamps on the polyline.
So we have the stamp number $n$ grows along the polyline.
Every time this $n$ value hits an integer, we stamp on that point.

0 comments on commit 55c4a78

Please sign in to comment.