【ShaderToy】基础篇之再谈抗锯齿(antialiasing,AA)

写在前面

在之前的基础篇中,我们讲到了在绘制点线时如何处理边缘的锯齿,也就是使用smoothstep函数。而模糊参数是一些定值,或者是跟屏幕分辨率相关的数值,例如分辨率宽度的5%等等。但这种方法其实是有一种问题的。这需要我们从绘制的图像说起。

ShaderToy中绘制的很多图像可以说是一种Procedure Texture,过程纹理,即是计算机生成的纹理。拿之前画的圆和线来说,这些圆和线的绘制过程,是我们计算每个fragment到“期望图像”的距离,然后根据距离来判断使用哪种颜色。如果这个距离是在欧式空间,即圆和线的例子中那样,那么直接使用定制或者和屏幕系数相关的值作为模糊参数是没有问题的。但一旦这个距离无法用欧氏距离来表示(有很多这样的情况,例如判断到一条正弦曲线的距离,就无法使用点到直线的距离这样简单的公式了),使用这种参数是无法得到很好的模糊效果的。在ShaderToy中,很多处理方法其实就是在进行图片处理工作。

这种时候,我们就可以利用另一个函数fwidth,和smoothstep等函数合作来达到抗锯齿的目的,而这种方法实际上更加通用。这里还将介绍一种新的函数clamp。

本篇涉及到的原ShaderToy中的例子主要有:

https://www.shadertoy.com/view/4ssSRl

https://www.shadertoy.com/view/ldsSRX

https://www.shadertoy.com/view/ldlSzS

https://www.shadertoy.com/view/MsjSzy

我整理的效果如下:

https://www.shadertoy.com/view/XtB3zw

https://www.shadertoy.com/view/4tB3zm

以及GPU Gems的一篇文章:

http://http.developer.nvidia.com/GPUGems/gpugems_ch24.html

纹理采样的抗锯齿

我们一定听说过抗锯齿这件事情,而不出意外的话,我们肯定也听说过多重采样这个抗锯齿途径。多重采样简单来说就是像素点的颜色是和其相邻的一些像素值有关的。我们把多重采样再一般化,实际上是是一个对图像求卷积的过程。输入图像A,经过某个定义的kernel进行卷积处理后,得到B,我们希望B中的锯齿尽可能少。很多GPU已经提供了一些快速的滤波操作,例如线性滤波等等。多重采样使用的kernel往往是一个长方形,每个点对应的权重是一样的,然后对原图像进行卷积,就可以输出抗锯齿后的图像B了。但是如果我们想要进行更复杂的滤波,就需要自定义kernel。当然,kernel的用处不仅仅是抗锯齿,还可以进行边界检测等等,我后面的文章可能会讲到。这里,我们仅讨论用于抗锯齿的kernel。

举个例子,我们要渲染一个有纹理的长方形,当知道了该像素对应的纹理UV坐标后,如果我们直接利用texture2D访问纹理,那么最终在长方形的边界就会出现明显锯齿。而如果我们利用多重采样的思想,就会在每次计算时,不仅仅对该像素对应的UV进行采样,还会调用多次texture2D函数,最后将结果相加取平均值。

常见的抗锯齿kernel有Box、Cubic等等。更多内容可以参考上面的GPU Gems中的文章。

复杂的滤波操作大都只依赖于,“我们应该对图像滤波多少(也可以理解成模糊多大的范围)”。很容易理解,例如在如果相邻像素的UV值是一样的,那么我们就不需要采样啦,也就是说,这时可以不进行滤波。而现代很多GPU提供了偏导数函数给我们,在GLSL中,这个函数就是fwidth。当调用这个函数时,我们相当于问GPU:“嘿伙计,我想知道,参数这个值在屏幕的横纵方向的像素之间变化了多少?”fwidth函数返回的是X和Y方向偏导数的绝对值的和,而单方向的偏导可以通过ddx和ddy函数得到,这里不涉及了。当然,这些函数由于和像素有关,因此只能在Fragment Shader中访问。

