写在前面

在之前的基础篇中,我们讲到了在绘制点线时如何处理边缘的锯齿,也就是使用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

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

以及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); 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)); // Apply X Y Z rotations
// Find more info from http://en.wikipedia.org/wiki/Rotation_matrix
float xSpeed = 0.3;
float ySpeed = 0.5;
float zSpeed = 0.7;
float3x3 mat = float3x3(1., 0., 0.,
0., cos(xSpeed*iGlobalTime), sin(xSpeed*iGlobalTime),
0., -sin(xSpeed*iGlobalTime), cos(xSpeed*iGlobalTime));
mat = mul(float3x3(cos(ySpeed*iGlobalTime), 0., -sin(ySpeed*iGlobalTime),
0., 1., 0.,
sin(ySpeed*iGlobalTime), 0., cos(ySpeed*iGlobalTime)), mat);
mat = mul(float3x3(cos(zSpeed*iGlobalTime), sin(zSpeed*iGlobalTime), 0.,
-sin(zSpeed*iGlobalTime), cos(zSpeed*iGlobalTime), 0.,
0., 0., 0.), mat); float l = 1.5;
vec3 p0 = vec3(0., 0., sqrt6_divide_12 * 3.) * l;
vec3 p1 = vec3(-0.5, -sqrt3_divide_6, -sqrt6_divide_12) * l;
vec3 p2 = vec3(0.5, -sqrt3_divide_6, -sqrt6_divide_12) * l;
vec3 p3 = vec3(0, sqrt3_divide_6 * 2., -sqrt6_divide_12) * l; p0 = mul(mat, p0);
p1 = mul(mat, p1);
p2 = mul(mat, p2);
p3 = mul(mat, p3);; vec2 point0 = p0.xy;
vec2 point1 = p1.xy;
vec2 point2 = p2.xy;
vec2 point3 = p3.xy; 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)之间。然后初始化颜色为背景颜色。接着,我们计算一个在XYZ轴上旋转的旋转矩阵,并计算出在标准坐标系中正四面体的四个顶点坐标p0~p3,并把旋转矩阵作用到顶点上来完成旋转。接着,坐标映射到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 pi 3.14159265358979
#define sqrt3_divide_6 0.289
#define sqrt6_divide_12 0.204 const float seg = 3.0;
const float segwidth = 1./(2.*seg-1.); 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 xSpeed = 0.3;
float ySpeed = 0.5;
float zSpeed = 0.7;
float3x3 mat = float3x3(1., 0., 0.,
0., cos(xSpeed*iGlobalTime), sin(xSpeed*iGlobalTime),
0., -sin(xSpeed*iGlobalTime), cos(xSpeed*iGlobalTime));
mat = mul(float3x3(cos(ySpeed*iGlobalTime), 0., -sin(ySpeed*iGlobalTime),
0., 1., 0.,
sin(ySpeed*iGlobalTime), 0., cos(ySpeed*iGlobalTime)), mat);
mat = mul(float3x3(cos(zSpeed*iGlobalTime), sin(zSpeed*iGlobalTime), 0.,
-sin(zSpeed*iGlobalTime), cos(zSpeed*iGlobalTime), 0.,
0., 0., 0.), mat); float l = 1.5;
vec3 p0 = vec3(0., 0., sqrt6_divide_12 * 3.) * l;
vec3 p1 = vec3(-0.5, -sqrt3_divide_6, -sqrt6_divide_12) * l;
vec3 p2 = vec3(0.5, -sqrt3_divide_6, -sqrt6_divide_12) * l;
vec3 p3 = vec3(0, sqrt3_divide_6 * 2., -sqrt6_divide_12) * l; p0 = mul(mat, p0);
p1 = mul(mat, p1);
p2 = mul(mat, p2);
p3 = mul(mat, p3);; vec2 point0 = p0.xy;
vec2 point1 = p1.xy;
vec2 point2 = p2.xy;
vec2 point3 = p3.xy; 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的话,我们就不需要考虑这些因素,因为它是直接得到和临近像素之间的差值,不依赖与欧氏距离。

扩展

