How to Create Dynamic Bezier‑Curve Motion and Blur Effects in WebGL Shaders
This article explains how to implement a dynamic video‑clip effect using Bezier‑curve‑based position, rotation, easing functions, and motion blur in a WebGL shader for Tencent’s Weijian mini‑program, detailing the mathematics, shader code, and practical steps to achieve smooth, natural animations.
The article introduces a short‑video editing plugin called "Tencent Weijian" and focuses on implementing a popular "beat‑point" (卡点) effect where an image follows a curved path, rotates along the tangent, and applies motion blur for a more natural look.
1. Effect Decomposition
By slowing down the video, the author shows that the image enters the screen along a curve from the top‑left corner to the center, with motion blur simulating fast entry and deceleration, plus an elastic bounce.
2. Moving Along a Bezier Curve
The trajectory is expressed with a cubic Bezier curve. The control points are chosen to fit the desired path, and the y‑axis is inverted to match WebGL coordinates.
WebGL control points used:
p0 = vec2(0.4, 0.2);
p1 = vec2(0.5, 0.303);
p2 = vec2(0.5, 0.362);
p3 = vec2(0.5, 0.5);The shader includes a Bezier evaluation function:
float Bezier(float p0, float p1, float p2, float p3, float t) {
float x0 = p0 * pow((1.0 - t), 3.0);
float x1 = 3.0 * p1 * t * pow((1.0 - t), 2.0);
float x2 = 3.0 * p2 * pow(t, 2.0) * (1.0 - t);
float x3 = p3 * pow(t, 3.0);
return x0 + x1 + x2 + x3;
}
vec2 getBezierPoint(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float progress) {
return vec2(
Bezier(p0.x, p1.x, p2.x, p3.x, progress),
Bezier(p0.y, p1.y, p2.y, p3.y, progress)
);
}Using the current animation progress, the shader computes the image's position and samples the texture at that point.
uniform float progress;
uniform sampler2D inputImageTexture;
vec4 getColor(vec2 position) {
// scalarRatio is the image scaling factor
position = (position - vec2(0.5, 0.5)) / scalarRatio + vec2(0.5, 0.5);
return texture2D(inputImageTexture, position);
}
void main() {
vec2 p0 = vec2(0.45, 0.2);
vec2 p1 = vec2(0.5, 0.303);
vec2 p2 = vec2(0.5, 0.5);
vec2 p3 = vec2(0.5, 0.5);
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, progress);
vec2 distance = currentPos - vec2(0.5, 0.5);
vec2 pos = textureCoordinate.xy - distance;
gl_FragColor = getColor(pos);
}3. Adding Rotation via the Tangent
To avoid a stiff straight‑line motion, the tangent of the Bezier curve is computed and used to rotate the image accordingly.
vec2 computeBezierDerivative(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float progress) {
p0 = 3.0 * (p1 - p0);
p1 = 3.0 * (p2 - p1);
p2 = 3.0 * (p3 - p2);
return p0 * (1.0 - progress) * (1.0 - progress)
+ 2.0 * p1 * (1.0 - progress) * progress
+ p2 * progress * progress;
}
vec2 getRotate(vec2 pos, float angle) {
float s = sin(angle);
float c = cos(angle);
vec2 center = vec2(0.5, 0.5);
mat2 rotMat = mat2(c, -s, s, c);
pos = pos - center;
pos = rotMat * pos;
pos = pos + center;
return pos;
}
void main() {
// ... same control points as before
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, progress);
vec2 distance = currentPos - vec2(0.5, 0.5);
vec2 pos = textureCoordinate.xy - distance;
vec2 dir = computeBezierDerivative(p0, p1, p2, p3, progress);
float angle = asin(dir.x / dir.y);
pos = getRotate(pos, -angle);
gl_FragColor = getColor(pos);
}4. Applying Easing for More Natural Motion
Uniform speed looks mechanical, so an easing function (elastic out) is applied to the progress value.
float easeOutElastic(float progress) {
float c4 = (2.0 * 3.1415926) / 3.0;
return progress == 0.0 ? 0.0
: progress == 1.0 ? 1.0
: pow(2.0, -10.0 * progress) * sin((progress * 10.0 - 0.75) * c4) + 1.0;
}
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, easeOutElastic(progress));5. Dynamic Motion Blur
Motion blur enhances the perception of speed. The shader samples multiple points along the velocity direction and averages them.
vec4 motionBlur(vec2 velocity, vec2 position) {
int kernelSize = 20;
int MAX_KERNEL_SIZE = 2048;
vec4 color = getColor(position);
if (kernelSize == 0) return color;
velocity = velocity / filterArea.xy;
float offset = -0.5 / length(velocity);
int k = kernelSize - 1;
for (int i = 0; i < MAX_KERNEL_SIZE - 1; i++) {
if (i == k) break;
vec2 bias = velocity * (float(i) / float(k) + offset);
color += getColor(position + bias);
}
return color / float(kernelSize);
}
void main() {
// ... after computing pos
float vy = 40.0 * progress;
vec4 color = motionBlur(vec2(0.0, vy), pos);
gl_FragColor = color;
}6. Final Result
Combining Bezier‑based translation, tangent‑based rotation, elastic easing, and motion blur yields a smooth, rhythmic animation that can be paired with music to create stylish beat‑point videos. The same technique can be extended to other effects such as the "swing‑back" style seen in many short‑video platforms.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
