在网上找了好久,也没有发现有现成用shader去实现canvas radialGradient效果的.大部分都是简单的只有一个中心圆或者通过canvas绘制渐变再作为纹理图像进行贴图,没有类似像canvas有内圆与外圆,两圆心位置不一样,可以用实现类似焦点视锥效果。然后想到canvas底层用的是skia,就去看了一下skia源码(过于复杂和抽象,不建议直接阅读,可以调试进入熟悉执行顺序和目录功能结构),后面在skia官网搜索了一篇关于Two-point Conical Gradient的介绍一些算法,但是我感觉有点复杂。
渐变效果(线性渐变、径向、圆锥)都是通过求t,然后通过t计算colorstop它们的插间值:
struct RadialGredient{
float x0;
float y0;
float r0;
float x1;
float y1;
float r1;
vec4 colorStops[3];
};
float sdfRect(vec2 uv,vec2 center,vec2 size){
vec2 halfSize=size/2.;
vec2 offset=abs(uv-center)-halfSize;
return length(max(offset,0.))+min(max(offset.x,offset.y),0.);
}
// 投影坐标系
vec2 ProjectionCoord(in vec2 coord, in float scale) {
return scale * 2. * (coord - 0.5 * iResolution.xy) / min(iResolution.x, iResolution.y);
}
// 坐标轴
vec4 AxisHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * axisWidth;
float dy = dFdy(coord.y) * axisWidth;
if(abs(coord.x) < dx) {
color = yAxisColor;
} else if(abs(coord.y) < dy) {
color = xAxisColor;
}
return color;
}
// 栅格
vec4 GridHelper(in vec2 coord, in float gridWidth, in vec4 gridColor) {
vec4 color = vec4(0, 0, 0, 0);
float dx = dFdx(coord.x) * gridWidth;
float dy = dFdy(coord.y) * gridWidth;
vec2 fraction = fract(coord);
if(fraction.x < dx || fraction.y < dy) {
color = gridColor;
}
return color;
}
// 投影坐标系辅助对象
vec4 ProjectionHelper(in vec2 coord, in float axisWidth, in vec4 xAxisColor, in vec4 yAxisColor, in float gridWidth, in vec4 gridColor) {
// 坐标轴
vec4 axisHelper = AxisHelper(coord, axisWidth, xAxisColor, yAxisColor);
// 栅格
vec4 gridHelper = GridHelper(coord, gridWidth, gridColor);
// =投影坐标系
return bool(axisHelper.a) ? axisHelper : gridHelper;
}
vec3 interplateColor(vec4 colorStops[3],float t){
vec3 col=colorStops[0].rgb;
float colorStop0=colorStops[0].w;
for(int i=1;i<3;i++){
vec3 currentColor=colorStops[i].rgb;
float colorStop1=colorStops[i].w;
float ct=clamp((t-colorStop0)/(colorStop1-colorStop0),0.,1.);
col=mix(col,currentColor,ct);
colorStop0=colorStop1;
}
return col;
}
float interplate(float start,float end,float value){
return clamp((value-start)/(end-start),0.,1.);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = ProjectionCoord(fragCoord,5.);
vec3 col=ProjectionHelper(uv,2.,vec4(1,0,0,1),vec4(0,1,0,1),1.,vec4(1)).rgb;
float len=sdfRect(uv,vec2(1,1),vec2(2));
RadialGredient gredient;
gredient.x0=1.;
gredient.y0=1.;
gredient.r0=0.;
gredient.x1=1.;
gredient.y1=1.;
gredient.r1=1.;
gredient.colorStops[0]=vec4(1,0,0,0.);
gredient.colorStops[1]=vec4(0,1,0,0.5);
gredient.colorStops[2]=vec4(0,0,1,1.);
if(len<0.){
float r1=gredient.r0; // 起始圆半径
float r2=gredient.r1; // 结圆半径
// 起始圆
vec2 c1=vec2(gredient.x0,gredient.y0);
// 结束圆
vec2 c2=vec2(gredient.x1,gredient.y1);
vec2 cd=c2-c1;
vec2 pd=uv-c1;
float dr=r2-r1;
float mindr=-1.*r1;
float A=cd.x*cd.x+cd.y*cd.y-dr*dr;
float B=pd.x*cd.x+pd.y*cd.y+r1*dr;
float C=pd.x*pd.x+pd.y*pd.y-r1*r1;
float inva;
float t=1. / 2. * C / B;
if(A!=0.){
inva=1.*1./A;
}
if(A==0.){
if(t*dr>=mindr){
col=interplateColor(gredient.colorStops,t);
}
}
else{
float discr = B*B - A * C;
if(discr>=0.){
float sqrtdiscr=sqrt(discr);
float t0=(B+sqrtdiscr)*inva;
float t1=(B-sqrtdiscr)*inva;
if(t0*dr>=mindr){
col=interplateColor(gredient.colorStops,t0);
} else if(t1*dr>=mindr){
col=interplateColor(gredient.colorStops,t1);
}
}
}
}
fragColor = vec4(col, 1);
}
内圆半径不为0时:
中心位置一样时,效果
内圆与外圆的中心位置不一样的效果
代码: