directory

  1. Basic Bezier curves
  2. Draw bezier curves
  3. Let the curve move
  4. Draw bezier surfaces
  5. data
  6. harvest

The final results of this paper are as follows:

Note: Because it is necessary to learn how to use Kotlin, subsequent Java layer code implementation should use Kotlin as much as possible

1. Basic knowledge of Bezier curve

Invented by French automotive engineer Pierre Bezier in 1962 while designing the body of a car, Bezier curves can be used to create a beautiful body.

In graphic software such as PS and Sketch, we often see Bessel curve drawing by pen Icon.

Bezier curves have at least one start and end point and n intermediate control points. The number of follow-up intermediate control points can be divided into order (n+1) Bezier curves. For example, the second-order Bezier curve has one control point, and the third-order Bezier curve has two intermediate control points. Let’s first look at the next Bessel curve

The variable t is an interpolation, the value of P(t) changes as time t changes.

The same is true for a second-order Bezier curve. You compute first order Bezier Q1 for P0 and P1, and then you compute first order Bezier Q2 for P1 and P2, and then you compute first order Bezier Q1 and Q2 and you get P(t) (img).

As mentioned in The Nature of Technology, technological innovation comes from the combination of technologies. For third-order Bessel curves, see also disassembling them into two second-order Bessel curves, P0, P1, P2 and P1, P2, P3, and then doing the first-order Bessel with the above two results, to get the third-order Bessel curves that are more commonly used in real applications.

Draw bezier curves

We use third-order Bezier curves to plot. Look at the implementation of the Path and OpenGL on Android respectively.

Android through Path implementation

class BeizerView : View { var path = Path() val paint = Paint() constructor(context: Context?) : super(context) constructor(context: Context? , attrs: AttributeSet?) : super(context, attrs) constructor(context: Context? , attrs: AttributeSet? , defStyleAttr: Int) : super(context, attrs, defStyleAttr) override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) updatePath() paint.isAntiAlias = true paint.strokeWidth = 5f paint.color = Color.RED paint.style = Paint.Style.STROKE } private fun updatePath() { path.reset() path.moveTo(10f, 1500f) path.cubicTo(300f, 650f, 800f, 100f, 1050f, 1500f) path.moveTo(10f, 100f) path.close() } override fun dispatchDraw(canvas: Canvas?) { canvas? .save() canvas? .drawPath(path, paint) super.dispatchDraw(canvas) canvas? .restore() }Copy the code

The effect is as follows:

Now, let’s look at implementing Bezier curves with OpenGL and first define the shader, the key vertex shader

// Vertex shader attribute float a_tData; uniform vec4 u_startEndData; uniform vec4 u_ControlData; Vec2 bezierMix(vec2 P0, vec2 P1, vec2 p2, vec2 p3, float t) {// Use the internal function mix vec2 q0 = mix(p0, p1, t); vec2 q1 = mix(p1, p2, t); vec2 q2 = mix(p2, p3, t); vec2 r1 = mix(q0, q1, t); vec2 r2 = mix(q1, q2, t); return mix(r1, r2, t); } void main() { vec4 pos; Pos. W = 1.0; vec2 p0 = u_startEndData.xy; vec2 p3 = u_startEndData.zw; vec2 p1= u_ControlData.xy; vec2 p2= u_ControlData.zw; float t= a_tData; vec2 point = bezierMix(p0, p1, p2, p3, t); If (t<0.0) {pos.xy = vec2(0.0, 0.0); } else { pos.xy = point; } gl_PointSize = 4.0 f; gl_Position = pos; }Copy the code

// Source shader

precision mediump float;

uniform vec4 u_Color;

void main() {
    gl_FragColor = u_Color;
}
Copy the code

Render:

