本文主要参考自CandyCat的博客
1.点采样的抗锯齿
先来看看效果图:
上图一共分为四个部分,
左一是普通的纹理采样,直接使用顶点着色器传入的插值后的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方向计算差值
fwidth 的返回值表明UV值在该点和临近像素之间的变化(其实计算的是简化版的梯度向量长度),这个值帮助我们判断模糊的大小范围。最后根据UV的小数部分进行模糊;
【更精确的计算是 ,即 】
左四使用了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)效果如图:
主要通过 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)效果图:
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。