Change color in middle of circle

2019-08-03 21:49发布

I'm new to WebGL and I'm trying to create a black ring in the middle of this green circle without making additional circles. I believe I can do this by making the normal of those triangles go the other way but I'm not sure exactly how to do this. My friend suggested changing the texture coordinates but I don't really understand how this would help. Can anyone shine some light on these ideas and possible intuition?

_______HTML File__________

<!DOCTYPE html>
<html>
<head>

<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;

void
main()
{
    gl_Position = vPosition;
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;

void
main()
{
    gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );
}
</script>

<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="Circle.js"></script>
</head>

<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
</html>

_____Javascript File______

var gl;
var points;


window.onload = function init()
{
    var canvas = document.getElementById( "gl-canvas" );

    gl = WebGLUtils.setupWebGL( canvas );
    if ( !gl ) { alert( "WebGL isn't available" ); }


    // The Vertices
    var pi = 3.14159;
    var x = 2*pi/100;
    var y = 2*pi/100;
    var r = 0.9;

    points = [ vec2(0.0, 0.0) ]; //establish origin 

    //for loop to push points
    for(var i = 0; i < 100; i++){
        points.push(vec2(r*Math.cos(x*i), r*Math.sin(y*i)));
        points.push(vec2(r*Math.cos(x*(i+1)), r*Math.sin(y*(i+1))));
    }

    //
    //  Configure WebGL
    //
    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.clearColor( 0.3, 0.3, 0.3, 1.0 );

    //  Load shaders and initialize attribute buffers

    var program = initShaders( gl, "vertex-shader", "fragment-shader" );
    gl.useProgram( program );

    // Load the data into the GPU

    var bufferId = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufferId );
    gl.bufferData( gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW );

    // Associate out shader variables with our data buffer

    var vPosition = gl.getAttribLocation( program, "vPosition" );
    gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vPosition );

    render();
};


function render() {
    gl.clear( gl.COLOR_BUFFER_BIT );
    gl.drawArrays( gl.TRIANGLE_FAN, 0, points.length );
}

1条回答
beautiful°
2楼-- · 2019-08-03 22:37

I assembled some part of your task as you requested. I tried to not change your code much, so you can understand all changes I have done. First small show:

You made circle out of 100 points (vertices). Now you want to make another shape inside. It means use another 100 points, which is probably what you don't want to do. Instead of this, you would like to use normals. But from the point of view of shaders (which are responsible for drawing), normals, vertices and other things like texture coordinates are just data and you are the one who decides, if data means vertices, normals, texture coordinates or anything else.

If I understand good, you want to customize your object without adding too much additional data. I don't think normals or textures can help you.

There are few problems you will have to face with texture ...

  • First is, if circle will be too big (close to you), then it will be not that nice with just 100 points.
  • If circle will be too small (far from you), but there will be a lot circles, you will use too many points for nothing which will lower performance.
  • If you use texture for black ring inside, it will be fuzzy if you will be closer.
  • And if you use too large texture for a lot of small circles, it will again lower performance.

... and normals are used to do light reflection like this.

Way I think about the problem. You can define circle with just few params, radius and center. With webgl, you can draw only triangles (and points). But you can for example customize shader to draw inscribed circle in each triangle.

So I defined just radius and center:

var r = 0.9;
var middle = vec2(0.0, 0.0);

Then I generate 3 points of triangle around the circle (circle is inscribed circle of this new triangle):

function buildCircle(center, r) {
    var points = [];

    points.push(vec2((r * TRI_HEIGHT_MOD * Math.cos(0 * DEG_TO_RAD)) + center[0], (r * TRI_HEIGHT_MOD * Math.sin(0 * DEG_TO_RAD)) + center[1]));
    points.push(vec2((r * TRI_HEIGHT_MOD * Math.cos(120 * DEG_TO_RAD)) + center[0], (r * TRI_HEIGHT_MOD * Math.sin(120 * DEG_TO_RAD) + center[1])));
    points.push(vec2((r * TRI_HEIGHT_MOD * Math.cos(240 * DEG_TO_RAD)) + center[0], (r * TRI_HEIGHT_MOD * Math.sin(240 * DEG_TO_RAD)) + center[1]));

    vertexPositions = points;
}

Then I pass middle, radius and triangle to my shader:

var vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);

program.middle = gl.getUniformLocation(program, "middle");
gl.uniform2f(program.middle, middle[0], middle[1]);

program.r = gl.getUniformLocation(program, "r");
gl.uniform1f(program.r, r);

And then I just render it with same as you do, except I need to allow alpha drawing, because some parts of triangle will be invisible, so it will look as circle:

gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.enable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);

Ok now shaders.

There are few things you really need to know to continue, so please read about it here: http://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html

My vertex shader is same as yours, except I need to pass interpolated vertex position to fragment shader:

varying vec4 pos;

... 

void main() {
    pos = vPosition;

My fragment shader needs to do only one thing and it is to decide, if pixel is in the circle or not. Simple equation: enter image description here

If the left side is smaller then the right side, then pixel is inside the circle. If not, then it is outside, so invisible:

    float inside = pow(pos.r - middle.r, 2.0) + pow(pos.g - middle.g, 2.0);
    if (inside < pow(r, 2.0)) {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
    }

End

So now you might know how to make a circle just from few points. You can use similar way to draw a ring inside. Then you can draw thousands of them in any distance and make them move. Program will be still fast and shapes will be as sharp as possible.

Just one last thing. Usually you dont simplify shapes like that, but sometimes you might. Good example is Bézier curve which might help you to do crazy sharp shapes with just few points. But it all matters what would you like to do. One technique can't solve all problems and you have to keep looking for more solutions.

EDIT 1: "What is var middle = vec2(0.0, 0.0)? I meam, vec2?"

There are 3 other scripts in this question that I replicated in my solution (in jsfiddle on the left: External Resources). It wasnt part of this question, but it was easy to find theirs origin:

<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>

MV.js is some supply javascript with basic math... or algebraic constructs like vectors and matrices. vec2 is function that returns array with length 2. So var middle = [0.0, 0.0]; is exactly the same thing. This is not part of native javascript, so you need some library for it (you don't need it, but it is very useful). I use glmatrix.

On the other hand in shaders, vectors and matrices are native. Find it out on your own in chapter 4.1 Basic Types.

查看更多
登录 后发表回答