Link
Link

Reputation: 93

webgl how to draw a ring

As shown below, all I can think of is to describe all the points on a circle and then use triangulation to draw a ring with width

enter image description here

I can also think of a way to use overlay. First draw a circle and then draw a circle with a smaller radius

const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
export function arc(x0: number, y0: number, radius: number, startAng = 0, endAng = Math.PI * 2) {
  const ang = Math.min(TAU, endAng - startAng);
  const ret = ang === TAU ? [] : [x0, y0];
  const segments = Math.round(TAU_SEGMENTS * ang / TAU);
  for(let i = 0; i <= segments; i++) {
    const x = x0 + radius * Math.cos(startAng + ang * i / segments);
    const y = y0 + radius * Math.sin(startAng + ang * i / segments);
    ret.push(x, y);
  }
  return ret;
}

const gl = container.current.getContext("webgl2");

const program = initProgram(gl);

const position = new Float32Array(arc(0, 0, 1).concat(...arc(0, 0, 0.9)));

let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);

let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);

gl.drawArrays(gl.LINE_STRIP, 0, position.length / 2);

The final effect of the code I wrote is as follows. May I ask how I should modify it to become the same as the above picture

enter image description here

Upvotes: 1

Views: 605

Answers (1)

Rabbid76
Rabbid76

Reputation: 211166

You have to add a color attributes for the vertices and you have to draw a gl.TRIANGLE_STRIP primitive instead of a gl.LINE_STRIP primitive.

The color can be calculated from the angle. Map the angle from the range [0, PI] to the range [0, 1] and use the formula for the hue value from the HSL and HSV color space:

function HUEtoRGB(hue)  {
    return [
        Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
        Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
        Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
    ];
}

Create vertices in pairs for the inner and outer arcs with the corresponding color attribute:

const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
    const ang = Math.min(TAU, endAng - startAng);
    const position = ang === TAU ? [] : [x0, y0];
    const color = []
    const segments = Math.round(TAU_SEGMENTS * ang / TAU);
    for(let i = 0; i <= segments; i++) {
        const angle = startAng + ang * i / segments;
        const x1 = x0 + innerRadius * Math.cos(angle);
        const y1 = y0 + innerRadius * Math.sin(angle);
        const x2 = x0 + outerRadius * Math.cos(angle);
        const y2 = y0 + outerRadius * Math.sin(angle);
        position.push(x1, y1, x2, y2);
        let hue = (Math.PI/2 - angle) / (2 * Math.PI);
        if (hue < 0) hue += 1;
        const rgb = HUEtoRGB(hue);
        color.push(...rgb);
        color.push(...rgb);
    }
    return { 'position': position, 'color': color };
}

Create a shader with a color attribute and pass the color attribute from the vertex to the fragment shader:

#version 300 es
precision highp float;
in vec2 position;
in vec4 color;
out vec4 vColor;

void main()
{
    vColor = color;
    gl_Position = vec4(position.xy, 0.0, 1.0);
}
#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;

void main() 
{
    fragColor = vColor;
}

Vertex specification:

const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);

let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);

let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);

Complet and runnable example:

const canvas = document.getElementById( "ogl-canvas");
const gl = canvas.getContext("webgl2");

const program = gl.createProgram();
for (let i = 0; i < 2; ++i) {
    let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
    let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
    gl.shaderSource(shaderObj, source);
    gl.compileShader(shaderObj);
    let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
    if (!status) alert(gl.getShaderInfoLog(shaderObj));
    gl.attachShader(program, shaderObj);
    gl.linkProgram(program);
}
status = gl.getProgramParameter(program, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(program));
gl.useProgram(program);

function HUEtoRGB(hue)  {
    return [
        Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)),
        Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))),
        Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0)))
    ];
}

const TAU_SEGMENTS = 360;
const TAU = Math.PI * 2;
function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) {
    const ang = Math.min(TAU, endAng - startAng);
    const position = ang === TAU ? [] : [x0, y0];
    const color = []
    const segments = Math.round(TAU_SEGMENTS * ang / TAU);
    for(let i = 0; i <= segments; i++) {
        const angle = startAng + ang * i / segments;
        const x1 = x0 + innerRadius * Math.cos(angle);
        const y1 = y0 + innerRadius * Math.sin(angle);
        const x2 = x0 + outerRadius * Math.cos(angle);
        const y2 = y0 + outerRadius * Math.sin(angle);
        position.push(x1, y1, x2, y2);
        let hue = (Math.PI/2 - angle) / (2 * Math.PI);
        if (hue < 0) hue += 1;
        const rgb = HUEtoRGB(hue);
        color.push(...rgb);
        color.push(...rgb);
    }
    return { 'position': position, 'color': color };
}

const attributes = arc(0, 0, 0.6, 0.9);
position = new Float32Array(attributes.position);
color = new Float32Array(attributes.color);

let position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
let gl_position = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_position);

let color_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW);
let gl_color = gl.getAttribLocation(program, "color");
gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(gl_color);

gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight];
vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];

gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, attributes.position.length / 2);
<script id="draw-shader-vs" type="x-shader/x-vertex">#version 300 es
precision highp float;

in vec2 position;
in vec4 color;
out vec4 vColor;

void main()
{
    vColor = color;
    gl_Position = vec4(position.xy, 0.0, 1.0);
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">#version 300 es
precision highp float;

in vec4 vColor;
out vec4 fragColor;

void main() 
{
    fragColor = vColor;
}
</script>
<canvas id="ogl-canvas" style="border: none"></canvas>

Upvotes: 3

Related Questions