#{}ogl表达式

Mmm… swirly goodness… Hey! Snap out of it! Ok. Now, in this tutorial we’re going to have some fun creating this pretty crazy little effect by tipping our toes into WebGL. The water is warm, I promise!

嗯...涡旋的善良...嘿! 振作起来! 好。 现在,在本教程中,通过将脚尖放到WebGL中,我们将获得一些有趣的效果来创建这个非常疯狂的小效果。 我保证,水很温暖!

演示地址

什么是OGL?(What’s OGL?)

OGL is a WebGL library that is going to save us having to write a bunch of pretty unfriendly WebGL code directly. It has been my baby for the last year or so – I’ve been busy adding a bunch of examples to try and give people a kick-start with practical references.

OGL是一个WebGL库,它将免去我们直接编写一堆非常不友好的WebGL代码的麻烦。 这一直是我的孩子在过去一年左右的时间-我一直忙于添加一堆的例子,试图给人以实用的参考脚踏启动。

By design, it’s tightly coupled to WebGL with only a small amount of abstraction – meaning it’s very lightweight. For example, the OGL classes required for this experiment are under 13kb gzipped – and more than half of them are just maths.

根据设计,它仅需少量抽象就可以与WebGL紧密耦合,这意味着它非常轻巧。 例如,此实验所需的OGL类的压缩量不足13kb,其中一半以上只是数学。

您还有更多的鼠标轨迹吗? (You got any more of them mouse trails?)

Yes! Moving on, sorry.

是! 继续前进,对不起。

Mouse trails (or any sort of trails) are just innately fun to interact with. The little touch of physics that it adds can really make an animation feel incredibly reactive and tangible, to the point where you can be certain that a good percentage of your users’ time will be spent playing with the effect, before consuming any of your site’s content… sorry not sorry.

鼠标轨迹(或任何种类的轨迹)与生俱来就是有趣的。 它所添加的一点物理效果实际上可以使动画令人难以置信地感觉到有形的变化,以至于可以确定,在消耗网站的任何内容之前,用户的大部分时间都将花在播放效果上内容...对不起,对不起

Of course, they can be achieved using a whole range of techniques. Example time.

当然,可以使用多种技术来实现。 时间示例。

Starting with a really clever, recent one done using the DOM (html) and CSS. I especially love the difference-blend style they’ve added to the trail. The crux of the effect is a number of diminishing circles that follow one another, giving the effect of a tapered line. Developed by Luca Mariotti. With the ink trail effect from Ricardo Mendieta.

从一个非常聪明的新手开始,最近一次使用DOM(html)和CSS。 我特别喜欢他们添加到足迹中的差异融合风格。 效果的症结是许多相互跟随的递减圆,给出了渐缩线的效果。 由Luca Mariotti开发。 借助Ricardo Mendieta的墨水效果。

Here’s an effective take on one, made using the 2D Canvas api. This one draws a circle at the head of each line every frame, while simultaneously the whole scene slowly disappears. By Hakim El Hattab.

这是使用2D Canvas API制作的一个有效的例子。 此帧每帧在每行的开头绘制一个圆圈,同时整个场景逐渐消失。 哈基姆·埃尔·哈塔塔布(Hakim El Hattab) 。

And here’s one used as a main game mechanic, made using SVG by yours truly – a few trips-around-the-sun ago… It’s a dynamic bezier curve whose start, middle and end positions are updated every frame.

这是一种用作主要游戏机制的机制,它是您真正使用SVG制作的-绕太阳转了几圈……这是一条动态的贝塞尔曲线,其起始位置,中间位置和结束位置每帧都会更新。

So each of these previous (viable) options use drawing APIs (CSS, Canvas 2D, SVG) to render those pixels. WebGL, on the other hand, leaves all of that pixel-drawing math in your capable hands. This means two things.

因此,每个上述(可行)选项都使用绘图API(CSS,Canvas 2D,SVG)来渲染这些像素。 另一方面,WebGL将所有这些绘制像素的数学运算交给了您。 这意味着两件事。

Firstly, it’s more work for you. Yep, it’ll probably take you longer to get something up and running, working out those pesky projection matrices, while keeping an eye on the optimisation of your attribute buffers…

首先,这为您带来更多工作。 是的,可能需要花费更长的时间来启动和运行某些东西,计算出那些令人讨厌的投影矩阵,同时还要注意属性缓冲区的优化……

Buuut (important but), it’s less work for the computer – so it’ll run (normally, waaaay) faster. You’re basically cutting out the middle man. It also gives you a crazy amount of flexibility, which I highly appreciate.

Buuut(很重要,但是),它对计算机的工作较少-因此它的运行速度(通常是waaaay)更快。 您基本上是在裁掉中间人。 它还给您带来了极大的灵活性,我对此深表赞赏。

该方法 (The Approach)

I’m a fan of visualising problems to understand them (let’s not talk about quaternions…), so let’s break it down.

我喜欢可视化问题以理解它们(我们不谈论四元数…),所以让我们对其进行分解。

We will start by making a number of points, made up of [x, y] coordinates, arranged in a lovely little line.

首先,我们将由[x,y]坐标组成的许多点排列成一条漂亮的小线。

Then we’ll use these to generate a geometry, duplicating each point so that there are two vertices at each step along the curve. Why two? Just a sec.

然后,我们将使用它们来生成几何图形,复制每个点,以便沿着曲线的每个步骤都有两个顶点。 为什么两个? 等一下

Now, because our paired points are at the exact same position, our line is infinitely thin when rendered – and hence, invisible… No good. What we need to do is separate those pairs to give the line some width. And, puuuush.

现在,因为我们的成对的点在完全相同的位置,所以渲染时我们的线是无限细的,因此是不可见的……不好。 我们需要做的是将这些对分开以给线一定宽度。 而且,puuuush。

And there we have our line mesh, all visible and line-y…

在那里,我们有了线形网格,所有的网格线都是可见的。

The part I nimbly skipped over there is also the most complicated – how do we know in which direction to move each vertex?

我灵活地跳过的那部分也是最复杂的部分–我们如何知道将每个顶点向哪个方向移动?

To solve this, we need to work out the direction between the previous and next points along the line. Then we can rotate it by 90 degrees, and we’re left with the angle we want: the normal.

为了解决这个问题,我们需要确定沿线的上一个点和下一个点之间的方向。 然后我们可以将其旋转90度,然后剩下所需的角度:法线。

Now that we have our normal, we can start getting a bit creative. For example, what if we separated the pairs differing amounts at each point? Here, if we make the pairs get closer together toward the ends of the line, it will give us this sharp, tapered effect.

现在我们有了正常的生活,我们可以开始变得更有创意了。 例如,如果我们在每个点将成对的不同数量分开怎么办? 在这里,如果我们使线对朝线的末端靠得更近,它将为我们提供这种尖锐的锥形效果。

Now see what fun ideas you can come up with! I’ll wait. A bit more. Ok, stop.

现在看看您能想到什么有趣的主意! 我会等。 多一点。 好吧,停下

And that’s the most complicated part over. Note: I haven’t gone into the depths of each caveat that drawing lines can present, because, well, I don’t need to. But I couldn’t possibly write about lines without mentioning this extremely informative and digestible article, written by Matt DesLauriers about everything you’d want to know about drawing lines in WebGL.

这是最复杂的部分。 注意:我没有深入到画线可能会出现的每个警告,因为,我不需要。 但是我可能无法不提及线而未提及Matt DesLauriers撰写的这篇内容丰富且易于理解的文章,内容涉及您想了解的有关在WebGL中绘制线的一切。

代码时间–设置场景 (Code time – setting the scene)

To kick us off, let’s set up a basic OGL canvas.

首先,让我们建立一个基本的OGL画布。

Here’s an OGL CodeSandbox Template project that I’ll be using as a guide. Feel free to fork this for any OGL experiments!

这是一个OGL CodeSandbox模板项目,我将以此为指南。 随意将其用于任何OGL实验!

演示地址

First import the required modules. Normally, I would import from a local copy of OGL for the ability to tree-shake, but to keep the file structure empty on CodeSandbox, here we’re using jsdelivr – which gives us CDN access to the npm deployment.

首先导入所需的模块。 通常,我会从OGL的本地副本导入以进行摇晃,但是为了在CodeSandbox上保持文件结构为空,此处使用的是jsdelivr,这使CDN可以访问npm部署。

import {
Renderer, Camera, Orbit, Transform, Geometry, Vec3, Color, Polyline,
} from 'https://cdn.jsdelivr.net/npm/ogl@0.0.25/dist/ogl.mjs';

Create the WebGL context, and add the canvas to the DOM.

创建WebGL上下文,然后将画布添加到DOM。

const renderer = new Renderer({dpr: 2});
const gl = renderer.gl;
document.body.appendChild(gl.canvas);

Create our camera and scene.

创建我们的相机和场景。

const camera = new Camera(gl);
camera.position.z = 3;
const controls = new Orbit(camera);
const scene = new Transform();

And then render the scene in an update loop. Obviously the scene is empty, so this will currently look very black.

然后在更新循环中渲染场景。 显然,场景是空的,因此当前看起来非常黑。

function update(t) {
requestAnimationFrame(update);
controls.update();
renderer.render({scene, camera});
}

But now we can do all of the things!

但是现在我们可以做所有事情!

As an input to OGL’s Polyline class, we need to create a bunch of points (xyz coordinates).

作为OGL的Polyline类的输入,我们需要创建一堆点(xyz坐标)。

Here, the x value goes from -1.5 and 1.5 along the line, while the y value moves in a sine pattern between -0.5 and 0.5.

在此,x值沿直线从-1.5和1.5变窄,而y值在-0.5和0.5之间以正弦模式移动。

const count = 100;
const points = [];
for (let i = 0; i < count; i++) {
const x = (i / (count - 1) - 0.5) * 3;
const y = Math.sin(i / 10.5) * 0.5;
const z = 0;
points.push(new Vec3(x, y, z));
};

Then we pass those points into a new instance of Polyline, along with colour and thickness variables (uniforms). And finally, attach it to the scene.

然后,将这些点以及颜色和厚度变量(均匀)传递到Polyline的新实例中。 最后,将其附加到场景中。

const polyline = new Polyline(gl, {
points,
uniforms: {
uColor: {value: new Color('#1b1b1b')},
uThickness: {value: 20},
},
});
polyline.mesh.setParent(scene);

Here we have that working live. (Click and drag, scroll etc. If you want...)

在这里,我们进行现场工作。 (单击并拖动,滚动等。如果需要...)

演示地址

How about a square? Let's just change those points.

正方形呢? 让我们改变这些观点。

const points = [];
points.push(new Vec3( 0, -1, 0));
points.push(new Vec3(-1, -1, 0));
points.push(new Vec3(-1,  1, 0));
points.push(new Vec3( 1,  1, 0));
points.push(new Vec3( 1, -1, 0));
points.push(new Vec3( 0, -1, 0));

演示地址

Circle?

圈?

const count = 100;
const points = [];
for (let i = 0; i < count; i++) {
const angle = i / (count - 2) * Math.PI * 2;
const x = Math.cos(angle);
const y = Math.sin(angle);
const z = 0;
points.push(new Vec3(x, y, z));
};

演示地址

You may have noticed that when you rotate or zoom the camera, the line will always stay the same thickness. You would probably expect the line to get thicker when it's closer to to camera, and also to be paper thin when rotated on its side.

您可能已经注意到,当旋转或缩放相机时,线条将始终保持相同的粗细。 您可能希望该线在离相机更近时会变粗,并且在侧面旋转时也会变薄。

This is because the pair separation we spoke about earlier is happening after the camera's projection is applied - when the vertex values are in what's called 'NDC Space' (Normalized Device Coordinates). Projection matrices can be confusing, but luckily, NDC Space is not.

这是因为我们之前提到的线对分离发生在应用了相机的投影之后-当顶点值位于所谓的“ NDC空间”(归一化设备坐标)中时。 投影矩阵可能会造成混淆,但幸运的是,NDC Space不会。

NDC Space is simply picturing your canvas as a 2D graph, with left to right (X), and bottom to top (Y) going from -1 to 1. No matter how complicated your scene is (geometry, projections, manipulations), each vertex will eventually need to be projected to a -1 to 1 range for X and Y.

NDC Space只是将画布描绘为2D图形,从左到右(X),从下到上(Y)从-1到1。无论场景多么复杂(几何,投影,操作),每个顶点最终将需要投影到X和Y的-1到1范围内。

A more common term you've probably heard is Screen Space, which is very similar, but instead of a -1 to 1 range, it's mapped from 0 to 1.

您可能听说过的更常见的术语是“屏幕空间”,它非常相似,但是它不是从-1到1的范围,而是从0到1映射的。

We generally use cameras to help us convert our 3D coordinates into NDC Space, which is absolutely vital when you need to spin around an object, or view geometry from a specific perspective. But for what we're doing (mouse trails. I haven't forgotten), we don't really need to do any of that! So, in fact, we're going to skip that whole step, throw away the camera, and create our points directly in NDC Space (-1 to 1) from the get-go. This simplifies things, and it also means that we're going to get the opportunity to write a custom shader! Let me show you.

通常,我们使用摄像头来帮助我们将3D坐标转换为NDC Space,这在您需要旋转对象或从特定角度查看几何图形时绝对至关重要。 但是对于我们正在做的事情(鼠标踪迹。我没有忘记),我们真的不需要做任何事情! 因此,实际上,我们将跳过整个步骤,扔掉摄影机,并从一开始就直接在NDC Space(-1到1)中创建点。 这简化了事情,也意味着我们将有机会编写自定义着色器! 让我演示给你看。

使用自定义着色器对线条进行着色 (Shaping the line with a custom shader)

Firstly, let's create our points in a straight line, with the X going from -0.5 to 0.5 and the Y left at 0. Keeping in mind that the screen goes from -1 to 1, this means we will end up with a horizontal line in the center of the screen, spanning half the width.

首先,让我们以一条直线创建点,其中X从-0.5变为0.5,Y保持为0。请记住,屏幕从-1变为1,这意味着我们将以一条水平线结束在屏幕中央,宽度的一半。

const count = 40;
const points = [];
for (let i = 0; i < count; i++) {
const x = i / (count - 1) - 0.5;
const y = 0;
const z = 0;
points.push(new Vec3(x, y, z));
};

This time when we create our Polyline, we are going to pass in a custom Vertex shader, which will override the default shader found in that class. We also don't need a thickness just yet as we'll be calculating that in the shader.

这次,当我们创建折线时,我们将传递一个自定义的顶点着色器,它将覆盖该类中的默认着色器。 我们还不需要厚度,因为我们将在着色器中进行计算。

const polyline = new Polyline(gl, {
points,
vertex,
uniforms: {
uColor: {value: new Color('#1b1b1b')},
},
});

Now, there are two shaders in the WebGL pipeline, Vertex and Fragment. To put it simply, the Vertex shader determines where on the screen to draw, and the Fragment shader determines what colour.

现在,WebGL管道中有两个着色器:顶点和片段。 简单地说,顶点着色器确定要在屏幕上绘制的位置,而片段着色器确定什么颜色。

We can pass into a Vertex shader whatever data we want, that's entirely up to you. However, it will always be expected to return a position on the viewport that should be rendered (in Clip Space, which, for this case, is the same as NDC Space; -1 to 1).

我们可以将所需的任何数据传递到Vertex着色器中,这完全取决于您。 但是,总是希望在视口上返回一个应渲染的位置(在“剪辑空间”中,对于这种情况,它与NDC空间相同; -1至1)。

At the start of our Vertex shader, you will find the input data: Attributes and Uniforms. Attributes are per-vertex variables, whereas Uniforms are common variables for all of the vertices. For example, as this shader is run for each vertex passed in, the position Attribute value will change, moving along each point, however the uResolution Uniform value will remain the same throughout.

在我们的顶点着色器的开始,您将找到输入数据:属性和制服。 属性是每个顶点的变量,而Uniforms是所有顶点的公共变量。 例如,为每个传入的顶点运行此着色器时, position Attribute值将沿每个点移动,但是uResolution Uniform值将始终保持不变。

attribute vec3 position;
attribute vec3 next;
attribute vec3 prev;
attribute vec2 uv;
attribute float side;
uniform vec2 uResolution;

At the very end of our Vertex shader, you'll find a function called main that defines the variable gl_Position. These two names are non-debatable! Our WebGL program will automatically look for the main function to run, and then it will pass the gl_Position variable on to the Fragment shader.

在我们的顶点着色器的最后,您会找到一个名为main的函数,该函数定义了变量gl_Position 。 这两个名字无可争议! 我们的WebGL程序将自动查找要运行的main功能,然后将gl_Position变量传递给Fragment着色器。

void main() {
gl_Position = vec4(position, 1);
}

As our points are already in NDC Space, our shader - made up of just these two sections - is technically correct. However the only issue (the same as we had in our breakdown) is that the position pairs are on top of each other, so our line would be invisibly thin.

由于我们的观点已经在NDC Space中,因此仅由这两部分组成的着色器在技术上是正确的。 但是,唯一的问题(与我们的细分相同)是位置对彼此重叠,因此我们的线很细。

So instead of passing our position right on through to the output, let's add a function, getPosition, to push each vertex apart and give our line some width.

因此,我们不要将position直接传递到输出,而是添加一个函数getPosition ,将每个顶点分开并给我们的线提供一定的宽度。

vec4 getPosition() {
vec2 aspect = vec2(uResolution.x / uResolution.y, 1);
vec2 nextScreen = next.xy * aspect;
vec2 prevScreen = prev.xy * aspect;
vec2 tangent = normalize(nextScreen - prevScreen);
vec2 normal = vec2(-tangent.y, tangent.x);
normal /= aspect;
normal *= 0.1;
vec4 current = vec4(position, 1);
current.xy -= normal * side;
return current;
}
void main() {
gl_Position = getPosition();
}

Ah, now we can see our line. Mmmm, very modernist.

啊,现在我们可以看到线了。 嗯,非常现代。

演示地址

This new function is doing the exact steps in our approach overview. See here.

此新功能正在执行我们的方法概述中的确切步骤。 看这里。

We determine the direction from the previous to the next point.

我们确定从上一个点到下一个点的方向。

vec2 tangent = normalize(nextScreen - prevScreen);

Then rotate it 90 degrees to find the normal.

然后将其旋转90度以找到法线。

vec2 normal = vec2(-tangent.y, tangent.x);

Then we push our vertices apart along the normal. The side variable has a value of -1 or 1 for each side of a pair.

然后,我们沿着法线将顶点分开。 对于一对中的每一侧, side变量的值为-1或1。

current.xy -= normal * side;

"OK OK... but you skipped a few lines".

“确定,确定...但是您跳过了几行”。

Indeed. So, the lines that determine and apply the aspect ratio are there to account for the rectangular viewport. Multiplying against the aspect ratio makes our scene square. Then we can perform the rotation without risk of skewing. And after, we divide by the aspect to bring us back to the correct ratio.

