Click( or touch ) the square above to toggle the rotation of the star.

This is another example of using a triangle fan and drawElements. The vertex coordinates are constructed to be in the clip coordinate system with $x$ and $y$ in the interval -1 to 1. Including the center, there are 11 vertices. In the draw function 12 vertices are passed to the drawElements function( line 131). This is because to make a star the triangle fan has to be extended to include one more point, point number 1( point number 0 is the center ). This is also why the last index in the indices array buffer is 1( line 123 ).

The rotation is achieved by the rotation matrix

\[\begin{pmatrix}\cos(u\_rot) & -\sin(u\_rot) & 0\\\sin(u\_rot) & \cos(u\_rot) & 0\\0 & 0 & 1\end{pmatrix}\]

In the mat3 constructor in the vertex shader the elements are entered column-wise( line 11 ).

The code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
const vert_shader_src = `#version 300 es
#pragma vscode_glsllint_stage: vert
precision highp float;

in vec3 position;
in vec3 vertex_color;
out vec3 color;
uniform float u_rot;

void main(void) {
  mat3 rot_mat = mat3(cos(u_rot), sin(u_rot), 0.0, -sin(u_rot), cos(u_rot), 0.0, 0.0, 0.0, 1.0);
  gl_Position = vec4(rot_mat*position, 1.0);
  color = vertex_color;
}`;

const frag_shader_src = `#version 300 es
#pragma vscode_glsllint_stage: frag
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif

in vec3 color;
out vec4 frag_color;

void main(void) {
  frag_color = vec4(color, 1.0);
}`;

const g = {}; //globals

function createProgram() {
  const gl = g.gl;

  const vert_shader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vert_shader, vert_shader_src);
  gl.compileShader(vert_shader);

  const frag_shader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(frag_shader, frag_shader_src);
  gl.compileShader(frag_shader);

  const prog = gl.createProgram();
  gl.attachShader(prog, vert_shader);
  gl.attachShader(prog, frag_shader);
  gl.linkProgram(prog);
  if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
    console.error(`Link failed: ${gl.getProgramInfoLog(prog)}`);
    console.error(`vs info-log: ${gl.getShaderInfoLog(vert_shader)}`);
    console.error(`fs info-log: ${gl.getShaderInfoLog(frag_shader)}`);
    throw new Error('Error creating program');
  }
  gl.deleteShader(vert_shader);
  gl.deleteShader(frag_shader);
  return prog;
}

function initGl() {
  const gl = g.gl;

  gl.clearColor(0.2, 0.3, 0.3, 1);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  gl.viewport(0, 0, g.canvas.width, g.canvas.height);
}

function initProg() {
  const prog = createProgram();
  const gl = g.gl;
  gl.useProgram(prog);

  g.u_rot = gl.getUniformLocation(prog, 'u_rot');

  /*
  Five point star, five vertices at the larger radius
  and five vertices at the smaller radius. Three coordinates(x, y, z) for
  each of them plus three coordinates for the center gives 33
  */
  const vert_posns = new Array(33);
  const vert_colors = new Array(33);
  const r1 = 0.9; //larger radius
  const r2 = 0.4; //smaller radius
  const angle = (36 * Math.PI) / 180;
  vert_posns[0] = 0;
  vert_posns[1] = 0;
  vert_posns[2] = 0;
  vert_colors[0] = 0.78;
  vert_colors[1] = 0.08;
  vert_colors[2] = 0.52;
  for (let i = 0; i < 5; i++) {
    vert_posns[i * 6 + 3] = r1 * Math.cos(2 * i * angle);
    vert_posns[i * 6 + 4] = r1 * Math.sin(2 * i * angle);
    vert_posns[i * 6 + 5] = 0;
    vert_posns[i * 6 + 6] = r2 * Math.cos((2 * i + 1) * angle);
    vert_posns[i * 6 + 7] = r2 * Math.sin((2 * i + 1) * angle);
    vert_posns[i * 6 + 8] = 0;

    vert_colors[i * 6 + 3] = 0.1;
    vert_colors[i * 6 + 4] = 0.1;
    vert_colors[i * 6 + 5] = 0.44;
    vert_colors[i * 6 + 6] = 0.6;
    vert_colors[i * 6 + 7] = 0.98;
    vert_colors[i * 6 + 8] = 0.6;
  }

  const position = gl.getAttribLocation(prog, 'position');
  const pos_buf = gl.createBuffer();
  gl.enableVertexAttribArray(position);
  gl.bindBuffer(gl.ARRAY_BUFFER, pos_buf);
  gl.vertexAttribPointer(position, 3, gl.FLOAT, false, 0, 0);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vert_posns), gl.STATIC_DRAW);

  const vertex_color = gl.getAttribLocation(prog, 'vertex_color');
  const col_buf = gl.createBuffer();
  gl.enableVertexAttribArray(vertex_color);
  gl.bindBuffer(gl.ARRAY_BUFFER, col_buf);
  gl.vertexAttribPointer(vertex_color, 3, gl.FLOAT, false, 0, 0);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vert_colors), gl.STATIC_DRAW);

  const index_buf = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buf);
  const indices = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
}

function draw() {
  const gl = g.gl;
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.uniform1f(g.u_rot, g.rotn);
  gl.drawElements(gl.TRIANGLE_FAN, 12, gl.UNSIGNED_BYTE, 0);
}

function animate(delay) {
  const rotn_speed = 0.8; //radians per second
  const TWOPI = 2 * Math.PI;
  let then;
  function tick(now) {
    if (!then) {
      then = now;
    }
    const elapsed = now - then;
    if (elapsed > delay) {
      then = now;
      //elapsed is in milliseconds
      rotn_change = (rotn_speed * elapsed) / 1000;
      g.rotn += rotn_change;
      if (g.rotn > TWOPI) {
        g.rotn -= TWOPI;
      }
      draw();
    }
    g.animation_request = requestAnimationFrame(tick);
  }
  g.animation_request = requestAnimationFrame(tick);
}

function onResize() {
  if (g.animation_request) {
    cancelAnimationFrame(g.animation_request);
  }
  g.canvas.width = g.canvas_div.clientWidth;
  g.canvas.height = g.canvas_div.clientHeight;
  try {
    g.gl = g.canvas.getContext('webgl2', { alpha: false, depth: false });
    if (!g.gl) {
      throw new Error('Browser does not support WebGL2');
    }
  } catch (e) {
    g.canvas_div.innerHTML = `<p>${e.message}</p>`;
    return;
  }
  try {
    initGl();
    initProg();
  } catch (e) {
    g.canvas_div.innerHTML = `<p>${e.message}</p>`;
    return;
  }
  if (g.animation) {
    animate(16);
  } else {
    draw();
  }
}

function onClick() {
  g.animation = !g.animation;
  if (g.animation) {
    animate(16);
  } else {
    if (g.animation_request) {
      cancelAnimationFrame(g.animation_request);
    }
  }
}

function init() {
  g.canvas_div = document.querySelector('#canvas_div');
  g.canvas = document.querySelector('canvas');
  g.canvas.addEventListener('click', onClick);
  g.rotn = 0;
  g.animation = true;
  onResize();
}

window.addEventListener('load', init);
window.addEventListener('resize', onResize, false);
window.addEventListener('orientationchange', onResize, false);