需求:
看了一段时间WebGL,现在想画一个圆,方式应该有很多,我现在列出自己练习的几个
分析:
WebGL只能画点、线、三角形,我现在要画一个圆,基本上就只能靠三角形模拟出来一个
下表的图形都是通过三角形来构造的“正多边形”几何体
发现只要三角形数量足够多,“正多边形”会越来越倾向于“圆”。
6个三角形 | 15个三角形 | 36个三角形 |
具体实施方案:
为了简要表达,会省略不重要的代码
方案1和方案2使用的绘画方式为
gl.drawArrays(gl.TRIANGLE_FAN, 0, n);
方案1:JavaScript中直接构造好点的坐标
直接在JavaScript中算好各个点的坐标,然后赋值给顶点着色器渲染
代码比较简单,主要注意一下相关个数
//生成顶点着色器需要的所有点的位置
function initVertexBuffers(gl) {
let circleCenter = [0, 0];
let n = 36;
let stepAngle = 360 / n;
let arr = [circleCenter[0], circleCenter[1]];
for (let i = 0; i < n; i++) {
let xy = getXYByIndex(i, stepAngle);
let {x, y} = xy;
arr.push(x);
arr.push(y);
}
//如果没有下面3行代码,会出现一个缺口
let xyRight = getXYByIndex(0, n);
arr.push(xyRight.x);
arr.push(xyRight.y);
let verticesColors = new Float32Array(arr);
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
let a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
return n + 2;
}
上述代码用到了一个自定义函数
function getXYByIndex(index, stepAngle) {
let pai = 3.1415926;
let circleRadius = 0.6;
let angle = stepAngle * index;
let angleInRadian = angle * pai / 180;
let x = Math.cos(angleInRadian) * circleRadius;
let y = Math.sin(angleInRadian) * circleRadius;
return {x, y}
}
备注:代码逻辑不复杂,当要放到博客中,心里对代码质量就有点要求了,此时看上述2段代码,心里不是很满意。
方案2:JavaScript中只算好等间隔的角度,通过角度在顶点着色器中计算出坐标
顶点着色器如下
//这里定义了pai,而不使用glsl中的radians,是为了和1.1中的方案进行对比,
//用一个统一的pai,减少对比时候的其他干扰
#define pai 3.1415926
precision mediump float;
attribute float a_Angle;//圆上的某个点相对于Z轴的旋转角度
void main()
{
//下述的180.0,如果去掉小数点以及其后的0,那么就会报错
float x=cos(a_Angle * pai / 180.0);
float y=sin(a_Angle * pai / 180.0);
gl_Position=vec4(x, y, 0.0, 1.0);
}
JavaScript中生成“角度数组”,并传给顶点着色器
function initAngles(gl) {
let n = 36;
let step = 360 / n;
let arr = [];
for (let i = 0; i < n; i++) {
arr.push(step * i);
}
let verticesColors = new Float32Array(arr);
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
let a_Angle = gl.getAttribLocation(gl.program, "a_Angle");
gl.vertexAttribPointer(a_Angle, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Angle);
return n;
}
方案3:顶点着色器绘制一个和canvas重合的矩形,利用gl_FragCoord的坐标来对每个片元进行圆内筛选
让我觉得可以在片元着色器中来画圆的想法,是来自于片元着色器中的gl_FragCoord对象
gl_FragCoord.xyz可以读取每个像素的x、y、z坐标
拿到坐标,我就可以根据distance()方法来画圆
因为顶点坐标是骨架,再借鉴shaderToy的做法,实现思路
先在顶点着色器中画一个覆盖整个canvas的一个矩形
然后在片元着色器中来画圆
precision mediump float;
void main(){
//canvas是一个400px*400px的矩形,我要在中心点画一个圆
vec2 center=vec2(200, 200);
//gl_FragCoord是一个二维坐标系,坐标原点位于canvas的左下角
vec2 uv=gl_FragCoord.xy;
//画一个半径为150px的圆
bool isInCircle=distance(center, uv)<=150.0;
if (isInCircle){
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
} else {
//如果没有下述语句,即使设置了gl.clearColor(),圆外底色会变成白色
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}