例如,当我们写下fwidth(myVar)时,GPU将会返回myVar这个值在当前像素和它的下一个相邻像素之间的差值(与X和Y方向上的下一个像素上该值差的绝对值和)。也就是说,这个值其实就是直线的线性差值。一旦我们知道了在当前像素上这个值的变化程度,我们就可以进行合适程度的滤波操作。因此,对于一张纹理才说,当我们给定它的UV坐标后,更恰当的方法是不仅仅用这个UV坐标直接采样,还应该考虑其周围方形区域内纹理采样的结果,而这个区域就是ddx和ddy给定的区域。幸运的是,当我们调用tex2D这样的函数时,系统背后已经为我们完成了这个操作。而在一些高级的profiles中,还会允许我们自定义滤波窗口的大小。我们以Unity中的代码为例:

float4 fragColor = tex2D(_MainTex, i.uv);

上述代码等价于:

float4 fragColor = tex2D(_MainTex, i.uv, ddx(i.uv), ddy(i.uv));

我们可以自定义滤波大小:

float4 fragColor = tex2D(_MainTex, i.uv, float2(0), float2(0));

上述代码意味着,我们强制滤波大小为0,也就是说,总是使用最近邻(Nearest-Neighbor)方法进行滤波,不考虑API状态和mipmaps。下面的图分别表示了滤波大小为*1,*5和*0的效果,注意场景视图中mipmap的使用情况,在大小为0是没有使用任何mipmap。

  

对于不支持这些函数的硬件,可以使用其他方法代替,有兴趣的可以看这篇文章:

http://http.developer.nvidia.com/GPUGems/gpugems_ch25.html

Point Style Sampling

上面的方法在point style sampling时同样可以抗锯齿,可以参见我在ShaderToy中写的一个合并版:https://www.shadertoy.com/view/XtB3zw。实现细节也请移步去那里看。这里只讲关键部分。下面表示了不同放缩程度下的效果:

 

这里我只分析下里面用到的不同的采样方法(依次对应从左到右的顺序)。代码如下:

vec4 AntiAlias_None(vec2 uv, vec2 texsize) {
    return texture2D(iChannel0, uv / texsize, -99999.0);
}

vec4 AntiAliasPointSampleTexture_None(vec2 uv, vec2 texsize) {
	return texture2D(iChannel0, (floor(uv+0.5)+0.5) / texsize, -99999.0);
}

vec4 AntiAliasPointSampleTexture_Smoothstep(vec2 uv, vec2 texsize) {
	vec2 w=fwidth(uv);
	return texture2D(iChannel0, (floor(uv)+0.5+smoothstep(0.5-w,0.5+w,fract(uv))) / texsize, -99999.0);
}

vec4 AntiAliasPointSampleTexture_Linear(vec2 uv, vec2 texsize) {
	vec2 w=fwidth(uv);
	return texture2D(iChannel0, (floor(uv)+0.5+clamp((fract(uv)-0.5+w)/w,0.,1.)) / texsize, -99999.0);
}