class BezierCurveLineRender(private val context: Context) : IGLRender { val POINTS_NUM = 256 val TRIANGLES_PER_POINT = 3 var mProgram: Int = -1 var tDataLocation = -1; var uOffsetLocation = -1; var uStartEndDataLocation = -1; var uControlDataLocation = -1; var uColorLocation = -1; lateinit var vaoBuffers: IntBuffer; override fun onSurfaceCreated() { val vertexStr = ShaderHelper.loadAsset(context.resources, "vertex_beziercurve.glsl") val fragStr = ShaderHelper.loadAsset(context.resources, "frag_beziercurve.glsl") mProgram = ShaderHelper.loadProgram(vertexStr, FragStr) / / by VAO batch data transfer tDataLocation = GLES20. GlGetAttribLocation (mProgram, "a_tData") uOffsetLocation = GLES20.glGetUniformLocation(mProgram, "u_offset") uStartEndDataLocation = GLES20.glGetUniformLocation(mProgram, "u_startEndData") uControlDataLocation = GLES20.glGetUniformLocation(mProgram, "u_ControlData") uColorLocation = GLES20.glGetUniformLocation(mProgram, "u_Color") setVaoData(); } fun setVaoData() { val tDataSize = POINTS_NUM * TRIANGLES_PER_POINT; val floatBuffer: FloatBuffer = FloatBuffer.allocate(tDataSize) for (i in 0.. TDataSize step TRIANGLES_PER_POINT) {// Set data to 0,1/3*256,2/3*256,3/3*356.... 1 if (I < tDataSize) {floatBuffer. Put (I, I * 1.0f/tDataSize)} if (I + 1 < tDataSize) {floatBuffer. Put (I + 1, (I + 1) * 1.0f/tDataSize)} if (I + 2 < tDataSize) {floatBuffer. Put (I + 2, (I + 1) * 1.0f/tDataSize)}} //VBO val buffers: IntBuffer = IntBuffer.allocate(1) GLES20.glGenBuffers(1, buffers) GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]) GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, 4 * tDataSize, floatBuffer, GLES20.GL_STATIC_DRAW) //VAO vaoBuffers = IntBuffer.allocate(1) GLES30.glGenVertexArrays(1, vaoBuffers) GLES30.glBindVertexArray(vaoBuffers[0]) GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]) GLES20.glEnableVertexAttribArray(tDataLocation) GLES30.glVertexAttribPointer(tDataLocation, 1, GLES20.GL_FLOAT, false, 4, 0) //delete GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) GLES30.glBindVertexArray(GLES30.GL_NONE) } override fun onSurfaceChanged(width: Int, height: Int) { GLES20.glViewport(0, 0, width, Height)} override fun the draw () {GLES20. GlClear (GLES20. GL_COLOR_BUFFER_BIT) GLES20. GlClearColor (0.0 f to 0.0 f to 0.0 f, 1.0 f) GLES20. GlUseProgram (mProgram) GLES30. GlBindVertexArray (vaoBuffers [0]) GLES20.glEnableVertexAttribArray(uStartEndDataLocation) GLES20.glUniform4f(uStartEndDataLocation, -1f, 0f, 1f, 0 f) GLES20. GlEnableVertexAttribArray (uControlDataLocation) GLES20. GlUniform4f (uControlDataLocation, 0.04 f, f 0.99, 0 f, 0.99 f) GLES20. GlEnableVertexAttribArray (uColorLocation) GLES20. GlUniform4f (uColorLocation, 1 f, 0 f, 0 f, 1f) GLES20.glUniform1f(uOffsetLocation, 1f) GLES20.glDrawArrays(GLES20.GL_POINTS, 0, POINTS_NUM * TRIANGLES_PER_POINT) } }Copy the code

The effect is as follows:

Three, let the curve move

The essence of animation is to render different pictures at different times. Due to the residual of human vision, when the picture reaches 24 frames per second, it looks like a movie. The level of streaming on our mobile phones is higher, generally 60 frames per second, and some VR items require more than 90 frames per second. Then we can Render different curves at different times by changing the value of vertex coordinates or the value of several points of the Bessel curve in Render onDrawFrame through variables such as time, so as to make the curve move. To do this, we modify the vertex shader by adding the offset variable, which changes the coordinates of several points on the Bezier curve.

uniform float u_offset; . void main() { ... p0.y *= u_offset; p1.y *= u_offset; p2.y *= u_offset; p3.y *= u_offset; . }Copy the code

Then, onDrawFrame calculates and passes the offset value by the current time or the current frame number

override fun draw() { ... mFrameIndex++; Var newIndex = mFrameIndex var offset = (newIndex % 100) * 1.0f / 100; Offset = if ((newIndex / 100) % 2 == 1) (1-offset) else offset = if ((newIndex / 100) % 2 == 1 GLES20.glEnableVertexAttribArray(uOffsetLocation) GLES20.glUniform1f(uOffsetLocation, offset) ... }Copy the code

The animation looks like this:

You can add a matrix transformation, rotate the model view 180 on the X-axis, draw it twice to get an up-down symmetric Bezier shader modification

uniform mat4 u_MVPMatrix; ... Void main () {... // gl_Position = pos; gl_Position = u_MVPMatrix * pos; ... } Override fun onSurfaceCreated() {... SetLookAtM (mViewMatrix, 0, 0f, 0f, 5f, 0f, 0f, 0f, 0f, 0f, 1f, 0f) orthoM(mPorjectMatrix, 0, -1f, -1f, 1f, 0.1f, 100f)} Override fun draw() {... SetIdentityM (mModelMatrix, 0) Matrix. RotateM (mModelMatrix, 0, 0f, 1f*offset1, 0f, MultiplyMM (mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0) Matrix. MultiplyMM (mMVPMatrix, 0, mModelMatrix, 0) Matrix. mPorjectMatrix, 0, mMVPMatrix, 0) GLES20.glUniformMatrix4fv(uMVPMatrixLocation, 1, false, mMVPMatrix, 0) GLES20.glDrawArrays(GLES20.GL_POINTS, 0, POINTS_NUM * TRIANGLES_PER_POINT) // Flip along the x axis to draw the other half // Set the Matrix Matrix. SetIdentityM (mModelMatrix, 0) Matrix. RotateM (mModelMatrix, 0, 180f, 1f*offset1, 0f, 0f) mViewMatrix, 0, mModelMatrix, 0) Matrix.multiplyMM(mMVPMatrix, 0, mPorjectMatrix, 0, mMVPMatrix, 0) GLES20.glUniformMatrix4fv(uMVPMatrixLocation, 1, false, mMVPMatrix, 0) GLES20.glDrawArrays(GLES20.GL_POINTS, 0, POINTS_NUM * TRIANGLES_PER_POINT) }Copy the code

Results the following

Bessel surface

The above drawing is composed of points, and by a miracle, points are composed of corresponding curves rendered according to the PO points of the Pessel curve. So how do you implement a surface? The basic shapes in OpenGL are points, lines and triangles. For the plane we can draw multiple triangles to achieve. For example, one vertex of a triangle is always fixed at the starting position, and two points and two points are calculated by bezier curve formula.

Changes the data in the floatBuffer

fun setVaoData() { ... for (i in 0.. tDataSize step TRIANGLES_PER_POINT) { if (i < tDataSize) { floatBuffer.put(i, I * 1.0f/tDataSize)} if (I + 1 < tDataSize) {floatBuffer. Put (I + 1, (I + 3) * 1.0f/tDataSize)} if (I + 2 < tDataSize) {floatBuffer. Put (I + 2, -1f)}}... }Copy the code

The vertex shader is modified as follows

void main() { ... If (t<0.0) {pos.xy = vec2(0.0, 0.0); } else { pos.xy = point; }... }Copy the code

Change points to lines when drawing

   private fun drawArray() {

            //GLES20.glDrawArrays(GLES20.GL_POINTS, 0, POINTS_NUM * TRIANGLES_PER_POINT)
  GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, POINTS_NUM * TRIANGLES_PER_POINT)
    }
Copy the code

The effect is as follows:

Changing the color of the vertices and drawing more Bezier surfaces is exactly what happened at the beginning of this article.

The full code can be found at github github.com/ayyb1988/me…

Five, the data

  1. GAMES101- Introduction to Modern Computer Graphics (Curses) – Lingqi Yan
  2. GAMES101- Introduction to Modern Computer Graphics (Animation)- Lingqi Yan
  3. Tsinghua University – Fundamentals of Computer Graphics (National Elite Course)
  4. OpenGL ES draws bezier curves
  5. Sound Visualization on Android: Drawing a Cubic Bezier with OpenGL ES

Six, harvest

  1. Understand the origin and realization principle of Bezier curve
  2. AndroidPath and OpenGL two ways to draw bezier curve, as well as performance comparison
  3. Let the picture move
  4. Implement bezier surfaces

Thank you for reading

Next we learn practice sky box, welcome to pay attention to the public account “audio and video development journey”, learn and grow together.

Welcome to communicate