Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】

时间:2021-05-05 23:40:58

本文主要参考自CandyCat的博客


1.点采样的抗锯齿

先来看看效果图:

Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】

上图一共分为四个部分,

左一是普通的纹理采样,直接使用顶点着色器传入的插值后的UV坐标对纹理采样;

左二是普通的点采样,因为左一显然太模糊,所以我们使用floor函数确保采样的是UV对应的纹理上某个定点像素的颜色,之所以各加上0.5偏移是为了和左一、左三、左四对齐;

左三使用了fwidth+smoothstep进行点采样,fwidth的效果相当于

w = fwidth(uv); // 同上
w.x = abs(dFdx(uv).x);
w.y = abs(dFdy(uv).y);

dFdx、dFdy就是分别对x,y方向计算差值

Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】


fwidth 的返回值表明UV值在该点和临近像素之间的变化(其实计算的是简化版的梯度向量长度),这个值帮助我们判断模糊的大小范围。最后根据UV的小数部分进行模糊;

Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】

【更精确的计算是 Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】,即 Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】


左四使用了fwidth+clamp进行点采样,同样是对UV值 floor 之后,加上0.5偏移,然后使用了除法比较来计算模糊区间。clamp方法的是将模糊边界直接截断在[0, 1]区间。
从效果上来看,比左三模糊程度轻一点,但是计算量也会比左三小一点;


RenderMonkey的Fragment Shader 源码如下:

