Skip to content

Commit

Permalink
Fix build and articulated line
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenCiao committed Sep 23, 2023
1 parent 4058453 commit 1b7e990
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 31 deletions.
1 change: 1 addition & 0 deletions docs/About/About.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ I'm eager to connect with individuals who share these interests.
[GP dev]: https://devtalk.blender.org/t/add-stamp-brush-and-fix-airbrush/30884/3
[Liyi bio]: https://www.liyiwei.org/personal/bio.html
[Ciallo]: https://github.com/ShenCiao/Ciallo
[The Book of Shader]: https://thebookofshaders.com/
17 changes: 9 additions & 8 deletions docs/Introduction/Introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ But time has changed, now we have modern GPU hardware crafted for graphics and p
and directly accessing a GPU framebuffer from a CPU can significantly hurt the performance.
So old algorithms may not satisfy your needs for real-time rendering.

In this tutorial, you will learn about the stroke rendering algorithms designed for the GPU graphics pipeline.
In this tutorial, you will learn about the brush stroke rendering (or brush rendering, stroke rendering for abbreviation)
algorithms designed for the GPU graphics pipeline.
We (I and my mentor [Liyi-Wei](https://www.liyiwei.org/)) name these algorithms as _Articulated_ in our paper (mainly because they look like drawing an articulated arm).
I assume our readers are already familiar with a graphics API like OpenGL or D3D.
This tutorial will concentrate more on the high-level algorithms than the implementation details.

Although graphics APIs provide us line primitives, including `LINES`, `LINE_STRIP`, and `LINE_LOOP`,
there are several well-known issues when using these primitives directly.
Check out Matt Deslauries' article [Drawing Lines is Hard](https://mattdesl.svbtle.com/drawing-lines-is-hard#line-primitives_1) if you know nothing about them.
As for us, the most significant issue is the limitation on the maximum line width or stroke radius.
Check out Matt Deslauries' article [_Drawing Lines is Hard_](https://mattdesl.svbtle.com/drawing-lines-is-hard#line-primitives_1) if you know nothing about them.
As for the brush rendering, the most significant issue is the limitation on the maximum line width or stroke radius.
We must be able to fully control the radius values when rendering brush strokes.

## Brush strokes
Expand All @@ -50,17 +51,17 @@ The radii are typically generated from the pressure values as a stylus presses a
For experienced artists after installing a painting program, one of the highest priorities is to configure the mapping function from pen pressure to brush radius.

In this tutorial, you will learn to render a stroke with mutable radius, and the most popular way to stylize it called "Stamp."
You cannot find them elsewhere since they come from our own research.
More than 90 percent of brushes in popular paint software are the stamp brushes.
Additionally, GPU brush stroke rendering a newly emerged topic.
Researchers will develop more novel methods in the future.
So I will continuously update this tutorial series to teach them.
Make sure to star our [code repository] for easy access to the latest updates.

## Vector curves

Mutable radius is imperative for the most artists when working on digital painting,
Mutable radius is imperative for the most artists working on digital painting,
but it's not defined in public vector standards like SVG.
This limitation is one of the primary reasons why lots of digital artists don't use vector workflow.
This limitation is one of the primary reasons that lots of digital artists don't use vector workflow.
(Another one is filling color.)

To support the mutable radius, we will render a unique type of vector curve:
Expand All @@ -76,7 +77,7 @@ Although the algorithms are very straightforward, I know how hard it could be to
That's why I created this tutorial, designed with a smooth learning curve and providing seamless coding environments.

You should start with the Basic part, which covers the basics of the rendering methods.
Read the articles in the Basic part in its original order, or you may miss something important.
Remember to read the articles in the Basic part in its original order, or you may miss something important.
Next, select your favorite topics to learn.
I will list the extra prerequisites at the very beginning of each article.

Expand Down Expand Up @@ -105,7 +106,7 @@ Wish you happy learning!

:::note Research Tip

To demonstrate your research work, select vector drawings have mutable radius or pen pressure data.
To demonstrate your research work about brush rendering, select vector drawings have mutable radius or pen pressure data.
Regular vector drawing datasets don't contain them.

- Zeyu Wang's work: [Paper](https://dl.acm.org/doi/10.1145/3450626.3459819) | [Dataset](https://github.com/zachzeyuwang/tracing-vs-freehand)
Expand Down
2 changes: 1 addition & 1 deletion docs/toc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you like this tutorial, please star the [code repository] instead of bookmark
## Table of Contents

- [Introduction](./introduction)
- [Basics](/Basics)
- Basics
- [Vanilla](./Basics/Vanilla)
- Vanilla with mutable radius
- Stamp
Expand Down
91 changes: 69 additions & 22 deletions src/components/ArticulatedLine2D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ export function ArticulatedLine2D({ uniforms = null }) {
});
renderer.setClearColor(new THREE.Color(1.0, 1.0, 1.0), 0.0);
renderer.setSize(canvasWidth, canvasHeight);
window.addEventListener("resize", () => {
function resizeRenderer(){
const canvasWidth = canvasContainerRef.current.clientWidth;
const canvasHeight = canvasWidth * (0.5 / gr);
renderer.setSize(canvasWidth, canvasHeight); // Update size
});
const canvasHeight = canvasWidth * 0.5 / gr;
renderer.setSize(canvasWidth, canvasHeight);
}
window.addEventListener("resize", resizeRenderer);
canvasContainerRef.current.appendChild(renderer.domElement);

const scene = new THREE.Scene();
Expand All @@ -65,6 +66,9 @@ export function ArticulatedLine2D({ uniforms = null }) {
controls.addEventListener("change", () => {
renderer.render(scene, camera);
});
renderSceneFnRef.current = () => renderer.render(scene, camera);
// @ts-ignore
window.addEventListener("TextureLoaded", renderSceneFnRef.current);

const trapezoidGeometry = new THREE.BufferGeometry();
const indices = [0, 1, 2, 2, 3, 0];
Expand Down Expand Up @@ -103,13 +107,16 @@ export function ArticulatedLine2D({ uniforms = null }) {
);
meshRef.current.frustumCulled = false;
scene.add(meshRef.current);

renderSceneFnRef.current = () => controls.dispatchEvent({ type: "change" });
renderSceneFnRef.current();

console.log("yes");

return () => {
renderer.dispose();
// canvasContainerRef.current.removeChild(renderer.domElement);
window.removeEventListener("resize", resizeRenderer);
// @ts-ignore
window.removeEventListener("TextureLoaded", renderSceneFnRef.current);
console.log("unmounted");
};
}, []);