vec4 AntiAliasPointSampleTexture_ModifiedFractal(vec2 uv, vec2 texsize) {
    uv.xy -= 0.5;
	vec2 w=fwidth(uv);
	return texture2D(iChannel0, (floor(uv)+0.5+min(fract(uv)/min(w,1.0),1.0)) / texsize, -99999.0);
}

  • 左一:AntiAlias_None对应的是最普通的纹理采样,直接使用Vertex传过来的插值后UV坐标对纹理采样,没有进行point sample。
    从效果来看,放大后的效果过于模糊。
  • 左二:AntiAliasPointSampleTexture_None对应的是普通的点采样,即我们确保采样的是UV对应的纹理上某个定点像素的颜色,这是通过对UV值四舍五入得到整数值,再加上0.5偏移做到的。
    从效果来看,放大后有明显的锯齿,缩小后纹理细节仍很多。
  • 左三:AntiAliasPointSampleTexture_Smoothstep对应用fwidth+smoothstep进行点采样,这里的smoothstep是三次插值方法。在对UV值下取整之后,加上0.5偏移,之后还会利用进行smoothstep抗锯齿。fwidth的返回值表明UV值在该点和临近像素之间的变化,这个值帮助我们判断模糊的大小范围。最后根据UV的小数部分进行模糊。
    从效果上来看,这种方法的模糊程度相比于后三种来说最高(也不一定都是好事)。
  • 左四:AntiAliasPointSampleTexture_Linear对应用fwidth+clamp进行点采样,之所以叫线性方法(Linear)是由于clamp是一个线性插值方法。同样是对UV值下取整之后,加上0.5偏移,然后使用了除法比较来计算模糊区间。clamp方法的是将模糊边界直接与0和1比较得到。
    从效果上来看,效果比上一种模糊程度小一点。
  • 左五:AntiAliasPointSampleTexture_ModifiedFractal和上一种方法很像,不同的是它直接将UV小数部分和w相除判断结果。

总结一下,我们通过点采样的多种方法来演示如何使用fwidth+smoothstep+clamp进行抗锯齿,而这三者的组合也是非常常见的。

过程纹理的抗锯齿

对于使用过程纹理(Procedure Texture)的shaders来说,知道上面的知识是有助于我们理解如何进行抗锯齿的。

过程纹理往往都是通过一些函数来得到的,像ShaderToy中那样,因此抗锯齿往往就需要在像素间评估函数的值来实现。最简单的情况就是,我们可以在边界处取平均值,像多重采样那样。

考虑我们之前圆和直线的例子,其实就是只考虑了边界。当我们判断该像素在边界时,就将圆的颜色和其他颜色进行混合。但是,如一开始所说,这种抗锯齿方法仅适用于根据欧氏距离判断边界的情况,对于那些不能用欧式距离表示的距离函数来说,这种方法在边界处还是会产生锯齿。

我们还是来画画直线和圆好啦。只是这次我们要画一个由线段和圆组成的正四面体:线段组成了四面体的六条边,每个顶点用圆来表示。为了更具有一般性,我们改变一下之前计算直线和圆的函数:

        float line(vec2 pos, vec2 point1, vec2 point2, float width) {
    		vec2 dir0 = point2 - point1;
			vec2 dir1 = pos - point1;
			float h = clamp(dot(dir0, dir1)/dot(dir0, dir0), 0.0, 1.0);
			return (length(dir1 - dir0 * h) - width * 0.5);
        }

        float circle(vec2 pos, vec2 center, float radius) {
        	float d = length(pos - center) - radius;
        	return d;
        }

这里只返回距离值,不计算颜色,以便后面我们对比不同的反锯齿效果。绘制函数也有所改变:

			vec2 originalPos = (2.0 * fragCoord - iResolution.xy)/iResolution.yy;
			vec2 pos = originalPos;

			// Twist
//			pos.x += 0.5 * sin(5.0 * pos.y);

			// Background
			vec3 col = _BackgroundColor.rgb * (1.0-0.2*length(originalPos));

			float speed = 0.2;
			float l = 0.8;
			vec3 p0 = vec3(l, 0, pi * 0.5);
			vec3 p1 = vec3(l, pi * 0.5, pi);
			vec3 p2 = vec3(l, pi * 0.5, pi + pi * 0.66);
			vec3 p3 = vec3(l, pi * 0.5, pi + pi * 1.33);

			vec2 point0 = vec2(cos(p0.z), sin(p0.z)) * sin(p0.y) * p0.x;
			vec2 point1 = vec2(cos(p1.z), sin(p1.z)) * sin(p1.y) * p1.x;
			vec2 point2 = vec2(cos(p2.z), sin(p2.z)) * sin(p2.y) * p2.x;
			vec2 point3 = vec2(cos(p3.z), sin(p3.z)) * sin(p3.y) * p3.x;

			float d = line(pos, point0, point1, _LineWidth);
			d = min(d, line(pos, point1, point2, _LineWidth));
			d = min(d, line(pos, point2, point3, _LineWidth));
			d = min(d, line(pos, point0, point2, _LineWidth));
			d = min(d, line(pos, point0, point3, _LineWidth));
			d = min(d, line(pos, point1, point3, _LineWidth));
			d = min(d, circle(pos, point0, _CircleRadius));
			d = min(d, circle(pos, point1, _CircleRadius));
			d = min(d, circle(pos, point2, _CircleRadius));
			d = min(d, circle(pos, point3, _CircleRadius));

