mirror of
https://github.com/scratchfoundation/scratch-render.git
synced 2025-07-22 20:18:47 -04:00
Merge pull request #582 from LLK/revert-580-revert-559-revert-438-pen-shader
Revert "Revert "Revert "Draw pen lines via fragment shader"""
This commit is contained in:
commit
58bd9cbe19
4 changed files with 130 additions and 94 deletions
117
src/PenSkin.js
117
src/PenSkin.js
|
@ -44,6 +44,11 @@ const __projectionMatrix = twgl.m4.identity();
|
|||
*/
|
||||
const __modelTranslationMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for rotation matrix for building a model matrix.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelRotationMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for scaling matrix for building a model matrix.
|
||||
|
@ -130,7 +135,7 @@ class PenSkin extends Skin {
|
|||
this._stampShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default, NO_EFFECTS);
|
||||
|
||||
/** @type {twgl.ProgramInfo} */
|
||||
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.line, NO_EFFECTS);
|
||||
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.lineSample, NO_EFFECTS);
|
||||
|
||||
this._createLineGeometry();
|
||||
|
||||
|
@ -216,15 +221,10 @@ class PenSkin extends Skin {
|
|||
* @param {number} y1 - the Y coordinate of the end of the line.
|
||||
*/
|
||||
drawLine (penAttributes, x0, y0, x1, y1) {
|
||||
// For compatibility with Scratch 2.0, offset pen lines of width 1 and 3 so they're pixel-aligned.
|
||||
// See https://github.com/LLK/scratch-render/pull/314
|
||||
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
|
||||
const offset = (diameter === 1 || diameter === 3) ? 0.5 : 0;
|
||||
|
||||
this._drawLineOnBuffer(
|
||||
penAttributes,
|
||||
x0 + offset, y0 + offset,
|
||||
x1 + offset, y1 + offset
|
||||
this._rotationCenter[0] + x0, this._rotationCenter[1] - y0,
|
||||
this._rotationCenter[0] + x1, this._rotationCenter[1] - y1
|
||||
);
|
||||
|
||||
this._silhouetteDirty = true;
|
||||
|
@ -234,16 +234,72 @@ class PenSkin extends Skin {
|
|||
* Create 2D geometry for drawing lines to a framebuffer.
|
||||
*/
|
||||
_createLineGeometry () {
|
||||
// Create a set of triangulated quads that break up a line into 3 parts:
|
||||
// 2 caps and a body. The y component of these position vertices are
|
||||
// divided to bring a value of 1 down to 0.5 to 0. The large y values
|
||||
// are set so they will still be at least 0.5 after division. The
|
||||
// divisor is scaled based on the length of the line and the lines
|
||||
// width.
|
||||
//
|
||||
// Texture coordinates are based on a "generated" texture whose general
|
||||
// shape is a circle. The line caps set their texture values to define
|
||||
// there roundedness with the texture. The body has all of its texture
|
||||
// values set to the center of the texture so it's a solid block.
|
||||
const quads = {
|
||||
a_position: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
-0.5, 1,
|
||||
0.5, 1,
|
||||
-0.5, 100000,
|
||||
|
||||
-0.5, 100000,
|
||||
0.5, 1,
|
||||
0.5, 100000,
|
||||
|
||||
-0.5, 1,
|
||||
0.5, 1,
|
||||
-0.5, -1,
|
||||
|
||||
-0.5, -1,
|
||||
0.5, 1,
|
||||
0.5, -1,
|
||||
|
||||
-0.5, -100000,
|
||||
0.5, -100000,
|
||||
-0.5, -1,
|
||||
|
||||
-0.5, -1,
|
||||
0.5, -100000,
|
||||
0.5, -1
|
||||
]
|
||||
},
|
||||
a_texCoord: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
1, 0.5,
|
||||
0, 0.5,
|
||||
1, 0,
|
||||
|
||||
1, 0,
|
||||
0, 0.5,
|
||||
0, 0,
|
||||
|
||||
0.5, 0,
|
||||
0.5, 1,
|
||||
0.5, 0,
|
||||
|
||||
0.5, 0,
|
||||
0.5, 1,
|
||||
0.5, 1,
|
||||
|
||||
1, 0,
|
||||
0, 0,
|
||||
1, 1,
|
||||
1, 1,
|
||||
1, 0.5,
|
||||
|
||||
1, 0.5,
|
||||
0, 0,
|
||||
0, 1
|
||||
0, 0.5
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -288,8 +344,6 @@ class PenSkin extends Skin {
|
|||
|
||||
/**
|
||||
* Draw a line on the framebuffer.
|
||||
* Note that the point coordinates are in the following coordinate space:
|
||||
* +y is down, (0, 0) is the center, and the coords range from (-width / 2, -height / 2) to (height / 2, width / 2).
|
||||
* @param {PenAttributes} penAttributes - how the line should be drawn.
|
||||
* @param {number} x0 - the X coordinate of the beginning of the line.
|
||||
* @param {number} y0 - the Y coordinate of the beginning of the line.
|
||||
|
@ -303,6 +357,26 @@ class PenSkin extends Skin {
|
|||
|
||||
this._renderer.enterDrawRegion(this._lineOnBufferDrawRegionId);
|
||||
|
||||
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
|
||||
const length = Math.hypot(Math.abs(x1 - x0) - 0.001, Math.abs(y1 - y0) - 0.001);
|
||||
const avgX = (x0 + x1) / 2;
|
||||
const avgY = (y0 + y1) / 2;
|
||||
const theta = Math.atan2(y0 - y1, x0 - x1);
|
||||
const alias = 1;
|
||||
|
||||
// The line needs a bit of aliasing to look smooth. Add a small offset
|
||||
// and a small size boost to scaling to give a section to alias.
|
||||
const translationVector = __modelTranslationVector;
|
||||
translationVector[0] = avgX - (alias / 2);
|
||||
translationVector[1] = avgY + (alias / 4);
|
||||
|
||||
const scalingVector = __modelScalingVector;
|
||||
scalingVector[0] = diameter + alias;
|
||||
scalingVector[1] = length + diameter - (alias / 2);
|
||||
|
||||
const radius = diameter / 2;
|
||||
const yScalar = (0.50001 - (radius / (length + diameter)));
|
||||
|
||||
// Premultiply pen color by pen transparency
|
||||
const penColor = penAttributes.color4f || DefaultPenAttributes.color4f;
|
||||
__premultipliedColor[0] = penColor[0] * penColor[3];
|
||||
|
@ -311,10 +385,19 @@ class PenSkin extends Skin {
|
|||
__premultipliedColor[3] = penColor[3];
|
||||
|
||||
const uniforms = {
|
||||
u_lineColor: __premultipliedColor,
|
||||
u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter,
|
||||
u_penPoints: [x0, -y0, x1, -y1],
|
||||
u_stageSize: this.size
|
||||
u_positionScalar: yScalar,
|
||||
u_capScale: diameter,
|
||||
u_aliasAmount: alias,
|
||||
u_modelMatrix: twgl.m4.multiply(
|
||||
twgl.m4.multiply(
|
||||
twgl.m4.translation(translationVector, __modelTranslationMatrix),
|
||||
twgl.m4.rotationZ(theta - (Math.PI / 2), __modelRotationMatrix),
|
||||
__modelMatrix
|
||||
),
|
||||
twgl.m4.scaling(scalingVector, __modelScalingMatrix),
|
||||
__modelMatrix
|
||||
),
|
||||
u_lineColor: __premultipliedColor
|
||||
};
|
||||
|
||||
twgl.setUniforms(currentShader, uniforms);
|
||||
|
|
|
@ -174,9 +174,9 @@ ShaderManager.DRAW_MODE = {
|
|||
colorMask: 'colorMask',
|
||||
|
||||
/**
|
||||
* Draw a line with caps.
|
||||
* Sample a "texture" to draw a line with caps.
|
||||
*/
|
||||
line: 'line'
|
||||
lineSample: 'lineSample'
|
||||
};
|
||||
|
||||
module.exports = ShaderManager;
|
||||
|
|
|
@ -33,11 +33,11 @@ uniform float u_mosaic;
|
|||
uniform float u_ghost;
|
||||
#endif // ENABLE_ghost
|
||||
|
||||
#ifdef DRAW_MODE_line
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
uniform vec4 u_lineColor;
|
||||
uniform float u_lineThickness;
|
||||
uniform vec4 u_penPoints;
|
||||
#endif // DRAW_MODE_line
|
||||
uniform float u_capScale;
|
||||
uniform float u_aliasAmount;
|
||||
#endif // DRAW_MODE_lineSample
|
||||
|
||||
uniform sampler2D u_skin;
|
||||
|
||||
|
@ -109,7 +109,7 @@ const vec2 kCenter = vec2(0.5, 0.5);
|
|||
|
||||
void main()
|
||||
{
|
||||
#ifndef DRAW_MODE_line
|
||||
#ifndef DRAW_MODE_lineSample
|
||||
vec2 texcoord0 = v_texCoord;
|
||||
|
||||
#ifdef ENABLE_mosaic
|
||||
|
@ -214,27 +214,15 @@ void main()
|
|||
// Un-premultiply alpha.
|
||||
gl_FragColor.rgb /= gl_FragColor.a + epsilon;
|
||||
#endif
|
||||
|
||||
#else // DRAW_MODE_line
|
||||
// Maaaaagic antialiased-line-with-round-caps shader.
|
||||
// Adapted from Inigo Quilez' 2D distance function cheat sheet
|
||||
// https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
||||
|
||||
// The xy component of u_penPoints is the first point; the zw is the second point.
|
||||
// This is done to minimize the number of gl.uniform calls, which can add up.
|
||||
vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy;
|
||||
// Magnitude of vector projection of this fragment onto the line (both relative to the line's start point).
|
||||
// This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point.
|
||||
float projMagnitude = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
|
||||
float lineDistance = length(pa - (ba * projMagnitude));
|
||||
|
||||
// The distance to the line allows us to create lines of any thickness.
|
||||
// Instead of checking whether this fragment's distance < the line thickness,
|
||||
// utilize the distance field to get some antialiasing. Fragments far away from the line are 0,
|
||||
// fragments close to the line are 1, and fragments that are within a 1-pixel border of the line are in between.
|
||||
float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - lineDistance, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = u_lineColor * cappedLine;
|
||||
#endif // DRAW_MODE_line
|
||||
#else // DRAW_MODE_lineSample
|
||||
gl_FragColor = u_lineColor * clamp(
|
||||
// Scale the capScale a little to have an aliased region.
|
||||
(u_capScale + u_aliasAmount -
|
||||
u_capScale * 2.0 * distance(v_texCoord, vec2(0.5, 0.5))
|
||||
) / (u_aliasAmount + 1.0),
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
#endif // DRAW_MODE_lineSample
|
||||
}
|
||||
|
|
|
@ -1,57 +1,22 @@
|
|||
precision mediump float;
|
||||
|
||||
#ifdef DRAW_MODE_line
|
||||
uniform vec2 u_stageSize;
|
||||
uniform float u_lineThickness;
|
||||
uniform vec4 u_penPoints;
|
||||
|
||||
// Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
|
||||
// Smaller values can cause problems on some mobile devices.
|
||||
const float epsilon = 1e-3;
|
||||
#endif
|
||||
|
||||
#ifndef DRAW_MODE_line
|
||||
uniform mat4 u_projectionMatrix;
|
||||
uniform mat4 u_modelMatrix;
|
||||
attribute vec2 a_texCoord;
|
||||
#endif
|
||||
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
uniform float u_positionScalar;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
#ifdef DRAW_MODE_line
|
||||
// Calculate a rotated ("tight") bounding box around the two pen points.
|
||||
// Yes, we're doing this 6 times (once per vertex), but on actual GPU hardware,
|
||||
// it's still faster than doing it in JS combined with the cost of uniformMatrix4fv.
|
||||
|
||||
// Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels
|
||||
// fall within the quad, even at a 45-degree diagonal
|
||||
vec2 position = a_position;
|
||||
float expandedRadius = (u_lineThickness * 0.5) + 1.4142135623730951;
|
||||
|
||||
float lineLength = length(u_penPoints.zw - u_penPoints.xy);
|
||||
|
||||
position.x *= lineLength + (2.0 * expandedRadius);
|
||||
position.y *= 2.0 * expandedRadius;
|
||||
|
||||
// Center around first pen point
|
||||
position -= expandedRadius;
|
||||
|
||||
// Rotate quad to line angle
|
||||
vec2 normalized = (u_penPoints.zw - u_penPoints.xy + epsilon) / (lineLength + epsilon);
|
||||
position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position;
|
||||
// Translate quad
|
||||
position += u_penPoints.xy;
|
||||
|
||||
// Apply view transform
|
||||
position *= 2.0 / u_stageSize;
|
||||
|
||||
gl_Position = vec4(position, 0, 1);
|
||||
v_texCoord = position * 0.5 * u_stageSize;
|
||||
#else
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
|
||||
v_texCoord = a_texCoord;
|
||||
#endif
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
vec2 position = a_position;
|
||||
position.y = clamp(position.y * u_positionScalar, -0.5, 0.5);
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(position, 0, 1);
|
||||
#else
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
|
||||
#endif
|
||||
v_texCoord = a_texCoord;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue