如何在WebGL中画图

如何在WebGL中画图 (How to draw gears in WebGL)

Original article: https://aralroca.com/blog/how-to-draw-gears-in-webgl

原始文章: https : //aralroca.com/blog/how-to-draw-gears-in-webgl

In this article we continue what we started in “First steps in WebGL”, where we saw what it is and how it works internally: the shaders, the program, buffers, how to link data from CPU to GPU, and finally how to render a triangle. To understand all this well, I recommend first reading the previous chapter.

在本文中,我们继续从“ WebGL的第一步 ”开始的内容,在那里我们看到了它的含义及其内部工作方式: 着色器程序缓冲区 ,如何将数据从CPU链接到GPU以及最后如何渲染一个三角形 。 为了很好地理解所有这些,我建议先阅读上一章。

Here, instead of rendering a triangle, we’ll see how to render more complex structures and how to give it movement. To do that we’ll implement three dynamic gears:

在这里,代替渲染三角形,我们将看到如何渲染更复杂的结构以及如何使其运动。 为此,我们将实现三个动态齿轮

Fig 1: gears generated by us in this article 图1:我们在本文中生成的齿轮

识别形状 (Identifying shapes)

The gears we want to draw are composed of circles. Among these circles, there are certain varieties: a circle with teeth, a circle with colored border and circle filled with a color.

我们要画的齿轮是由组成的。 在这些圆圈中,有某些变种:带齿的圆圈,带有彩色边框的圆圈和填充有颜色的圆圈。

Fig 2: three different circles 图2:三个不同的圆圈

Therefore, this confirms that we can draw these gears by drawing circles but, as we saw in the previous article, in WebGL you can only rasterize triangles, points, and lines… So, what’s the difference between these circles and how can we make each of them?

因此,这证实了我们可以通过绘制圆来绘制这些齿轮,但是,正如我们在上一篇文章中看到的那样,在WebGL中,您只能光栅化三角形,点和线...因此,这些圆之间有什么区别,我们如何制作每个他们中的?

带边框的圆圈 (Circle with border)

To draw a circle with a border, we’ll use multiple points:

要绘制带边框的圆,我们将使用多个

Fig 3: mouting a circle with 360 points 无花果3:用360点圈出一个圆

Circle with filled color

填充颜​​色的圆圈

To draw a circle with a filled color, we’ll use multiple triangles:

为了绘制一个填充颜色的圆,我们将使用多个三角形

Fig 4: filled circle with triangle strip 图4:带三角形条的实心圆

The drawing mode needed for this is Triangle strip:

为此所需的绘制模式是Triangle strip

A triangle strip is a series of connected triangles from the triangle mesh, sharing vertices, allowing for more efficient memory usage for computer graphics. They are more efficient than triangle lists without indexing, but usually equally fast or slower than indexed triangle lists. The primary reason to use triangle strips is to reduce the amount of data needed to create a series of triangles. The number of vertices stored in memory is reduced from 3N to N+2, where N is the number of triangles to be drawn. This allows for less use of disk space, as well as making them faster to load into RAM.Source: Wikipedia

三角形带是三角形网格中一系列相连的三角形,它们共享顶点,从而可以更有效地利用计算机图形的内存。 它们比不带索引的三角列表更有效,但通常比带索引的三角列表快或慢。 使用三角形带的主要原因是减少创建一系列三角形所需的数据量。 存储在内存中的顶点数量从3N减少到N + 2,其中N是要绘制的三角形数量。 这允许使用较少的磁盘空间,以及使他们更快地加载到RAM.Source: 维基百科

牙齿圈 (Circle with teeth)

For the gear teeth, we’ll also use triangles. This time, without the “strip” mode. This way we’ll draw triangles that go from the center of the circumference to the outside.

对于齿轮,我们还将使用三角形 。 这次,没有“ strip”模式。 这样,我们将绘制从圆周中心到外部的三角形。

Fig 5: gear teeth are triangles 图5:齿轮齿为三角形

While we build the teeth, it’s important that we create another circle inside filled with color to make the effect that the teeth are coming out of the circle itself.

在构建牙齿时,重要的是我们在内部创建另一个充满颜色的圆,以使牙齿从圆本身中脱出。

识别要绘制的数据 (Identifying data to draw)

One thing these 3 types of figures have in common is that we can calculate their coordinates from 2 variables:

这3种图形的共同点是,我们可以从2个变量计算它们的坐标:

  • Center of the circle (x and y)

    圆心( xy )

  • Radius

    半径

As seen in the previous article, the coordinates within webGL go from -1 to 1. So let’s locate the center of each piece of gear and its radius:

如上一篇文章所述,webGL中的坐标从-1变为1。因此,让我们找到每个齿轮的中心及其半径:

Fig 6: sketch of the coordinates of the center and the radius of each gear 图6:每个齿轮的中心和半径坐标的草图

In addition, we have optional variables for specific figures such as:

此外,我们还有一些特定数字的可选变量,例如:

  • Number of teeth

    齿数
  • Stroke color (color of the border)

    描边颜色(边框颜色)

  • Fill color

    填色
  • Children (more pieces of the same gear with the same data structure)

    子级(具有相同数据结构的更多相同齿轮)

  • Direction of the rotation (only valid for the parent)

    旋转方向(仅对父级有效)

At the end, in JavaScript, we’ll have this array with the data of the three gears and all their pieces:

最后,在JavaScript中,我们将获得一个包含三个齿轮及其所有零件的数据的数组:

const x1 = 0.1
const y1 = -0.2const x2 = -0.42
const y2 = 0.41const x3 = 0.56
const y3 = 0.28export const gears = [
{
center: [x1, y1],
direction: 'counterclockwise',
numberOfTeeth: 20,
radius: 0.45,
fillColor: [0.878, 0.878, 0.878],
children: [
{
center: [x1, y1],
radius: 0.4,
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1],
radius: 0.07,
fillColor: [0.741, 0.741, 0.741],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1 - 0.23, y1],
radius: 0.12,
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1 - 0.23],
radius: 0.12,
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1 + 0.23, y1],
radius: 0.12,
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x1, y1 + 0.23],
radius: 0.12,
fillColor: [1, 1, 1],
strokeColor: [0.682, 0.682, 0.682],
},
],
},
{
center: [x2, y2],
direction: 'clockwise',
numberOfTeeth: 12,
radius: 0.3,
fillColor: [0.741, 0.741, 0.741],
children: [
{
center: [x2, y2],
radius: 0.25,
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x2, y2],
radius: 0.1,
fillColor: [0.682, 0.682, 0.682],
strokeColor: [0.6, 0.6, 0.6],
},
],
},
{
center: [x3, y3],
direction: 'clockwise',
numberOfTeeth: 6,
radius: 0.15,
fillColor: [0.741, 0.741, 0.741],
children: [
{
center: [x3, y3],
radius: 0.1,
strokeColor: [0.682, 0.682, 0.682],
},
{
center: [x3, y3],
radius: 0.02,
fillColor: [0.682, 0.682, 0.682],
strokeColor: [0.6, 0.6, 0.6],
},
],
},
]

For the colors, a little reminder: they go from 0 to 1, instead of 0 to 255, or 0 to F, as we are accustomed in CSS. For example [0.682, 0.682, 0.682] would be equivalent to rgb(174, 174, 174) and #AEAEAE.

对于颜色,有一点提醒:它们从0到1,而不是0到255,或从0到F,这是我们在CSS中惯用的。 例如[0.682, 0.682, 0.682]等效于rgb(174, 174, 174) #AEAEAE rgb(174, 174, 174)#AEAEAE

我们将如何实施轮换 (How we will implement the rotation)

Before we begin the implementation, we need to know how to implement the rotation of each gear.

在开始实施之前,我们需要知道如何实现每个齿轮的旋转。

In order to understand the rotation and other linear transformations, I highly recommend the serie about linear algebra from 3blue1brown YouTube channel. In special, this video explains it very well:

为了理解旋转和其他线性变换,我强烈推荐来自3blue1brown YouTube频道的关于线性代数的系列。 特别地,该视频很好地说明了这一点:

To sum up, if we multiply our positions by any matrix, it receives a transformation. We have to multiply each gear position by the rotation matrix. We need to add every “transformation” in front of it. If we want to rotate, we will do rotation * positions instead of positions * rotation.

总而言之,如果我们将位置乘以任何矩阵,它将得到一个转换。 我们必须将每个齿轮位置乘以旋转矩阵。 我们需要在其前面添加每个“转换”。 如果要旋转,我们将rotation * positions而不是positions * rotation

We can create the rotation matrix by knowing the angle in radians:

我们可以通过知道弧度角来创建旋转矩阵:

function rotation(angleInRadians = 0) {
const c = Math.cos(angleInRadians)
const s = Math.sin(angleInRadians) return [
c, -s, 0,
s, c, 0,
0, 0, 1
]
}

This way we can make each gear turn differently by multiplying the positions of each gear with its respective rotation matrix. To have a real rotation effect, in each frame we must increase the angle a little bit until it gives the complete turn and the angle returns to 0.

这样,我们可以通过将每个齿轮的位置与其各自的旋转矩阵相乘来使每个齿轮不同地旋转。 为了产生真实的旋转效果,我们必须在每个帧中稍微增加一点角度,直到完成完整的旋转,并且角度返回到0。

However, it’s not enough to simply multiply our positions with this matrix. If you do it, you’ll get this:

但是,仅将我们的职位与该矩阵相乘是不够的。 如果这样做,您将获得:

rotationMatrix * positionMatrix // This is not what we want.
Fig 7: rotation on the canvas (not what we want) 图7:在画布上旋转(不是我们想要的)

We’ve got every gear doing its rotation but the axis of rotation is always the center of the canvas, and that’s incorrect. We want them to rotate on their own center.

我们已经使每个齿轮旋转,但是旋转轴始终是画布的中心,这是不正确的。 我们希望他们在自己的中心旋转。

In order to fix this, first, we’ll use a transformation named translate to move our gear to the center of the canvas. Then we'll apply the right rotation (the axis will be the center of the canvas again, but in this case, it's also the center of the gear), and finally, we'll move the gear back to its original position (by using translate again).

为了解决这个问题,首先,我们将使用名为translate将齿轮移动到画布的中心。 然后,我们将应用正确的旋转(该轴将再次成为画布的中心,但在这种情况下,它也是齿轮的中心),最后,我们将齿轮移回其原始位置(通过再次使用translate )。

The translation matrix can be defined as follows:

转换矩阵可以定义如下:

function translation(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
]
}

We’ll create two translation matrices: translation(centerX, centerY) and translation(-centerX, -centerY). Their center must be the center of each gear.

我们将创建两个转换矩阵: translation(centerX, centerY)translation(-centerX, -centerY) 。 它们的中心必须是每个齿轮的中心。

To get that, we’ll do this matrix multiplication:

为此,我们将执行以下矩阵乘法:

// Now they will turn on their axis
translationMatrix * rotationMatrix * translationToOriginMatrix * positionMatrix
Fig 8: self-rotation (what we want) 图8:自我旋转(我们想要的)

You are probably wondering how to do that each gear spins at its own speed.

您可能想知道如何使每个齿轮以自己的速度旋转。

There’s a simple formula to calculate the speed according to the number of teeth:

有一个简单的公式可以根据齿数计算速度:

(Speed A * Number of teeth A) = (Speed B * Number of teeth B)

This way, in each frame we can add a different angle step to each gear and everyone spins at the speed that they’re physically supposed to.

这样,在每个框架中,我们可以为每个齿轮增加一个不同的角度步长,并且每个人都以他们应有的速度旋转。

让我们实现它! (Let’s implement it!)

Having reached this section, we now know:

到达本节后,我们现在知道:

  • What figures we should draw and how.

    我们应该画什么数字以及如何画。
  • We have the coordinates of each gear and its parts.

    我们有每个齿轮及其零件的坐标。
  • We know how to rotate each gear.

    我们知道如何旋转每个齿轮。

Let’s see how to do it with JavaScript and GLSL.

让我们看看如何使用JavaScript和GLSL。

使用着色器初始化程序 (Initialize program with shaders)

Let’s write the vertex shader to compute the positions of the vertices:

让我们编写顶点着色器以计算顶点的位置:

const vertexShader = `#version 300 es
precision mediump float;
in vec2 position;
uniform mat3 u_rotation;
uniform mat3 u_translation;
uniform mat3 u_moveOrigin;void main () {
vec2 movedPosition = (u_translation * u_rotation * u_moveOrigin * vec3(position, 1)).xy;
gl_Position = vec4(movedPosition, 0.0, 1.0);
gl_PointSize = 1.0;
}
`

Unlike the vertex shader we used in the previous article, we’ll pass the u_translation, u_rotation, and u_moveOrigin matrices, so the gl_Position will be the product of the four matrices (along with the position matrix). This way we apply the rotation as we have seen in the previous section. In addition, we'll define the size of each point we draw (which will be useful for the circle with the border) using gl_PointSize.

与上一篇文章中使用的顶点着色器不同,我们将传递u_translationu_rotationu_moveOrigin矩阵,因此gl_Position将是这四个矩阵的乘积(以及位置矩阵)。 通过这种方式,我们可以像上一节所述应用旋转 。 此外,我们将使用gl_PointSize 定义绘制的每个点的大小 (这对于带有边框的圆很有用)。

Note: Matrix multiplication is something we could do directly on the CPU with JavaScript and already pass the final matrix here, but the truth is that the GPU is made precisely for matrix operations so it’s much better for performance to do it on the shader. Besides, from JavaScript, we would need a helper to do this multiplication, since we can’t multiply arrays directly.

注意 :矩阵乘法是我们可以使用JavaScript直接在CPU上完成的事情,并且已经在此处传递了最终矩阵,但事实是GPU是专门为矩阵运算而制作的,因此在着色器上执行性能会更好。 此外,由于无法直接对数组进行乘法运算,因此从JavaScript中需要一个辅助函数来进行乘法运算。

Let’s write the fragment shader to compute the color of each pixel corresponding to each location:

让我们编写片段着色器以计算与每个位置对应的每个像素的颜色:

const fragmentShader = `#version 300 es
precision mediump float;
out vec4 color;
uniform vec3 inputColor;void main () {
color = vec4(inputColor, 1.0);
}
`

As we can see there is no magic added to this fragment, it’s the same as in the previous article. Given a defined color in the CPU with JavaScript, we’ll pass it to the GPU to color our figures.

我们可以看到,此片段中没有添加任何魔术,这与上一篇文章相同。 给定使用JavaScript在CPU中定义的颜色,我们会将其传递给GPU来对图形进行着色。

Now we can create our program with the shaders, adding the lines to get the uniform locations that we defined in the vertex shader. This way, later while running our script we can send each matrix to each uniform location per each frame.

现在,我们可以使用着色器创建程序,添加线条以获取我们在顶点着色器中定义的统一位置。 这样,稍后在运行脚本时,我们可以将每个矩阵发送到每个帧的每个统一位置。

const gl = getGLContext(canvas)
const vs = getShader(gl, vertexShader, gl.VERTEX_SHADER)
const fs = getShader(gl, fragmentShader, gl.FRAGMENT_SHADER)
const program = getProgram(gl, vs, fs)
const rotationLocation = gl.getUniformLocation(program, 'u_rotation')
const translationLocation = gl.getUniformLocation(program, 'u_translation')
const moveOriginLocation = gl.getUniformLocation(program, 'u_moveOrigin')run() // Let's see this in the next section

The getGLContext, getShader and getProgram helpers do what we saw in the previous article. I put them down here:

getGLContextgetShadergetProgram帮助器完成了上一篇文章中的操作 。 我把它们放在这里:

function getGLContext(canvas, bgColor) {
const gl = canvas.getContext('webgl2')
const defaultBgColor = [1, 1, 1, 1] gl.clearColor(...(bgColor || defaultBgColor))
gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT) return gl
}function getShader(gl, shaderSource, shaderType) {
const shader = gl.createShader(shaderType) gl.shaderSource(shader, shaderSource)
gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader))
} return shader
}function getProgram(gl, vs, fs) {
const program = gl.createProgram() gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
gl.useProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program))
} return program
}

绘制每帧+计算旋转角度 (Draw each frame + calculate rotation angles)

The run function we have seen called in the previous section will be responsible for the gears being drawn at a different angle in each frame.

我们在上一节中看到的run功能将负责在每个框架中以不同角度绘制齿轮。

// step for a gear of 1 tooth
// gears with more teeth will be calculated with this formula:
// realRotationStep = rotationStep / numberOfTeeth
const rotationStep = 0.2// Angles are all initialized to 0
const angles = Array.from({ length: gears.length }).map((v) => 0)function run() {
// Calculate the angles of this frame, for each gear
gears.forEach((gear, index) => {
const direction = gear.direction === 'clockwise' ? 1 : -1
const step = direction * (rotationStep / gear.numberOfTeeth) angles[index] = (angles[index] + step) % 360
}) drawGears() // Let's see this in the next section // Render next frame
window.requestAnimationFrame(run)
}

Given the data we have in the gears array, we know the number of teeth and in which direction each gear rotates. With this we can calculate the angle of each gear on each frame. Once we save the new calculated angles, we call the function drawGears to draw each gear with the correct angle. Then we'll recursively call the run function again (wrapped with window.requestAnimationFrame to make sure that it's called again only in the next animation cycle).

根据gears阵列中的数据,我们知道了齿数以及每个齿轮的旋转方向 。 这样,我们可以计算每个框架上每个齿轮的角度 。 保存新的计算角度后,我们将调用drawGears函数以正确的角度绘制每个齿轮。 然后,我们将再次递归调用run函数(与window.requestAnimationFrame进行包装,以确保仅在下一个动画周期中再次调用它)。

You will probably be wondering why we don’t implicitly tell to clean the canvas before each frame. It’s because WebGL does it automatically when drawing. If it detects that we change the input variables, by default it will clean the previous buffer. If for some reason (not this case) we want the canvas not to be cleaned, then we should have obtained the context with an additional parameter const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true });.

您可能会想知道为什么我们没有隐式告诉每帧之前要清洁画布 。 这是因为WebGL在绘制时会自动执行。 如果检测到我们更改了输入变量,则默认情况下它将清除先前的缓冲区。 如果出于某种原因( 不是这种情况 ),我们希望不清理画布,那么我们应该使用附加参数const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true });获得上下文const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true });

牵引齿轮 (Draw gears)

For each gear in each frame, we’ll pass to the GPU the necessary matrices for the rotation: u_translation, u_rotation and u_moveOrigin. Then, we'll start drawing each of the pieces of the gear:

对于每一帧中的每个齿轮,我们将旋转所需的矩阵传递给GPU: u_translationu_rotationu_moveOrigin 。 然后,我们将开始绘制齿轮的每个部分:

function drawGears() {
gears.forEach((gear, index) => {
const [centerX, centerY] = gear.center // u_translation
gl.uniformMatrix3fv(
translationLocation,
false,
translation(centerX, centerY)
) // u_rotation
gl.uniformMatrix3fv(rotationLocation, false, rotation(angles[index])) // u_moveOrigin
gl.uniformMatrix3fv(
moveOriginLocation,
false,
translation(-centerX, -centerY)
) // Render the gear + each gear piece
renderGearPiece(gear)
if (gear.children) gear.children.forEach(renderGearPiece)
})
}

We will draw each piece of the gear with the same function:

我们将用相同的功能绘制齿轮的每个部分:

function renderGearPiece({
center,
radius,
fillColor,
strokeColor,
numberOfTeeth,
}) {
const { TRIANGLE_STRIP, POINTS, TRIANGLES } = gl
const coords = getCoords(gl, center, radius) if (fillColor) drawShape(coords, fillColor, TRIANGLE_STRIP)
if (strokeColor) drawShape(coords, strokeColor, POINTS)
if (numberOfTeeth) {
drawShape(
getCoords(gl, center, radius, numberOfTeeth),
fillColor,
TRIANGLES
)
}
}
  • If it’s a circle with a border (Fig 3.) → we’ll use POINTS.

    如果它是一个有边界的圆( 图3 ),则将使用POINTS

  • If it’s a color-filled circle (Fig 4.) → we’ll use TRIANGLE_STRIP.

    如果是彩色圆圈( 图4 )。→我们将使用TRIANGLE_STRIP

  • If it’s a circle with teeth (Fig 5.) → we’ll use TRIANGLES.

    如果它是一个有牙齿的圆( 图5 )。→我们将使用TRIANGLES

Implemented with various “ifs”, it allows us to create a circle filled with one color but with the border in another color, or a circle filled with color and with teeth. That means more flexibility.

通过使用各种“ if”来实现,它允许我们创建一个填充有一种颜色但边框处于另一种颜色的圆,或者创建一个填充有颜色和牙齿的圆。 这意味着更大的灵活性。

The coordinates of the filled circle and the circle with border, even if one is made with triangles and the other with points, are exactly the same. The one that does have different coordinates is the circle with teeth, but we’ll use the same helper to get the coordinates:

实心圆和带有边界的圆的坐标,即使一个是由三角形制成而另一个是由点制成,也完全相同。 具有不同坐标的坐标是带有牙齿的圆,但是我们将使用相同的助手来获取坐标:

export default function getCoords(gl, center, radiusX, teeth = 0) {
const toothSize = teeth ? 0.05 : 0
const step = teeth ? 360 / (teeth * 3) : 1
const [centerX, centerY] = center
const positions = []
const radiusY = (radiusX / gl.canvas.height) * gl.canvas.width for (let i = 0; i <= 360; i += step) {
positions.push(
centerX,
centerY,
centerX + (radiusX + toothSize) * Math.cos(2 * Math.PI * (i / 360)),
centerY + (radiusY + toothSize) * Math.sin(2 * Math.PI * (i / 360))
)
} return positions
}

What we still need to know would be the helper drawShape, although it's the same code we saw in the previous article: It passes the coordinates and color to paint to the GPU, and calls the function drawArrays indicating the mode (if triangles, points...).

我们仍然需要知道的是辅助对象drawShape ,尽管它与上一篇文章中看到的代码相同:它将坐标和颜色传递到GPU,然后调用函数drawArrays指示模式(如果是三角形,点)。 ..)。

function drawShape(coords, color, drawingMode) {
const data = new Float32Array(coords)
const buffer = createAndBindBuffer(gl, gl.ARRAY_BUFFER, gl.STATIC_DRAW, data) gl.useProgram(program)
linkGPUAndCPU(gl, { program, buffer, gpuVariable: 'position' }) const inputColor = gl.getUniformLocation(program, 'inputColor')
gl.uniform3fv(inputColor, color)
gl.drawArrays(drawingMode, 0, coords.length / 2)
}

And voila! We got it.

瞧! 我们得到了它。

Photo by Clay Banks on Unsplash Clay Banks在Unsplash上​​拍摄的照片

告诉我所有代码 (Show me all the code)

I’ve uploaded all the code for this article to my GitHub. I have implemented it with Preact. All the code can be found inside the hook useGears:

我已将本文的所有代码上传到我的GitHub。 我已经用Preact实现了它。 所有代码可以在hook useGears内找到:

You can also see the demo here:

您还可以在此处查看演示:

结论 (Conclusion)

We have seen how to generate more complex figures using triangles and points. We have even given them movement with matrix multiplications.

我们已经看到了如何使用三角形和点生成更复杂的图形。 我们甚至给他们移动矩阵乘法。

There is a drawing mode we haven’t seen yet, lines. That’s because the lines that can be made with it are very thin, and they wouldn’t fit the teeth of the gear. You can’t change the thickness of the line easily, to do it you have to make a rectangle (2 triangles). These lines have very little flexibility and most figures are drawn with triangles. Anyway, at this point, you should be able to use the gl.LINES given 2 coordinates.

目前我们还没有看到, 线条绘图模式。 那是因为可以用它制作的线很细,而且它们不适合齿轮的齿。 您不能轻易更改线条的粗细,而要做到这一点,您必须制作一个矩形(2个三角形)。 这些线条几乎没有灵活性,大多数图形都是用三角形绘制的。 无论如何,在这一点上,您应该能够使用给定2个坐标的gl.LINES

This article was the second part of “First steps with WebGL”. Stay tuned because in next articles of this series we’ll see: textures, image processing, framebuffers, 3d objects, and more.

本文是“ WebGL的第一步”的第二部分。 请继续关注,因为在本系列的下一篇文章中,我们将看到:纹理,图像处理,帧缓冲区,3d对象等等。

参考资料 (References)

翻译自: https://medium.com/@aralroca/how-to-draw-gears-in-webgl-eabf3a1a3b0d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值