Expand All @@ -122,6 +129,37 @@ export function ArticulatedLine2D({ uniforms = null }) {
const position1 = [...position.slice(2)];
const radius0 = [...radius];
const radius1 = [...radius.slice(1)];

const lengthRatio: number[] = [];
let currLengthRatio = 0.0;
for (let i = 0; i < radius.length - 1; ++i) {
const stride = 2 * i;
const p0 = new THREE.Vector2(position[stride], position[stride + 1]);
const p1 = new THREE.Vector2(position[stride + 2], position[stride + 3]);
let r0 = radius[i];
let r1 = radius[i + 1];

// When radius is zero index comes to infinity, which is avoided here.
const tolerance = 1e-5;
if (r0 <= 0 || r0 / r1 < tolerance) {
r0 = tolerance * r1;
radius0[i] = r0;
}
if (r1 <= 0 || r1 / r0 < tolerance) {
r1 = tolerance * r0;
radius1[i] = r1;
}

let l = p0.distanceTo(p1);

if (r0 <= 0.0 && r1 <= 0.0) currLengthRatio += 0.0;
else if (r0 == r1) currLengthRatio += l / r0;
else currLengthRatio += (Math.log(r0 / r1) / (r0 - r1)) * l;
lengthRatio.push(currLengthRatio);
}
const lengthRatio0 = [0.0, ...lengthRatio];
const lengthRatio1 = [...lengthRatio];

geometry.setAttribute(
"position0",
new THREE.InstancedBufferAttribute(new Float32Array(position0), 2),
Expand All @@ -138,7 +176,14 @@ export function ArticulatedLine2D({ uniforms = null }) {
"radius1",
new THREE.InstancedBufferAttribute(new Float32Array(radius1), 1),
);
//TODO: Compute the length ratio
geometry.setAttribute(
"summedLength0",
new THREE.InstancedBufferAttribute(new Float32Array(lengthRatio0), 1),
);
geometry.setAttribute(
"summedLength1",
new THREE.InstancedBufferAttribute(new Float32Array(lengthRatio1), 1),
);
}

function updateMaterial(vert: string, frag: string) {
Expand Down Expand Up @@ -193,7 +238,7 @@ export function ArticulatedLine2D({ uniforms = null }) {
[],
);

const editorHeight = "80vh";
const editorHeight = "60vh";

return (
<>
Expand Down Expand Up @@ -234,22 +279,24 @@ export function ArticulatedLine2D({ uniforms = null }) {
);
}

import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

let pencilBrushTexture = new THREE.Texture();
if(ExecutionEnvironment.canUseDOM){
pencilBrushTexture = new THREE.TextureLoader().load(
`/${docusaurusConfig.projectName}/img/stamp2.png`,
(texture) => {
window.dispatchEvent(new CustomEvent("TextureLoaded"));
},
undefined,
undefined,
)
}

export const pencilBrushUniforms = {
type: { value: BrushType.Stamp },
color: { value: [0.0, 0.0, 0.0, 1.0] },
footprint: {
type: 't',
value: new THREE.TextureLoader().load(
`/${docusaurusConfig.projectName}/img/stamp2.png`,
(texture) => {
console.log("Texture is loaded");
},
undefined,
(e: Event) => {
console.log("Texture Load Error: " + e.target);
},
),
},
footprint: { value: pencilBrushTexture },
stampIntervalRatio: { value: 0.4 },
noiseFactor: { value: 1.2 },
rotationFactor: { value: 0.75 },
Expand Down

0 comments on commit 1b7e990

Please sign in to comment.