我突然觉得旋转的正四面体很容易就让人分不清前后了,就想表示出来深度。目前使用了虚线+较暗的颜色来表示一条棱被挡在后面。具体可以见这里(https://www.shadertoy.com/view/4t2GRm)。不过,还是有穿帮的地方,额,以后有机会再优化下好了。效果如下:

写在最后

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

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

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

  1. Java基础篇——集合浅谈

    原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10703558.html,否则将追究法律责任!!! Set(基于Map来实现的,不细说) H ...

  2. java基础篇 之 再探内部类跟final

    之前写过一篇文章:从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题,经过这两天的学习,发现有些不对,必须再来捋一捋 先看之前的例子: /** * @a ...

  3. Python基础篇 -- 小数据池和再谈编码

    小数据池 1. id() 通过id()可以查看到一个变量表示的值在内存中的地址 s = "Agoni" print(id(s)) # 2410961093272 2. is 和 = ...

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

    写在前面 写前面一篇的时候,发现还是不够基础.因此打算增加几篇基础篇,从点线面开始,希望可以更好理解. 其实用Pixel Shader的过程很像在纸上绘画的过程.屏幕上的每一个像素对应了纸上的一个方格 ...

  5. 【ShaderToy】抗锯齿相关函数

    *示例代码可以直接在ShaderToy中运行. *我放在这里咯ShaderToy基础学习中~欢迎交流(ノ>ω<)ノ 先上未抗锯齿的两个圆形图案,可以清楚看清图案边缘像素块,即“锯齿”. 附 ...

  6. 手撸ORM浅谈ORM框架之基础篇

    好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...

  7. 再谈多线程模型之生产者消费者(基础概念)(c++11实现)

    0.关于 为缩短篇幅,本系列记录如下: 再谈多线程模型之生产者消费者(基础概念)(c++11实现)[本文] 再谈多线程模型之生产者消费者(单一生产者和单一消费者)(c++11实现) 再谈多线程模型之生 ...

  8. Python基础 第7章 再谈抽象

    1. 1 多态 多态,即便不知道变量指向的是哪种对象,也能对其执行操作,且操作的行为将随对象所属的类型(类)而异. 1.2 多态与方法 当无需知道对象是什么样的就能对其执行操作时,都是多态在起作用. ...

  9. 2000条你应知的WPF小姿势 基础篇<1-7>

    在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师,对C#和WPF有着极深的热情.最为出色的是他维护了两个博客:2,000Things You Should Know ...

随机推荐

  1. Codeforces278E Tourists

    来自FallDream的博客,未经允许,请勿转载,谢谢. 给定一张无向图,有点权,要支持单点修改点权和询问从一个点到另一个点不重复经过节点的路径上点权最小值的最小值. n,m<=10^5 考虑求 ...

  2. Map 探索

    关于Map问题我一直了解的不是很透彻,下面进行比较系统的总结一下. 1.

  3. C语言程序第三次作业

    (一)改错题 计算f(x)的值:输入实数x,计算并输出下列分段函数f(x)的值,输出时保留1位小数. 输入输出样例1: Enterr x: 10.0 f(10.0) = 0.1 输入输出样例2: En ...

  4. 一款基于Vue的扩展性组件库 VV-UI

    github: https://github.com/VV-UI/VV-UI 演示地址: https://vv-ui.github.io/VV-UI/#/meta-info 1. LoadingBar ...

  5. 使用mybatis注解@Options实现添加记录时返回主键值

    官网:http://www.mybatis.org/mybatis-3/index.html 在使用mybatis作为ORM框架时,我通常更喜欢使用注解而非xml配置文件的方式.业务场景:添加记录之后 ...

  6. Tomcat出现validateJarFile-jar not loaded问题

    tomcat启动时问题: validateJarFile(...\WEB-INF\lib\servlet-api.jar)-jar not loaded. See Servlet Spec 2.3, ...

  7. web.xml is missing and <failOnMissingWebXml> is set to true

    这时候需要右击项目-->Java EE Tools-->Generate Deployment Descriptor Stub .然后系统会在src/main/webapp/WEB_INF ...

  8. git报错:'fatal:remote origin already exists'怎么处理?附上git常用操作以及说明。

    git添加远程库的时候有可能出现如下的错误, 怎么解决? 只要两步: 1.先删除 $ git remote rm origin 2.再次执行添加就可以了. ---------------------- ...

  9. aways on 配置部署(一)——准备工作

    sqlserver的aways on 配置需要经历三个步骤,前面两个步骤是对aways on 配置的一个准备步骤. 经过了一个星期的研究,终于成功的完成了前两个步骤,期间参考了很多的资料和博客,总感觉 ...

  10. PHP Zip File 函数

    通过 PHP 中的相关函数,你可以实现 zip 文件的解压缩操作! PHP Zip File 简介 Zip File 函数允许您读取压缩文件. 安装 如需在服务器上运行 Zip File 函数,必须安 ...