确实。 因此,确定和应用纵横比的线在那里考虑了矩形视口。 乘以长宽比可以使场景变得方形。 这样我们就可以进行旋转而不会产生扭曲的风险。 之后,我们按方面进行划分,以使我们回到正确的比例。

And the other line...

还有另一条线

normal *= 0.1;

Yes that one... is where we can have some fun. As this manipulates the line's width.

是的,那是我们可以在其中找到乐趣的地方。 这样可以操纵线的宽度。

Without this bit of code, our line would cover the entire height of the viewport. Why? See if you can guess...

没有这些代码,我们的行将覆盖视口的整个高度。 为什么? 看看你能猜到...

You see, as the normal is a 'normalised' direction, this means it has a length of 1. As we know, the NDC Space goes from -1 to 1, so if our line is in the middle of the screen, and each side of the line is pushed out by 1, that will cover the entire range of -1 to 1. So multiplying by 0.1 instead only makes our line cover 10% of the viewport.

您会看到,因为法线是“归一化”方向,所以它的长度为1。我们知道,NDC空间从-1变为1,所以如果我们的行在屏幕中间,并且每个线的一侧被1推出,将覆盖-1到1的整个范围。因此,乘以0.1只会使线覆盖视口的10%。

Now if we were to change this line, to say...

现在,如果我们要更改此行,请说...

normal *= uv.y * 0.2;

We get this expanding, triangular shape.

我们得到了这种不断扩展的三角形形状。

This is because the variable uv.y goes from 0 to 1 along the length of the line. So we can use this to affect the shape in a bunch of different ways.

这是因为变量uv.y沿着线的长度从0变为1。 因此,我们可以使用它以多种方式影响形状。

Like, we can wrap that code in a pow function.

就像,我们可以将该代码包装在pow函数中。

normal *= pow(uv.y, 2.0) * 0.2;

Hm, how exponentially curvy. No, I want something more edgy.

嗯,曲线如何指数。 不,我想要些前卫的东西。

normal *= abs(fract(uv.y * 2.0) - 0.5) * 0.4;

Too edgy...

太前卫...

normal *= cos(uv.y * 12.56) * 0.1 + 0.2;

Too flabby.

太松弛了。

normal *= (1.0 - abs(uv.y - 0.5) * 2.0) * 0.2;

Almost... but a little too diamond-y.

差不多...但是有点钻石般的。

normal *= (1.0 - pow(abs(uv.y - 0.5) * 2.0, 2.0)) * 0.2;

That's not bad. Let's run with that.

不错让我们开始吧。

So now we have our shape, let's deal with the movement.

现在我们有了形状,让我们来应对一下运动。

增加运动 (Adding movement)

To start off we just need 20 points, left at the default [0, 0, 0] value.

首先,我们只需要保留20点,即默认的[0,0,0]值即可。

Then we need a new Vec3 that will track the mouse input, and covert the X and Y values to a -1 to 1 range, with the Y flipped.

然后,我们需要一个新的Vec3 ,它将跟踪鼠标的输入,并将X和Y值隐蔽在-1到1范围内,同时将Y翻转。

const mouse = new Vec3();
function updateMouse(e) {
mouse.set(
(e.x / gl.renderer.width) * 2 - 1,
(e.y / gl.renderer.height) * -2 + 1,
0
);
}

Then in our update function, we can use this mouse value to move our points.

然后,在更新功能中,我们可以使用此鼠标值移动点。

Every frame, we loop through each of our points. For the first point, we ease it to the mouse value. For every other point, we ease it to the previous point in the line. This creates a trail effect, that grows and shrinks as the user moves the mouse faster and slower.

在每一帧中,我们遍历每一个要点。 首先,我们将其简化为鼠标值。 对于其他每个点,我们将其放宽到行中的上一个点。 这会产生拖尾效果,随着用户越来越快地移动鼠标,它会逐渐增大和缩小。

requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
for (let i = points.length - 1; i >= 0; i--) {
if (!i) {
points[i].lerp(mouse, 0.9);
} else {
points[i].lerp(points[i - 1], 0.9);
}
}
polyline.updateGeometry();
renderer.render({scene});
}

Have a play with it below.

在下面玩。

演示地址

We can make this a bit more fun by replacing the first point's linear easing with a spring.

通过用弹簧替换第一点的线性缓动,我们可以使此操作更加有趣。

const spring = 0.06;
const friction = 0.85;
const mouseVelocity = new Vec3();
const tmp = new Vec3();
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
for (let i = points.length - 1; i >= 0; i--) {
if (!i) {
tmp.copy(mouse).sub(points[i]).multiply(spring);
mouseVelocity.add(tmp).multiply(friction);
points[i].add(mouseVelocity);
} else {
points[i].lerp(points[i - 1], 0.9);
}
}
polyline.updateGeometry();
renderer.render({scene});
}

The extra bit of physics just makes it that much more interesting to play with. I can't help but try and make a beautiful curving motion with the mouse...

物理的额外作用使它变得更加有趣。 我不禁尝试用鼠标做出优美的弯曲动作...

演示地址

Finally, one line is never enough. And what's with all of this dark grey?! Give me 5 coloured lines, with randomised spring values, and we'll call it even.

最后,单行永远是不够的。 而且所有这些深灰色是什么? 给我5条带有随机弹簧值的彩色线,我们称其为偶数。

演示地址

And there we have it!

我们终于得到它了!

As we're using random values, every time you refresh, the effect will behave a little differently.

由于我们使用随机值,因此每次刷新时,效果都会有所不同。

结束 (End)

Thank you so much for sticking with me. That ended up being a lot more in-depth than I had planned... I implore you to play around with the code, maybe try randomising the number of points in each line... or changing the shape of the curve over time!

非常感谢您坚持我。 最终,这比我计划的要深入得多。我恳请您使用代码,或者尝试随机化每条线中的点数……或者随着时间的推移更改曲线的形状!

If you're new to WebGL, I hope this made the world of buffer attributes and shaders a little less overwhelming - it can be really rewarding to come up with something interesting and unique.

如果您是WebGL的新手,我希望这会使缓冲区属性和着色器的世界变得不那么令人不知所措-提出一些有趣且独特的东西真的很有意义。

翻译自: https://tympanus.net/codrops/2019/09/24/crafting-stylised-mouse-trails-with-ogl/

#{}ogl表达式

#{}ogl表达式_使用OGL制作程式化的鼠标轨迹相关推荐

  1. 笛卡尔心形函数表达式_几何画板制作笛卡尔心形函数的详细操作方法

    朋友们或许不知道几何画板怎样制作笛卡尔心形函数的详细操作,那么今天绿软吧就讲解几何画板制作笛卡尔心形函数的详细操作方法哦,希望能够帮助到大家呢. 1.新建参数.右键绘图区空白处,"新建参数& ...

  2. python图片水印软件_基于Python制作的控制鼠标删除图片水印的小工具

    在做视频或者图片处理的时候,我们经常会遇到存在水印的情况,或者我们需要去除图片的某一个部分, 这时候我们就需要想办法去除不需要的这一部分.下面这个工具能够控制鼠标将图片上的任意部分改变颜色, 从而达到 ...

  3. js拆字_分图程序 _制作个人字体_手写字制作ttf字体方法

    js拆字_分图程序 _制作个人字体_手写字制作ttf字体方法 前言 FontForgeBuilds制作ttf FontForgeBuilds制作个人字体 Adobe_Fireworks_CS5批量转换 ...

  4. 计算机基础应用软件ppt制作,大学计算机基础_演示文稿制作软件.ppt

    大学计算机基础_演示文稿制作软件资料 插入影片和声音 插入影片 插入声音 播放CD乐曲 录制声音 5.5 演示文稿中的动画和超链接技术 5.5.1 为幻灯片加入动画效果 5.5.2 创建超级链接 为幻 ...

  5. c语言 栈求解表达式_非线性方程组的编程求解方法

    [作者声明] 本文所有文字均为作者原创,所有图片均为作者本人亲自拍摄或制作. 版权所有,仅供阅读欣赏,禁止任何单位或个人以任何形式对本文的文字或图片进行包括但不限于复制.转载.引用.抄袭.截图.模仿. ...

  6. vb.net怎么调用fastreport报表_零编码制作报表可能吗?

    要回答这个问题,首先要明确啥程度算"零编码"? 以 Excel 为例,如果把写 Excel 公式(包括复杂一些的)看做零编码:而把写 Excel VBA 看做编码的话, 报表开发是 ...

  7. Docker Review - dockerfile 实战_使用dockerfile制作tomcat镜像

    文章目录 Pre Docker 官方镜像 Dockerfile dockerfile制作tomcat镜像 准备软件 编写Dockerfile文件 dockerfile构建镜像 启动镜像 测试访问tom ...

  8. ae万能弹性表达式_外置常用ae插件 快速掌握AE软件的精髓

    ​相信经常用ae软件的小伙伴就知道,AE的强大之处就在于它的插件,它就像一个巨型的加工厂.所以,AE插件在AE软件的使用中是必不可少的. 鉴于AE插件的种类繁多,为了让影视后期爱好者在后续学习AE软件 ...

  9. 地图定义一个中间不动标注_高精度地图制作(三)

    高精度地图主要用于无人驾驶路径规划,还可以应用于无人驾驶定位,ROI区域过滤等.接下来我们主要来看如何制作高精度地图. 高精度地图制作流程 高精度地图的制作过程分为4个步骤: 地图采集 点云地图制作 ...

最新文章

  1. 关于大数据的完整讲解
  2. pytorch神经网络之卷积层与全连接层参数的设置
  3. gtid mysql failover_Keepalived + MySQLfailover + GTIDs 高可用
  4. python 比较文件夹或列表异同
  5. 【论文解读】EfficientNet强在哪里
  6. 编写一个程序,要求输入一个ASCII码值(如66),然后输入相应的字符`
  7. 关于Android sdkmanager目录结构的总结
  8. Java中的关键字--volatile
  9. 20号:JAVA的值传递与引用传递的正确理解
  10. notepad++自动补全括号
  11. R语言机器学习xgboost实例,油管上的关于xgboost的例子
  12. 因子分析法(Factor Analysis)是什么分析
  13. 命名实体消歧的代码实现
  14. Java实现 LeetCode 492 构造矩形
  15. 大白菜Ghost备份还原系统-人人都会重装系统
  16. 超人:钢铁之躯 Man of Steel (2013)
  17. php ean-13,用php生成EAN_13标准的条形码_php
  18. java系列之J2ME的移动支付系统的设计与实现
  19. 基于Inception v2实现判别mnist手写数据集
  20. 三国志战略版:Daniel_吕玲绮分析

热门文章

  1. 用Python来玩微信小游戏跳一跳
  2. fixedsys字体 win7_帮您win7系统记事本像Word文档一样更换字体的解决步骤
  3. php 递归无限极分类和层级展示(适用于权限管理和分类管理功能)
  4. 连目标管理都不会,还谈什么目标?
  5. python获取文件夹下指定后缀名文件列表(可手工设定是否遍历子文件夹)cmd复制文件命令使用
  6. 【微信小程序】微信小程序--倒放音频的实现
  7. 微趣能Weiqn(微信源码下载)V1.5.0.2beta官方版
  8. c语言编程一对新出生的兔子,C语言 有一对兔子。从出生后第三个月起每个月都生一对兔子,小兔子长到三个月后又生一对小兔子,假如兔子都不死,问每个月的兔子总数为多少。...
  9. win10重装系统后,无限自动修复
  10. html隐藏手机状态栏,如何隐藏iPhone手机状态栏_隐藏iPhone手机状态栏操作方法介绍-果粉控...