首先,我们把屏幕坐标变换到(-1,1)之间。然后初始化颜色为背景颜色。接着,我们用球面坐标系表示四面体的四个顶点p0~p3。其中,x分量表示了到球心的距离,y分量表示与Z轴的夹角,z分量表示与X轴的夹角。接着,把球面坐标系映射到XY平面内,得到二维点坐标point0~point3。这样就完成了准备工作。接着,我们以此判断距离六条线段和四个顶点圆的距离,取最小值。

接下来就是抗锯齿的部分:

			if (originalPos.x < split.x) {
				col = mix(_OutlineColor.rgb, col, step(0, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, step(0, d));
			} else if (originalPos.y > split.y) {
				float w = _Antialias;
				col = mix(_OutlineColor.rgb, col, smoothstep(-w, w, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, smoothstep(-w, w, d));
			} else {
				float w = fwidth(0.5*d) * 2.0;
				col = mix(_OutlineColor.rgb, col, smoothstep(-w, w, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, smoothstep(-w, w, d));
			}

			// Draw split lines
			col = mix(vec3(0), col, smoothstep(0.005, 0.007, abs(originalPos.x - split.x)));
			col = mix(col, vec3(0), (1 - smoothstep(0.005, 0.007, abs(originalPos.y - split.y))) * step(split.x, originalPos.x));

为了对比效果,我们把屏幕分割成三个部分:屏幕左半侧不做任何抗锯齿处理,即直接根据距离值(这里还绘制了一条轮廓线,所以多了一行)判断使用哪种颜色;屏幕右半侧又分了两个部分,上半部分使用我们之前的定制处理方法,即根据抗锯齿参数_Antialias来决定模糊的范围;下面部分我们使用新的函数fwidth来处理锯齿。最后两行是用于绘制分割线的。

保存后,我们可以得到下面的效果:

为了更好的效果,我们可以添加时间参数让它旋转起来,并且接受鼠标事件来移动分割线的位置,鼠标事件的使用可以参见更新后的开篇。完整的代码如下:

Shader "shadertoy/AA Line" {
	Properties{
		_CircleRadius ("Circle Radius", Range(0, 0.1)) = 0.05
		_OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.01
		_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
		_LineWidth ("Line Width", Range(0, 0.1)) = 0.01
		_LineColor ("Line Color", Color) = (1, 1, 1, 1)
		_Antialias ("Antialias Factor", Range(0, 0.05)) = 0.01
		_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)

		iMouse ("Mouse Pos", Vector) = (100,100,0,0)
		iChannel0("iChannel0", 2D) = "white" {}
		iChannelResolution0 ("iChannelResolution0", Vector) = (100,100,0,0)
	}

	CGINCLUDE
	 	#include "UnityCG.cginc"
  		#pragma target 3.0
  		#pragma glsl   

  		#define vec2 float2
  		#define vec3 float3
  		#define vec4 float4
  		#define mat2 float2x2
  		#define iGlobalTime _Time.y
  		#define mod fmod
  		#define mix lerp
  		#define atan atan2
  		#define fract frac
  		#define texture2D tex2D
  		// 屏幕的尺寸
  		#define iResolution _ScreenParams
  		// 屏幕中的坐标,以pixel为单位
  		#define gl_FragCoord ((_iParam.srcPos.xy/_iParam.srcPos.w)*_ScreenParams.xy) 

  		#define PI2 6.28318530718
  		#define pi 3.14159265358979
  		#define halfpi (pi * 0.5)
  		#define oneoverpi (1.0 / pi)

  		float _CircleRadius;
  		float _OutlineWidth;
  		float4 _OutlineColor;
  		float _LineWidth;
  		float4 _LineColor;
  		float _Antialias;
  		float4 _BackgroundColor;

  		fixed4 iMouse;
  		sampler2D iChannel0;
  		fixed4 iChannelResolution0;

        struct v2f {
            float4 pos : SV_POSITION;
            float4 srcPos : TEXCOORD0;
        };              

       //   precision highp float;
        v2f vert(appdata_base v) {
        	v2f o;
        	o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
            o.srcPos = ComputeScreenPos(o.pos);
            return o;
        }  

        vec4 main(vec2 fragCoord);

        fixed4 frag(v2f _iParam) : COLOR0 {
        	vec2 fragCoord = gl_FragCoord;
        	return main(gl_FragCoord);
        }  

        float line(vec2 pos, vec2 point1, vec2 point2, float width) {
    		vec2 dir0 = point2 - point1;
			vec2 dir1 = pos - point1;
			float h = clamp(dot(dir0, dir1)/dot(dir0, dir0), 0.0, 1.0);
			return (length(dir1 - dir0 * h) - width * 0.5);
        }

        float circle(vec2 pos, vec2 center, float radius) {
        	float d = length(pos - center) - radius;
        	return d;
        }

		vec4 main(vec2 fragCoord) {
			vec2 originalPos = (2.0 * fragCoord - iResolution.xy)/iResolution.yy;
			vec2 pos = originalPos;

			// Twist
//			pos.x += 0.5 * sin(5.0 * pos.y);

			vec2 split = vec2(0, 0);
			if (iMouse.z > 0.0) {
				split = (-iResolution.xy + 2.0 * iMouse.xy) / iResolution.yy;
			}

			// Background
			vec3 col = _BackgroundColor.rgb * (1.0-0.2*length(originalPos));

			float speed = 0.2;
			float l = 0.8;
			vec3 p0 = vec3(l, 0 + speed * iGlobalTime, pi * 0.5  + speed * iGlobalTime);
			vec3 p1 = vec3(l, pi * 0.5 +speed * iGlobalTime, pi +speed * iGlobalTime);
			vec3 p2 = vec3(l, pi * 0.5 +speed * iGlobalTime, pi + pi * 0.66 + speed * iGlobalTime);
			vec3 p3 = vec3(l, pi * 0.5 +speed * iGlobalTime, pi + pi * 1.33 + speed * iGlobalTime);

			vec2 point0 = vec2(cos(p0.z), sin(p0.z)) * sin(p0.y) * p0.x;
			vec2 point1 = vec2(cos(p1.z), sin(p1.z)) * sin(p1.y) * p1.x;
			vec2 point2 = vec2(cos(p2.z), sin(p2.z)) * sin(p2.y) * p2.x;
			vec2 point3 = vec2(cos(p3.z), sin(p3.z)) * sin(p3.y) * p3.x;

			float d = line(pos, point0, point1, _LineWidth);
			d = min(d, line(pos, point1, point2, _LineWidth));
			d = min(d, line(pos, point2, point3, _LineWidth));
			d = min(d, line(pos, point0, point2, _LineWidth));
			d = min(d, line(pos, point0, point3, _LineWidth));
			d = min(d, line(pos, point1, point3, _LineWidth));
			d = min(d, circle(pos, point0, _CircleRadius));
			d = min(d, circle(pos, point1, _CircleRadius));
			d = min(d, circle(pos, point2, _CircleRadius));
			d = min(d, circle(pos, point3, _CircleRadius));	

			if (originalPos.x < split.x) {
				col = mix(_OutlineColor.rgb, col, step(0, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, step(0, d));
			} else if (originalPos.y > split.y) {
				float w = _Antialias;
				col = mix(_OutlineColor.rgb, col, smoothstep(-w, w, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, smoothstep(-w, w, d));
			} else {
				float w = fwidth(0.5*d) * 2.0;
				col = mix(_OutlineColor.rgb, col, smoothstep(-w, w, d - _OutlineWidth));
				col = mix(_LineColor.rgb, col, smoothstep(-w, w, d));
			}

			// Draw split lines
			col = mix(vec3(0), col, smoothstep(0.005, 0.007, abs(originalPos.x - split.x)));
			col = mix(col, vec3(0), (1 - smoothstep(0.005, 0.007, abs(originalPos.y - split.y))) * step(split.x, originalPos.x));

			return vec4(col, 1.0);
		}

    ENDCG    

    SubShader {
        Pass {
            CGPROGRAM    

            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest     

            ENDCG
        }
    }
    FallBack Off
}

想看动态效果的可以直接看ShaderToy中的效果:https://www.shadertoy.com/view/4tB3zm

对比结果

上面使用的距离其实还是欧式距离,因此使用定值或是fwidth看起来似乎没什么差别。但是,如果我们对它做少许改变,例如取消下面的注释:

			// Twist
//			pos.x += 0.5 * sin(5.0 * pos.y);

就会发现两者的差异了:

右下角使用fwidth的区域仍然可以保持良好的抗锯齿状态,而右上角已经开始出现一定量的锯齿了。虽然我们可以通过调大定值_Antialias的方法来增大模糊范围,但这样会造成有些区域过于模糊,而且这种根据情况来调整定值的方法会很麻烦。而使用fwidth的话,我们就不需要考虑这些因素,因为它是直接得到和临近像素之间的差值,不依赖与欧氏距离。

写在最后

这篇文章老实说来有点杂,我尽我可能想把问题阐述清楚了。。。总结一下,这篇主要想要介绍fwidth这个函数在抗锯齿方面的应用,除此之外还介绍了clamp,当然我个人还是比较喜欢用smoothstep。。。但一个是线性插值的,一个的三次插值的,性能上有少许差别。效果上也有一点思维差别,这要根据项目需要有所选择。

好啦,下一篇我想写下各种插值曲线的细节,包括贝塞尔曲线,Catmull-Rom样条等等。我自己也总是忘。。。写一篇备忘一下。

时间: 2024-11-07 00:36:08

【ShaderToy】基础篇之再谈抗锯齿(antialiasing,AA)的相关文章

【转载】浅谈抗锯齿技术-老文章(供参考)

原文:http://vga.zol.com.cn/2002/1007/48701.shtml 一代又一代的图形芯片和显卡不断的推出,PC图形子系统的图形处理能力也随之大幅度的提高,这使得我们有可能在计算机上看到更精美的实时生成的图像.无论图形芯片如何改进,在图形输出技术没有革命性变化的今天,我们看到的最终图像依然是由上百万个显示屏上的像素组成的.正是因为像素的存在,使得图像总是存在一个近乎于无法完全克服的缺点:锯齿. 在现实世界中相邻的两个物体边缘一般是光滑的,但是在电脑上生成的图像中相邻的物体

Java基础——再谈面向对象

去年的这个时候,心血来潮写了篇<简述面向对象技术>,先在看来不由的会想:这都是写的什么跟什么啊?(ps:虽然现在写的博客依然不咋地)但是,Java的学习中又一次不得不再一次面向对象,所以,奉上一篇<再谈面向对象>,做为新年的一盘开胃菜. 面向对象是相对于面向过程而言,是一种思想. 区别于面向过程: 面向过程是以函数为基础,完成各种操作,强调的是过程,而面向对象是以对象为基础,强调的是对象. 比如说把大象装进冰箱分为几步,宋丹丹是这样说的:三步呗, 第一步:打开冰箱门, 第二步:把大

SQL Server高并发问题系列基础篇(谈谈事务的属性)

前言 SQL Server作为一款优秀的关系型数据库,在支撑起最基本的数据存储功能之外,还承受着各种大量的并发操作和用户访问. 而本系列内容将一层层慢慢分析出在SQL Server的生态环境中,是怎样通过一系列的机制来维持各种访问的秩序,俗话说得好:“有人的地方就有了江湖,有了江湖就有了江湖规矩”,同样在SQL Server的世界中,也有它自己的“江湖规矩”,如果你不按照规矩出牌,那么你将会被踢出局! 同样本系列也会分析出遇到各种问题的解决思路,供院友们参考. 本篇作为第一篇,首先同样先提基础,

【ShaderToy】基础篇之谈谈点、线的绘制

写在前面 写前面一篇的时候,发现还是不够基础.因此打算增加几篇基础篇,从点线面开始,希望可以更好理解. 其实用Pixel Shader的过程很像在纸上绘画的过程.屏幕上的每一个像素对应了纸上的一个方格,如果你愿意,你甚至可以一个个判断像素的位置,从而画出任何你想画的图像,也的确有爱好者这么做过.但往往,我们需要的是一个动态的效果,这个效果往往依赖于数学公式的约束.我们可以说是,用数学去绘画.我们用数学去约束,哪些点应该用什么颜色去绘制. 这篇,我们从基本的点和线开始,看一下如何在Pixel Sh

【ShaderToy】抗锯齿相关函数

*示例代码可以直接在ShaderToy中运行. 先上未抗锯齿的两个圆形图案,可以清楚看清图案边缘像素块,即"锯齿". 附代码: void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y; vec2 center1 = vec2(-0.75,0); vec2 center2 = vec2(0.75,

OpenGL核心技术之抗锯齿

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 抗锯齿问题在游戏中一直存在的,尤其是体现在3D模型上的材质或者游戏UI界面上,由于现在引擎都非常完善,并且引擎都提供了抗锯齿功能,我们通过引擎提供的参数界面设置一下就可以消除.但是很

2000条你应知的WPF小姿势 基础篇&lt;1-7&gt;

在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师,对C#和WPF有着极深的热情.最为出色的是他维护了两个博客:2,000Things You Should Know About C# 和 2,000 Things You Should Know About WPF .听到博客名字就懂这个人有多伟大了吧.他以类似微博式的150字简短语言来每天更新一条WPF和C#重要又容易被遗忘的知识.Follow他的博客也有一段日子了,很希望能够分享给大家. 本系列我不仅会翻译

SSE图像算法优化系列二十四: 基于形态学的图像后期抗锯齿算法--MLAA优化研究。

偶尔看到这样的一个算法,觉得还是蛮有意思的,花了将近10天多的时间研究了下相关代码. 以下为百度的结果:MLAA全称Morphological Antialiasing,意为形态抗锯齿是AMD推出的完全基于CPU处理的抗锯齿解决方案.对于游戏厂商使用的MSAA抗锯齿技术不同,Intel最新推出的MLAA将跨越边缘像素的前景和背景色进行混合,用第2种颜色来填充该像素,从而更有效地改进图像边缘的变现效果,这就是MLAA技术. 其实就是这个是由Intel的工程师先于2009年提出的技术,但是由AMD将

js调试系列: 源码定位与调试[基础篇]

js调试系列目录: - 如果看了1, 2两篇,你对控制台应该有一个初步了解了,今天我们来个简单的调试.昨天留的三个课后练习,差不多就是今天要讲的内容.我们先来处理第一个问题:1. 查看文章下方 推荐 这个功能所调用的函数源码其实非常简单,点放大镜选中那个推荐即可.这个  votePost(cb_entryId,'Digg')  就是推荐按钮所调用的函数了,是不是非常简单. 第二个问题,定位到函数所在文件位置.其实也是非常简单的,当然,不熟悉控制台的朋友也许不知道怎么看.我在控制台输入 voteP