const vec2 iResolution = vec2(640., 640.);const vec2 texSize = vec2(256., 256.);uniform float iGlobalTime;uniform sampler2D iChannel0;float split=floor(iResolution.x/4.);vec4 AntiAlias_None(vec2 uv){   return texture2D(iChannel0, (uv) / texSize, 0.0);}vec4 AntiAliasPointSampleTexture_Shift(vec2 uv) {     return texture2D(iChannel0, (floor(uv+0.5)+0.5) / texSize, 0.0);}vec4 AntiAliasPointSampleTexture_Smoothstep(vec2 uv) {      vec2 w;//=fwidth(uv);   w.x = abs(dFdx(uv)) + abs(dFdy(uv));//同上   w.y = abs(dFdx(uv)) + abs(dFdy(uv));   return texture2D(iChannel0, (floor(uv)+0.5 + smoothstep(0.5-w,0.5+w,fract(uv))) / texSize, 0.0);   }vec4 AntiAliasPointSampleTexture_Linear(vec2 uv) {      vec2 w=fwidth(uv);   return texture2D(iChannel0, (floor(uv)+0.5 + clamp((fract(uv)-0.5+w)/w,0.,1.)) / texSize, 0.0);   }   void main(void){   vec2 uv = gl_FragCoord.xy;      // split line   if (floor(uv.x)==split || floor(uv.x)==split*2. || floor(uv.x)==split*3.)    {       gl_FragColor=vec4(1.);       return ;    }      // rotate the uv with time         float c=cos(iGlobalTime*0.1), s=sin(iGlobalTime*0.1);   uv=uv*mat2(c,s,-s,c)*0.025;   // 旋转像素并放大40倍      // sample the texture!   float anisotest=1.0; // 可以对像素进行拉伸   uv*=vec2(1.0, anisotest);      if (gl_FragCoord.x<split)      gl_FragColor = AntiAlias_None(uv);      else if (gl_FragCoord.x<split*2.)      gl_FragColor = AntiAliasPointSampleTexture_Shift(uv);   else if (gl_FragCoord.x<split*3.)      gl_FragColor = AntiAliasPointSampleTexture_Smoothstep(uv);      else      gl_FragColor = AntiAliasPointSampleTexture_Linear(uv);   }

注意:texture2D的最后一个参数bias,用于计算LOD(level-of-detail),LOD则是用于指定纹理采样的Mimaps等级的。


2.程序纹理的抗锯齿

(1)效果如图:

Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】

主要通过 fiwidth函数 确定了模糊区间,再通过 smoothstep函数 进行插值,再通过 mix函数 与目标颜色进行混合

const vec2 iResolution = (640.0, 640.0);uniform vec2 fMouseCoordsXY;vec2 iMouse = vec2(fMouseCoordsXY.x, iResolution.y - fMouseCoordsXY.y); ❤ 将RenderMonkey的鼠标y轴反向uniform float iGlobalTime;// 点p到直线AB的距离float line( in vec2 a, in vec2 b, in vec2 p ){   vec2 pa = p - a;   vec2 ba = b - a;   float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );   return length( pa - ba*h );}void main(void){   vec2 p = (-iResolution.xy + 2.0*gl_FragCoord.xy) / iResolution.yy;   vec2 q = p;      vec2 c = vec2(0.0);   if( fMouseButtonStateLeft > 0.0 )       c=(-iResolution.xy + 2.0*iMouse.xy) / iResolution.yy; // 换算到[-1, 1]的区间       // 背景颜色      vec3 col = vec3(0.5,0.85,0.9)*(1.0-0.2*length(p));   if( q.x>c.x && q.y>c.y )       col = pow(col,vec3(2.2)); // 颜色加深    // 通过sin函数控制放大和缩小动画      p *= 1.0 + 0.2*sin(iGlobalTime*0.4);      // 计算点p到直线集合的距离   float d = 1e20;      for( int i=0; i<7; i++ )   {      float anA = 6.2831*float(i+0)/7.0 + 0.15*iGlobalTime;      float anB = 6.2831*float(i+3)/7.0 + 0.20*iGlobalTime;      vec2 pA = 0.95*vec2( cos(anA), sin(anA) );            vec2 pB = 0.95*vec2( cos(anB), sin(anB) );            float h = line( pA, pB, p );      d = min( d, h );   }    // 绘制直线,屏幕左侧,没有抗锯齿   if( q.x<c.x )   {      if( d<0.12 ) col = vec3(0.0,0.0,0.0); // 外轮廓颜色:黑色       if( d<0.04 ) col = vec3(1.0,0.6,0.0); // 内部颜色:橙色   }    // 绘制直线,屏幕右侧,有抗锯齿   else   {      float w = 0.5*fwidth(d);       w *= 1.5; // extra blur      // ❤ 通过fiwidth确定了模糊区间,再通过smoothstep进行插值,再通过mix函数与目标颜色进行混合      if( q.y<c.y )      {         col = mix( vec3(0.0,0.0,0.0), col, smoothstep(-w,w,d-0.12) ); // black         col = mix( vec3(1.0,0.6,0.0), col, smoothstep(-w,w,d-0.04) ); // orange      }      else      {         col = mix( pow(vec3(0.0,0.0,0.0),vec3(2.2)), col, smoothstep(-w,w,d-0.12) ); // black         col = mix( pow(vec3(1.0,0.6,0.0),vec3(2.2)), col, smoothstep(-w,w,d-0.04) ); // orange      }   }      if( q.x>c.x && q.y>c.y )      col = pow( col, vec3(1.0/2.2) );       // 绘制横竖的分割线   col = mix( vec3(0.0), col, smoothstep(0.007,0.008,abs(q.x-c.x)) );   col = mix( col, vec3(0.0), (1.0-smoothstep(0.007,0.008,abs(q.y-c.y)))*step(0.0,q.x-c.x) );         gl_FragColor = vec4( col, 1.0 );}


注意:
1. smoothstep函数 和 step函数在 x[i] < edge[i] 时返回0,反之则返回 1.0 ;
2. mix函数的混合公式为 x×(1−a)+y×a。


(2)效果图:

Shader特效——实现“抗锯齿(AntiAliasing)”【GLSL】

GLSL代码及注释
/* There are a few shaders on this site already that attempt to do this (most notably * <https://www.shadertoy.com/view/ldlSzS>), but none of them were quite what I wanted, * so I made this one myself. * * It has an interface almost as simple as texture2D's, with only one extra parameter * for the resolution of the texture. You don't need to pass in the screen resolution * or anything, since it uses dFdx and dFdy. I haven't tested this, but it should also * work on textures that have been transformed in complex ways, such as ones in 3D * environments. * It doesn't use fwidth. It uses the Pythagorean Theorem instead. I don't really get * why fwidth doesn't just work like that in the first place... Maybe fwidth has some * hidden meaning that I don't understand? In any case, it isn't the right function * for this. * It also has no branching and only calls texture2D once, using the GPU's built-in * bilinear interpolation. * * You can honestly just lift the v2len and textureBlocky functions out of this shader, * put them into your own, and use them. It's very easy. * * Since copyright law exists, I guess I ought to put this under a license of some kind * if I want you to be able to use it. It's tiny and actually rather self-evident, so * I'll put it under the CC0 1.0 Public Domain Dedication: * <http://creativecommons.org/publicdomain/zero/1.0/> * Ta-dahhh! Now you can use it however you want without even giving me credit. */// change this value to compare different interpolation methods.//// 0: antialiased blocky interpolation// 1: linear interpolation// 2: aliased blocky interpolation#define RENDER_MODE 0// basically calculates the lengths of (a.x, b.x) and (a.y, b.y) at the same timevec2 v2len(in vec2 a, in vec2 b){	return sqrt(a*a+b*b);}// samples from a linearly-interpolated texture to produce an appearance similar to// nearest-neighbor interpolation, but with resolution-dependent antialiasing//// this function's interface is exactly the same as texture2D's, aside from the 'res'// parameter, which represents the resolution of the texture 'tex'.vec4 textureBlocky(in sampler2D tex, in vec2 uv, in vec2 res){	uv *= res; // enter texel coordinate space.	vec2 seam = floor(uv+.5);// find the nearest seam between texels.	// here's where the magic happens. scale up the distance to the seam so that all	// interpolation happens in a one-pixel-wide space.	uv = (uv-seam)/v2len(dFdx(uv),dFdy(uv))+seam;	uv = clamp(uv, seam-.5, seam+.5);// clamp to the center of a texel.	return texture2D(tex, uv/res, -1000.);// convert back to 0..1 coordinate space.}// simulates nearest-neighbor interpolation on a linearly-interpolated texture//// this function's interface is exactly the same as textureBlocky's.vec4 textureUgly(in sampler2D tex, in vec2 uv, in vec2 res){	return texture2D(tex, (floor(uv*res)+.5)/res, -1000.);}#define fragCoord gl_FragCoord#define fragColor gl_FragColor#define mainImage mainvoid mainImage(out vec4 fragColor, in vec2 fragCoord){	vec2 uv = (fragCoord.xy * 2. - iResolution.xy) / min(iResolution.x, iResolution.y);	float theta = iGlobalTime / 12.;	vec2 trig = vec2(sin(theta), cos(theta));	uv *= mat2(trig.y, -trig.x, trig.x, trig.y) / 8.;#if RENDER_MODE == 0	fragColor = textureBlocky(iChannel0, uv, iChannelResolution[0].xy);#elif RENDER_MODE == 1	fragColor = texture2D(iChannel0, uv);#elif RENDER_MODE == 2	fragColor = textureUgly(iChannel0, uv, iChannelResolution[0].xy);#endif}






注意:
1. smoothstep函数 和 step函数在 x[i] < edge[i] 时返回0,反之则返回 1.0 ;
2. mix函数的混合公式为 x×(1−a)+y×a。