OpenGL常用命令备忘录(Part B)

3.glPixelStore

像glPixelStorei(GL_PACK_ALIGNMENT, 1)这样的调用,通常会用于像素传输(PACK/UNPACK)的场合。尤其是导入纹理(glTexImage2D)的时候:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(,,,, &pixelData);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

很明显地,它是在改变某个状态量,然后再Restore回来。——为什么是状态?你难道8知道OpenGL就是以状态机不?——什么状态?其实名字已经很直白了,glPixelStore这组函数要改变的是像素的存储格式。

涉及到像素在CPU和GPU上的传输,那就有个存储格式的概念。在本地内存中端像素集合是什么格式?传输到GPU时又是什么格式?格式会是一样么?在glTexImage2D这个函数中,包含两个关于颜色格式的参数,一个是纹理(GPU端,也可以说server端)的,一个是像素数据(程序内存上,也就是client端)的,两者是不一定一样的,哪怕一样也无法代表GPU会像内存那样去存储。或者想象一下,从一张硬盘上的图片提取到内存的像素数据,上传给GPU成为一张纹理,这个“纹理”还会是原来的那种RGBARGBA的一个序列完事么?显然不是的。作为一张纹理,有其纹理ID、WRAP模式、插值模式,指定maipmap时还会有一串各个Level下的map,等等。就纹理的数据来说,本质纹理是边长要满足2的n次方(power of two)的数据集合,这样首先大小上就有可能不一样,另外排列方式也未必就是RGBA的形式。在OpenGL的“解释”中,纹理就是一个“可以被采样的复杂的数据集合”,无论外面世界千变万化,GPU只认纹理作为自己“图像数据结构”,这体现着“规范化”这条世界纽带的伟大之处。

姑且把GPU里面的像素存储格式看做一个未知数,把该存储空间内那批像素看做一堆X。不要深究一堆X究竟是什么样子的,嘛,反正就想象成一堆软绵绵的,或者模糊不清的,打满马赛克的,(哔——)的一样的东西就可以了。与此相比,内存中的像素数据实在太规则规范了!可能源文件各种图片格式,什么bmp、jpg、png甚至dds,但只要你按该格式的算法结构来提取(类似[Bmp文件的结构与基本操作(逐像素印屏版)] ),总可以提取出一列整齐的RGBARGBA(或者RGBRGB什么的,反正很整齐就行了管他呢)的数据堆出来,是可以在程序中实测的东西。

涉及到像素在CPU和GPU上的传输,那就有个传输方向的概念。那就是大家耳濡目染的PACK和UNPACK。嘛,装载和卸载也可以,打包和解压也可以,随你怎么译了。结合上述存储格式的概念:

  • PACK —— 把像素从一堆X的状态转变到规则的状态(把一堆泥土装载进一个花盆,把散散的货物装上货柜,或者把一堆各样的文件打包成一个rar压缩包,等等);
  • UNPACK —— 把像素从规则的状态转变到一堆X的状态(把花盆里的泥倒出来,把货柜中的货物卸载到盐田港,或者解压压缩包,等等)。

我认为这两个概念还是很容易混淆的,所以形象化一点总好点嘛。从本地内存向GPU的传输(UNPACK),包括各种glTexImage、glDrawPixel;从GPU到本地内存的传输(PACK),包括glGetTexImage、glReadPixel等。也正因如此,PBO也有PACK和UNPACK模式的区别。

好像说了好多不相关的事情。嘛,适当也当做延伸。回头来真正看一下glPixelStore吧。它的第一个参数,譬如ALIGNMENT、ROW_LENGTH、IMAGE_HEIGHT等等,都有PACK和UNPACK的两种版本,所以对应的也是上述关于PACH和UNPACK的两类函数。所以对于glTexImage2D,才使用GL_UNPACK_ALIGNMENT的版本。但要说明的是,无论是哪种传输方式,它都是针对本地内存端(client端)上的像素数据的。在上述例子中,它起着补充glTexImage2D中关于传输起点——本地像素集合的格式,的作用。

一般来说,这些本地的数据集合,只要知道其起始位置、大小(width*height)和颜色格式(譬如GL_RGBA等等)、值格式(GL_UNSIGNED_CHAR、GL_FLOAT等等),就能准确地传输。而这些都是需要向glTexImage2D函数(或者上述的其他传输型函数)提供的。但是,这里头也一些细节,其实是需要glPixelStore这个函数来进行设置的。

3.1 GL_UNPACK_ALIGNMENT / GL_PACK_ALIGNMENT

通常,提取一张图像的时候,我们怎么知道一行的数据量呢?这个一行的数据量应该是:width * sizeof(Pixel) ,应对最一般RGBA、各通道各占一个字节的像素结构,width * sizeof(Pixel) = width * 4 * sizeof(byte),是4的整数倍。但是也有时候,我们的像素数据中一行的数据量不一定是4的整数倍(譬如一张RGB的图像、宽度150、各通道各占一个字节的像素结构,一行的数据量就是450个字节)。

另一方面,跟编译器一样,GPU传输时也喜欢4字节对齐,也即是说喜欢对像素数据按4字节存取。所以它更偏向于喜欢每一行的数据量是4的整数倍(按上述,这恰好是比较常见的)。所以为了更高的存取效率,OpenGL默认让像素数据按4字节4字节的方式传输向GPU——但是问题在于,对于行非4字节对齐的像素数据,第一行的最后一次存取的4字节将部分包括第一行的数据部分包括第二行的数据,当然致命的不是在这里,而是在最后一行:存取将很可能会越界。为了防止这样的情况,一是硬性把像素数据延展成4字节对齐的(就像BMP文件的存储方式一样,[Bmp文件的结构与基本操作(逐像素印屏版)] );二是选择绝对会造成4字节对齐的颜色格式或值格式(GL_RGBA啦,或者GL_INT、GL_FLOAT之类);三是以牺牲一些存取效率为代价,去更改OpenGL的字节对齐方式——这就是glPixelStore结合GL_UNPACK_ALIGNMENT / GL_PACK_ALIGNMENT。

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(,,,, &pixelData);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

再次看回这段代码,这时候就明白了:让字节对齐从默认的4字节对齐改成1字节对齐(选择1的话,无论图片本身是怎样都是绝对不会出问题的,嘛,以效率的牺牲为代价),UNPACK像素数据,再把字节对齐方式设置回默认的4字节对齐。至于哪种方式更适合,就看你依据硬件环境限制、麻烦程度等,去选择了。

3.2 GL_UNPACK_ROW_LENGTH/ GL_PACK_ROW_LENGTH 和 
GL_UNPACK_SKIP_ROWS / GL_PACK_SKIP_ROWS 、 GL_UNPACK_SKIP_PIXELS/GL_PACK_SKIP_PIXELS

有的时候,我们把一些小图片拼凑进一张大图片内,这样使用大图片生成的纹理,一来可以使多个原本使用不同的图片作为纹理的同质物件如今能够在同一个Batch内,节省了一些状态切换的开销,二来也容易综合地降低了显存中纹理的总大小。但是,也有些时候,我们需要从原本一张大的图片中,截取图片当中的某一部分作为纹理。要能够做到这样,可以通过预先对图片进行裁剪或者在获得像素数据后,把其中需要的那一部分另外存储到一个Buffer内再交给glTexImage2D之类的函数。而上述这些参数下glPixelStore的使用将帮助我们更好地完成这个目的:

//原图中需要单独提取出来制成纹理的区域
RECT subRect = {{100, 80}, {500, 400}}; //origin.x, origin.y, size.width, size.height

//假设原图的宽度为BaseWidth, 高度为BaseHeight

glPixelStorei(GL_UNPACK_ROW_LENGTH,  BaseWidth);    //指定像素数据中原图的宽度
glPixelStorei(GL_UNPACK_SKIP_ROWS,      subRect. origin.y.); //指定纹理起点偏离原点的高度值
glPixelStorei(GL_UNPACK_SKIP_PIXELS,     subRect. origin.x);  //指定纹理起点偏离原点的宽度值

glTexImage2D(..., subRect.size.width, ubRect.size.height,.. &pixelData);  //使用区域的宽高

glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);

这段代码本身,即使没有注释也很清楚了。注意的是GL_UNPACK_ROW_LENGTH的必要性,因为为了确认区域起点的Offset,就需要把线性数据pixelData上标记起点的“游标”从0移动到OffsetToData = subRect. origin.y * BaseWidth + subRect. origin.x的位置。有了区域纹理原点的在原图数据的位置,以及区域的尺寸,glTexImage2D就可以确定区域纹理生成所需要的信息了。通过glPixelStore的使用,避免了新建Buffer和自己处理图像数据的开销和麻烦了。

说到这里,到底为什么要这样做来提取区域纹理呢?尤其是原图若其他部分都是程序所需要的,那是不是就可以直接通过纹理坐标去切割更好呢?我想到的是一种情况(也可以说我是因为这种情况才注意到glPixelStore的这种用法):如果这块区域纹理需要作重复铺设(wrap mode选择GL_REPEAT)呢?这时候纹理坐标的方法就没用了,因为REPEAT所依据的也是纹理坐标(使用纹理坐标的小数部分进行采样)。这时候就需要上述做法了。(事实上3DSMAX等软件纹理导入的类似区域纹理平铺的功能就能如此实现。)

4.glScissor

我想这个函数也应该很常见才对。裁剪测试啊,当年跟Alpha测试、Depth测试、Stencil测试可以并列哦,而今更是不掉时髦值啊。因为我实在很难想象在Shader里能容易地实现它的功能:裁剪。当然这只是矩形裁剪,但是对于discard掉渲染中不需要的像素真是颇简单粗暴。我使用它最多的是一些二维图片缩略图栏——有时候我们只需要把这些缩略图的显示限制在一个区域里,但又要支持滑动。

glEnable(GL_SCISSOR_TEST);
glScissor(GLint(m_rtThumbRegion.x), GLint(m_rtThumbRegion.y), GLint(m_rtThumbRegion.width), GLint(m_rtThumbRegion.height));
//.....  Render
glDisable(GL_SCISSOR_TEST);

其中,除了启用GL_SCISSOR_TEST外,只要给glScissor指出需要保留显示的区域就可以了。在此区域外的像素依然会被渲染(不会怎么省流水线操作,所以也别指望它附带什么提高效率之类的功能),在下图中,其实左右两侧还是继续渲染其他的图片(或者说,其实这个缩略图栏横跨整个屏幕),但是就在fragment shader之后,它们会被检测到不在该区域内而被discard掉罢。

原文地址:http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html

OpenGL常用命令备忘录(Part A)

1. glCullFace 和 glFrontFace

这两个都是opengl的初级命令,但是其实我都是最近才算得上“用”,以前的话,是因为有一次做billboard广告牌的时候,不剔除面片其中一面的话,很难看出效果成功了没,于是用了一次;最近的话,包括shadow volume中Z-FAIL和Z-PASS都需要正反面分别渲染,shadowmap中在pass 1里剔除正面,在光源视觉下渲染到纹理也用到了,一来减少渲染的”费用”,二来可以提高深度测试的精度(橙书里说的)。以前曾经在哪里看到篇文章说是其实剔除正或反面都提高不了多少效率,首先作者肯定做过测试来了,但是这样算不算武断我也不会说,问过一些搞游戏开发的人,答到“当然能提高效率啦,只不过不可能提高一倍而已”,事实上提高一点点都是很好的说。谁对谁错我也懒得分辨了,因为至少目前我还很少用,而且根据以前尝试的经验决定还是“能不用就不用”,因为正反面判断实在太烦了,有很多时候你用到的是别人弄的模型,而不那么专业的美工会在建模的时候搞乱面片绕序。

所以这组命令除了用在某些特殊场合(实现某些技术所必须),还有就是提高渲染效率的可能性,对某些效果正确性的辅助判断。其中glFrontFace用于指定正面的绕序,默认是“逆时针为正”,即传入参数GL_CCW,可以改成GL_CW顺时针。glCullFace 传入GL_FRONT来剔除正面,或者GL_BACK来剔除背面,之前先得glEnable(GL_CULL_FACE)。

通常情况是这样的:当你想剔除背面的时候,你只需要调用glEnable(GL_CULL_FACE)就可以了,OPENGL状态机会自动按照默认值进行CULL_FACE,默认是glFrontFace(GL_CCW),glCullFace(GL_BACK),当然如果你不想按照默认的来,你就得设置一下了。最后在不再需要剔除的时候调用glDisable(GL_CULL_FACE)。我建议的是“每次开始用,无论是不是默认的你最好都设置一下”,不然你迟些很容易被状态机(保持上一次设置的状态)搞到呕血——除非真的是很简单的demo制作,譬如之前shadow volume中我只是glEnable(GL_CULL_FACE),然后直接glFrontFace(GL_CCW)——它启用剔面功能,指定了“顶点逆时针绕序为正面”,之后的渲染它就会自动剔除那些反面;之后再调用glFrontFace(GL_CW),重新指定为“顶点顺时针绕序为正面”,再渲染一次,注意这时候它同样也是剔除背面(你没改过glCullFace),但是剔除的面就恰恰与前面的相反了。最后在disable前记得先改回glFrontFace(GL_CCW)!否则下次一enable就会按“顺时针绕序为正”了(与常理不一)。(望天!我忘记当时有没设置回来了~)

2.glShadeModel

在opebgl流水线里头,有一个步骤是栅格化(Rasterization),它在顶点组合的几何信息处理后执行,目的是“插值”,vertex shader的varying变量就是在这里被栅格化(/插值),然后再传入fragment shader作象素级别的处理。两顶点之间的象素怎么处理呢?就是靠这两顶点的信息的线性插值。譬如最简单的,颜色,一条线段两顶点A和B的颜色分别为红色和绿色,但是最后“显示颜色”的不可能是顶点,只有象素,因此这两点之间的象素就得按照它们临近的顶点的颜色而获得,越靠近点A的那些象素越红,越靠近点B的那些象素越绿,中间因此呈现渐变效果(一般来说,线段中点有一半红一半绿而成为黄色…)。对于填充的三角形,矩形也是一样的道理,只不过是平面内的线性插值罢了。

说了那么多,其实glShadeModel作用相当于打开/关闭这种功能(栅格化),传入参数GL_FLAT,流水线还是要经过这步骤但相应顶点不被处理,故顶点间的象素的颜色只会参考其中一个点的信息。譬如线段AB上的象素点全是红的或全是绿的——是哪种通常不要紧,因为无论是哪种,出来的结果都会好难看,所以渲染最初(初始化阶段)都会把参数设置成GL_SMOOTH,即启用栅格化功能。当然插值的计算量就上来了…..在渲染不注重效果而只注重速度的时候,譬如我做shadow map的PASS1作场景深度图的离线渲染时,非象素深度的信息根本对我无用,而且象素深度不是插值来的,故关闭栅格化计算,直接glShadeModel(GL_FLAT)再渲染就可以了,之后记得调回glShadeModel(GL_SMOOTH)。

好了,PART 1到此。

原文地址:http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html

写在2011->2012之际

2011的年度仍是个多事之年。包括微博牵动的各种事件,各种团购的洗牌,各种移动应用的产生,各种移动设备的诞生、完善,各种巨星的陨落⋯⋯

在这个多事之年,我也主动或被动地做出了一些改变。

以下将颁发的是:

年度给力互联网产品

  • WordPress:仍然在里面学了不少东西,但是目前个人学习方向转向纯php框架方向了;
  • Google Reader:依旧对订阅的文章爱不释手;
  • 新浪围脖:这是他爆发的一年,跟其他同类产品拉开距离的一年。

年度给力电子产品

  • ipod touch 4:仍是那个用了一年多,还一直待在我身边的它,最主要的还是听歌、记事、阅读,偶尔的随身拍;
  • 金河田音箱:一个用了十年的金河田音箱,在家尘封3年后重新出山,音质依然完美。(如有广告嫌疑,请金河田自觉支付广告费用)
  • macbook pro:我终于也砸锅卖铁如愿拿下了梦寐以求的mbp,从外观到系统,仍是让我爱不释手,当然有那么点装逼的成分,但是确实半身心地投入了mac os的怀抱,他不完美,甚至说不完善,但仍值得拥有!

年度给力人物

  • 一颗清澈不圆滑的鱼丸。

年度给力装×必备

  • 电子琴:还是那个琴,但是会弹的曲子多多了;
  • 布鲁斯口琴sp20:还是那个琴,吹出来的效果好多了。

年度给力书籍

  • 一味:超个人心理学大师 肯.威尔伯的札记,太深奥难懂,看得我晕晕乎乎;
  • 重来(Rework):封面有句“置此书于不顾者,后果自负”,我怕怕,所以看了,37signals创始人写的,充满了美国创业人的思维,确实值得一看学学,但可能不必太当真;
  • 定位:一本美国营销书籍;
  • 乔布斯传:讲述jobs神奇花哨的一生,感觉很奇妙的一生,辗转曲折充满崎岖,目前在这个国出一个这样的人确实悬。值得一看。

年度给力旅游

  • 昆明:纯属出差间隙随性出游,感觉昆明确实是个适合人活的地儿,少数民族味浓厚,纯朴,之间去过九乡、滇池、海埂公园等,遗憾没去丽江。
  • 三明泰宁:山清水秀,但是跟团旅游,感觉有点乱。

2012年小小的目标

  • 坚定不移地走产品营销到品牌营销的人生道路;
  • 来点不同寻常的东西。
  • 花太多时间在碎片化信息的获取,诸如围脖,保持轻度投入,多看完整系统的书籍,2011仍然看得太少;
  • 坚决把驾照考出来!(去年的目标,年末报的,仍然没排上,悲催)

C++中const用法浅析

看到const 关键字,很多程序员想到的可能是const 常量,这可有点象踩到陷井上还不知道自己危险了。读读以下文字会使你对c++中的const有一个全面的认识。

const 是C++中常用的类型修饰符,有某些微妙的应用场合,如果没有搞清本源,则错误在所难免。本篇中将对const进行辨析。溯其本源,究其实质,希望能对大家理解const有所帮助,根据思维的承接关系,分为如下几个部分进行阐述。

C++中为什么会引入const

C++的提出者当初是基于什么样的目的引入(或者说保留)const关键字呢?,这是一个有趣又有益的话题,对理解const很有帮助。

1. 大家知道,C++有一个类型严格的编译系统,这使得C++程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了C++与C相比,有着突出优点的一个方面。

2. C中很常见的预处理指令 #define VariableName VariableValue 可以很方便地进行值替代,这种值替代至少在三个方面优点突出:

一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:
#define USER_NUM_MAX 107 这样就避免了直接使用107带来的困惑。
二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,
三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。
鉴于以上的优点,这种预定义指令的使用在程序中随处可见。

3. 说到这里,大家可能会迷惑上述的1点、2点与const有什么关系呢?,好,请接着向下看来:

预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受C++严格类型检查的好处,从而可能成为引发一系列错误的隐患。

4.好了,第一阶段结论出来了:

结论: Const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点
现在它的形式变成了:
Const DataType VariableName = VariableValue ;
为什么const能很好地取代预定义语句?
const 到底有什么大神通,使它可以振臂一挥取代预定义语句呢?

1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。
2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
3. 第三,C++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。
这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因为,编译器不会去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的常量了。
4. 最后,const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。

const 使用情况分类详析

1.const 用于指针的两种情况分析:
int const *a; //a/可变,*a不可变
int *const a; //a不可变,*a可变
分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符合为一个类型

修饰符,所以,int const 限定 *a,不限定a。int *const 限定a,不限定*a。

2.const 限定函数的传递值参数:

void fun(const int var);

分析:上述写法限定参数在函数体中不可被改变。由值传递的特点可知,var在函数体中的改变不会影响到函数外部。所以,此限定与函数的使用者无关,仅与函数的编写者有关。
结论:最好在函数的内部进行限定,对外部调用者屏蔽,以免引起困惑。如可改写如下:

void fun(int var){
const int & varalias = var;

varalias ………

}

3.const 限定函数的值型返回值:

const int fun1();
const myclass fun2();

分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时(如fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。

4. 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。

5. const 限定类的成员函数

class classname {
public:
int fun() const;
…..
}

注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。

获得能力:可以操作常量对象。
失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。

颁给2010的

2010的年度关键字应该莫过于“给力”了。这一年互联网产生诸多给力的事儿,新浪围脖的雄起、3Q大战、百团大战、一大堆诸如犀利哥的网络人物的火爆、一大堆给力的网络词汇应运而生……

除了见证给力的的互联网秩事,2010里我也想过、念过、入过、出过、痛过…(想歪的自觉面壁)

以下将颁发的是:

年度给力互联网产品

  • WordPress:经历了2.×到3.×的蜕变,从开始碰到他就被她的强大及无数的theme和plugins吸引,因他学到不少东西;
  • Google Reader:依旧对订阅的文章爱不释手,Geek们的思想、行业分析、电子产品测评、美文赏析,即使经常性的1000+,还是能在碎片化信息充斥的年代下找到几片净土;
  • 新浪围脖:由于众所周知的不可告人的原因,twitter该页无法显示,新浪围脖在这一年应运雄起,我也随之成为一个轻度用户,喜欢里边的精英强关系链,同时被看到了不少名言警句,看好新浪围脖。

年度给力电子产品

  • ipod touch 4:不经常吃苹果,不敢说是苹果控,但还是在苹果cn store开张的第一天拿下了这个朝思暮想的她,至今仍是充满爱;
  • HTC Wildfire:由于我一直想着天上掉个Android机子给我,老天实在看不下去了,终于安排在公司的年终会上让我抽到之,满心欢喜。分辨率磕碜了点,但作为随身的wifi热点,已成为随身touch的伴侣,帮我省下了个HUAWEI E5,还能打电话,完美。

年度给力人物

  • 让我时喜时悲辗转反侧苦不堪言的那个。

年度给力装×必备

  • 电子琴:胡总买的,大多数时间我在弹,至今能完整弹10首以内的曲子,算是还没成型的装×必备,但是指日可待;
  • 布鲁斯口琴sp20:坚决地戒掉了之前的28孔口琴,改投怀小巧玲珑的sp20了,这样以后就可以随时随地不分昼夜地装×了。

年度给力书籍

  • 湿营销 by Tom Hays&Michael S. Malone:觉得还不错的社会化营销研究分析书籍,在之前的文章有小写了下读后感;
  • 网络江湖:从小白那顺的,分析江湖大佬们的各种纷争,之中的所谓36计,程苓峰写的,可看一下,仅供参考;
  • 货币战争:中国人写的由资本贯穿的西方金融、历史;
  • 给你一个亿:老查写的,现在我还不知道给我一个亿我得怎么花,谁能给我半个亿试试先。
  • 有味:汪涵写的,可作为文学小品看看。

年度给力旅游

  • 颐和园、长城、故宫等等北京景点:纯属出差间隙随性出游,看的红砖琉璃瓦花岗岩白玉石等等砌成的建筑,但是首先看到的还是人其次看到的还是人,北京不管何时何地都充满了人味儿。
  • 武夷山:山清水秀人杰地灵的地方,个人觉得是福建最值得一去的地方了。

年度给力个人产出

  • 秀范儿时尚资讯:时尚资讯网站,基于wordpress修改,死命维系之;
  • 考录吧:自考成考等考试报考网站,出师未捷身先死;
  • blueshowyo theme:虽然很丑不温柔,但是我的第一个theme,也算给力吧;
  • 工作上从需求到验收一气呵成完成一个BPM,算是见识了项目之外的各种嘴脸。

2011年小小的目标

  • 坚定不移地走产品营销到品牌营销的人生道路;
  • 跟我的兄弟们好好干点实事;
  • 花太多时间在碎片化信息的获取,诸如围脖,保持轻度投入,多看完整系统的书籍,2010看得太少,很对不起之前吹过的牛逼;
  • 多写博客;
  • 坚决把驾照考出来!

wordpress添加google自定义站内搜索功能

想在wordpress中添加google自定义搜索可能有两个原因:一是wordpress本身搜索功能又慢又弱,二是有Adsense账号,这个搜索广告不用白不用,说不定还可以小弄点美刀什么的。
对于添加google自定义搜索,有三种方法:
1、把搜索结果放google页面上,这种方法通过点击站内google搜索框,然后跳转到google页面显示搜索结果,个人觉得这个方法比较不可取,因为把浏览者牵出站外了,对自己对浏览者都不是一个好的体验。本文对该方法不作描述。
2、使用 Custom Search element让搜索结果显示在站内,这种方法针对没有Adsense账户的可以用下,该搜索结果显示在站内,展现结果跟第三种方法一样。
3、把google自定义搜索框和搜索结果都放在wordpress主题中的文件,达到整过搜索过程在站内显示的目的。
一、获取google自定义搜索框及搜索结果代码
有Adsense账户的可以直接在Adsense中跟据向导生成搜索广告代码,在生成步骤中需要注意两点:
1、指定的搜索网站列表要填写正确(如果只针对自己站点搜索,输入站点地址即可),比如:http://www.xiuchezu.com/;
2、选定搜索结果页时,可以先自己确定搜索结果页面(目前还没添加,将在下面说明),比如:http://www.xiuchezu.com/site-search,这是之后要添加生成的搜索结果代码的页面;
然后所有设置步骤完成后,会生成搜索框和搜索结果代码。
没有Adsense账户的只能使用 Custom Search element了:
1、进入www.google.com.hk/cse/,当然这个google账户必须得有(请原谅我话多)。

2、根据向导填写每个步骤的信息,要注意的是自定义搜索引擎免费版本有广告的,可以在添加后通过css处理iframe隐藏广告。
3、搜索结果的样式可以选择几个默认样式或自定义样式。下一步得到最终的搜索结果代码。

二、将生成的搜索框、搜索结果代码放到wordpress主题中。
在Adsense账户中获取的有两段代码,一是搜索框代码,另一是搜索结果代码:
1、把获取的搜索框代码替换掉主题中searchform.php文件中的form节点的所有内容,如下:

<form id="cse-search-box" action="http://www.xiuchezu.com/site-search">
<div><input type="hidden" name="cx" value="********************" /> <input type="hidden" name="cof" value="FORID:11" /> <input type="hidden" name="ie" value="UTF-8" /> <input type="text" name="q" size="31" /> <input type="submit" name="sa" value="搜索" /></div>
</form>
<script type="text/javascript" src="http://www.google.com.hk/cse/brand?form=cse-search-box&amp;lang=zh-Hans">// <![CDATA[
mce:0
// ]]></script>

其中name=”cof”的input的 value=”FORID:11″查看google的解释,我之前设置forid值为10的时候,宽度是设置不了795以下的,改成11可以将宽度设置为600以下以适应主题。效果见本站点的搜索结果。
name=”ie”的input是输入字符的编码,取决于在生成代码过程中国家区域的选择,我之前是GB2312,输入的中文就显示乱码,改成UTF-8就OK。
修改搜索结果宽度则在搜索结果代码中修改,如下:

 var googleSearchFrameWidth = 630;

2、剩下的就是要创建搜索结果页面,处理搜索结果代码了。
新建一个文件如google-site-search.php,将要显示的博客站点框架加进来,比如head,sidebar,footer,再把搜索结果代码放到适当的地方,比如content中。
接着就要小用下wordpress的自定义页面的功能了,将如下代码添加到google-site-search.php文件的最前面。

<!--?php /* Template Name:google-site-search */ ?-->

在wordpress后台添加新页面,在模板下拉框中选择刚添加的google-site-search,修改固定链接,如:http://showyo.net/site-search/,这个链接就是刚才在上面生成代码的时候用到的搜索结果链接地址。

针对使用Custom Search element方法:
生成的代码只有搜索结果的,从建立文件到代码放置到新建页面都跟上面提到的Adsense搜索结果代码一致,不一样的是这边没有搜索框代码,但是这个是比较容易解决的,在searchform.php作小修改,只要使用“get”方法,表单结构自定义,将结果正确提交到搜索结果页面即可。类似代码如下:

<form action="http://www.xiuchezu.com/site-search/" method="get">
<div class="s input"><input id="q" type="text" name="q" /> <input type="hidden" name="ie" value="UTF-8" /></div>
<div class="s"><input class="icon" id="searchsubmit" type="submit" value="搜 索" /></div>
</form>

然后处理下CSS,让它和你的站点水乳交融就OK乐。具体可以看下本站的google自定义搜索结果的效果哦。

读了“湿营销”后

实话说对营销这个东西没有什么概念,也就谈不上什么湿营销了。偶然在豆瓣上看到“湿营销”(作者Tom Hays和Michael S. Malone,曹蔓 译,见文章的插图)这本书,读者评价不错,就买了看看,也当做给自己空缺的一大片知识面填补一小块空白吧。基本上书本一半的内容是在上下班的地铁上看完的,所以可能错过细读一些精彩内容,有空继续补之,当然也不知道错过了多少与美女对视的惊险场面,给擦肩而过的美女们造成的不便之处敬请见谅(莫名自恋中。。弱弱地请帅哥们飘过~~)。好了,下面华丽地转正经切入主题。

看这本书是从三个推荐序看起的,看了第一个序“人是湿的”,觉得在瞎扯淡,而且是扯到蛋疼的那种,二序和三序还稍微让我懂了点那个啥湿营销;看完整书后再来看一序,还是觉得大部分在扯淡,但还是赞同了里边的“阴阳”论与“和而不同”论:这里描述的“阴阳”就是道教里阴阳结合,你中有我、我中有你的境界,但是阴和阳又有本质的区别,这样理解的话“阴阳”论也就与“和而不同”一个意思了。

作者从人类学、社会学、心理学的角度描述社会群体中的人的关系是趋向于亲密而自然的,这也是人性深处最本质的需求。人与人无论从思想、行动等等方面是有种种不同的,但又因为这种亲密自然的需求,就有了《周易.系辞》里的“方以类聚,物以群分”的道理。

“群分”不管是在原始社会里的部落还是现代社会的民族概念,或者讲小点到人们自发组成的各种兴趣爱好协会、小组、俱乐部等,这些都说明了人的“群分”本性。作者提到的一个有意思的人类学现象,人类的部落集群是有极限的,当一个组织机构的人数不断膨胀后,组织中人与人之间的直接交流会变得越来越困难,这样导致组织很有效率的交流及工作。

据研究,一个组织的人数一般在150人以内会保持效率及灵活性。这个在原始族群中就有明显的体现,在亚马逊里原始部落里,每个部落有一个萨满管理人们的生、死及族群发展的重大决定,而这个重大决定通常是关于族群的拆分,即把族群一分为二,这就是原始部落的组织拆分以方便首领的直接管理。同时从原始部落的集群还可以得出:一个人可以通过蜂窝式群体组织,让不同区域的人先组成较小的群体,而后较小的群体再组织成一个大圈子。

个人觉得马云的“Small is beautiful”的新型企业观点与以上这种群体的趋小化观点大同小异,只是马云说的是“Small is beautiful for 21st century”而已。这当然仍不能很好地解释SNS疯狂兴起的现象,但是不同的价值观、不同爱好等因素形成了SNS中不同的群体,不同的关注群组,这之中爆发的众多群体引爆了碎片化经济的圈子世界,开启所谓新的“湿营销”时代。

因为这本书主要讲的是湿营销,所以作者偏向的还是湿营销基于的所谓的新的互联网营销模式,而把传统的大众营销方式视为一定会被取代的模式,本人虽然也很不好意思地跟作者的观点类似,但是目前中国的传统的大众营销模式还是占据相当大的一部分市场的,这可能部分取决中国的消费者教育程度,因此在中国要谈新型网络营销完全取代传统营销仍有待时日,只能说前者在灰常迅速地奔跑。而传统大众营销由于一度地强奸用户,无论从电视、报纸、杂志,充满了人们不喜欢的“在广告中插播电视剧、在广告中插入新闻消息”等的硬性强加行为,估计会加速其灭亡。

在这个发展迅猛的碎片化经济时代,新型网络营销人员怎样为自己的产品做广告呢?书中讲的很大一部分也就是怎么融入各个有不同兴趣爱好,思想观点千差万别的群体们中。

作者从人们在社会群体中的极力维护群体观点(不管客观对错),排斥不同群体言论及成员,并竭力争取在群体中的地位以及群体中的个别观点或部分人观点如何通过明星效应等形成信息瀑的病毒式传播等等人类社会学观点,解释了SNS中群体间关系、消息的高速传播及SNS中很明显的明星效应,当然更重要的,所有的这些都是新型网络营销人员面临的挑战和机遇(承认这俩词好用且和谐)。个人觉得这部分精彩、描述的挺在理,毕竟接触的SNS看到的那些灰常厉害的明星效应、很尖锐的不同群体中的言论针锋相对(如twitter上针对那个啥裆的啥啥,有上推的笑而不语~)等等。

对碎片化经济时代的各种圈子现象的一大堆人类学阐述后,作者在后面的三篇分别从SNS用户消费者的思维,如何提供不同群体消费者所需,不冗余、不繁杂、不强硬(不小心搞出个“三不”,请原谅我的有才~)地参与SNS等网络媒介用户群体的讨论,并成为群体认同的成员切入,提出“身份即营销”的强大观点。

在湿营销时代一篇,讲到真实人性的回归,同样包含的人类学、社会学外加少许经济学的元素,讲到了“湿”的深层次含义,讲得我面红耳赤(请排除一切邪念阅读本句)。最后讲到湿营销的信任体系,也是营销人员如何融入立场坚定、极力排外的各种群体中最为基本的要素。

整书下来,人的社会群体生存之道、交流之道占书内容的很大部分,可以说很多观点都是围绕这个展开的,因此,看这本书是有点在学人类学、社会学、心理学的味道。这本书目前我只看了一遍,觉得看起来、理解起来还是通的,遍观现在的忒特、非死不可、新浪围脖等SNS或新型网络群体中的湿营销还是很常见的,说明新的碎片化经济是真的在迅猛发展。所以这书可以稍微关注下。

wordpress rss feed显示html标签问题

今天不小心发现了博客的feed打开不了了,于是打开定制的feedsky查看博客的feed更新状态,发现feedsky报告源地址为http://www.xiuchezu.com/?feed=rss2,出现如下错误:

打开http://www.xiuchezu.com/?feed=rss2发现链接地址变为永久链接的http://www.xiuchezu.com/feed/(我之前有设置过固定链接),打开后发现是错误页面,也就是说feed失效了??没错,确实失效了,为什么呢,很不幸google半天都找不到解决方案。

回想了一下下,好像之前有装过feedsky的相关插件,会不会跟这个有关系呢?查看了下服务器文件,果然,根目录下有一个feed命名的文件夹,是之前插件生成的文件夹。显然,是因为这个文件夹路径与feed固定链接路径冲突导致访问异常。我把feed文件夹删除之后,http://www.xiuchezu.com/feed/打开正常,feedsky更新状态就正常了。

问题如果只是这样也就OK了,只是。。。

当我打开http://www.xiuchezu.com/feed/的时候,发现文章的html标签都没有了,这样在阅读器中显示的文章效果一团糟,我查看了一下另一个wordpress的feed,默认feed是有html标签的,也就是说wordpress feed并没屏蔽html标签(就当废话吧),进入后台管理中的设置->阅读菜单:

我原来Feed中的每篇文章,显示项中选择摘要,当选择全部文字后,feed中html正常显示,当选择摘要的时候,有摘要的文章显示摘要,没有摘要的文章显示所有,但是丢掉了HTML标签。选择全部文字后就正常了,包括含有more标签的文章也全文输出feed。

js处理导航菜单当前位置高亮

在wordpress中,当用Category或Page当导航菜单的话,wordpress已经处理好当前li标签的class了,所以用wordpress的Category或Page当导航菜单不用作本文的处理。我在bbpress中,想把每个父版块做成导航菜单的时候碰到这个问题的。

Jeremy Keith 在《JavaScript DOM编程艺术》一书中为我们做了一个小的例子,例子中使用了一段简单的高亮当前位置的 js 代码,代码如下:

function highlightPage() {
       if (!document.getElementsByTagName) return false;
       if (!document.getElementById) return false;
       if (!document.getElementById("navigation")) return false;

       var nav = document.getElementById("navigation");
       var links = nav.getElementsByTagName("a");

       for (var i=0; i<Links.length; i++){
              var linkurl = links[i].getAttribute("href");
              var currenturl = window.location.href;
              if (currenturl.indexOf(linkurl) != -1) {
                    links[i].className = "here";
                    var linktext = links[i].lastChild.nodeValue.toLowerCase();
                    document.body.setAttribute("id",linktext);
              }
        }
}

原理很简单,就是首先获取当前的网址,然后遍历菜单栏,如果当前网址包含了菜单栏内的链接地址,就给这个链接的 class 定义为 here,从而达到高亮的效果。但是我在使用的过程中遇到了一些特殊情况,这段代码就不能满足我的要求了。

先看以下菜单栏:

<div id="menu">
   <ul>
      <li><a href="http://yoursite.net/">首页</a></li>
      <li><a href="http://yoursite.net/2">第二页</a></li>
      <li><a href="http://yoursite.net/3">第三页</a></li>
   </ul>
</div>

如果我是用了上述 js 代码,那么当我停留在第二页或者第三页的时候首页同时也会被高亮,这并不是我想要的结果,解决方法有二:

第一种方法,可以在首页的网址后边再加一个 index.php 或者其他的后缀,这样子就可以解决问题,优点是简单,缺点是无法解决更复杂的菜单,比如在一个使用了 URL 静态化的二级页面,这种方法将无法实施。

第二种方法,采用 split() 获得更精确的定位。

split() 方法用于把一个字符串分割成字符串数组,这里我们将网址通过“/”进行拆分,首先是拆分当前网址:

  
var windowLocation = window.location.href.split("/");

现在我们将网址拆分成一个数组,然后我们将获得网址最后的部分,但是需要注意的是,http://yoursite.net/ 和 http://yoursite.net 是两个不同的网址,如果我们漏掉了其中的一个,可能会导致我们的脚本无效,因此我们需要判断这个网址的末尾是否含有”/”。

JS 代码:

var windowLocation = window.location.href.split("/");
var locationLong = windowLocation.length;
var locationHref;

if(windowLocation[locationLong-1] == "") {
    locationHref = windowLocation[locationLong-2];
} else {
    locationHref = windowLocation[locationLong-1];
}

当当前的网址含有”/”也就是 windowLocation 的最后一个为”"的时候,我们就取倒数第二个值,否则我们就取倒数第一个值。取好当前网址的值后,同理我们也要这样子取一下菜单栏的地址:

var menu = document.getElementById("menu");
var menuLinks = menu.getElementsByTagName("a");

for(var i=0; i<menuLinks.length; i++){
     var linksHref = menuLinks[i].split("/");
     var linksLong = linksHref.length;
     var linksRighthref;

     if(linksHref[linksLong-1] == "") {
          linksRighthref = linksHref[linksLong-2];
     } else {
          linksRighthref = linksHref[linksLong-1];
     }
     //hight menu
    if(locationHref == linksRighthref) {
          menuLinks[i].className = "here";
     }
}

当然,如果页面还有存在页面翻页参数,如在同一个菜单下默认第一页链接是http://yoursite.net/,第二页链接是http://yoursite.net/?page=2,这样必须在后面的截取段进行进一步的if匹配判断,方法类似。
这样代码就可以解决我们的问题了,优点是实用性较高,但是代码有些复杂。

本文部分内容来自:http://blog.admin9.com/?p=888

jquery实现有默认内容的展开收起效果

前些天在实现一个网站的需求:1、展示一个分类里边的条目;2、默认显示前十条,并显示一个展开按钮,点击展开按钮,点击后下拉显示该分类下所有条目;3、展开后,按钮变为收起按钮,点击收起恢复为默认显示条目。

试图想用jquery的slideToggle()animate()函数实现,发现行不通,处理不了剩余内容的展开收起。后来想到通过联系展开按钮的class与主题内容的id匹配,在按钮的click事件里通过修改css和jquery的animate()函数结合实现想要的效果,代码如下(这边显示一段文字,默认显示文字前面的一部分内容):

jquery部分:

$(".more-btn").click(function(){
			 if($("#"+this.parentNode.className).css("height")=="100px"){
			  	$("#"+this.parentNode.className).css("height","auto");
			  	this.value="收起";
			  }
			 else{
			 	$("#"+this.parentNode.className).animate({height:"100px"},"slow");
			 	this.value="展开";
			 	}
		  });

css部分:

body {
			margin:0;
			padding:40px 40px;
			background:#fff;
			font:Arial, Helvetica, sans-serif;
			font-size:13px;
			line-height:180%;
		}
		.case{
			width:240px;
		}
		.entry{
			clear: both;
			line-height: 1.6em;
			width:223px;
			height:100px;
			overflow:hidden;
			line-height:18px;
			border-top:#ff9933 1px solid;
			border-left:#ff9933 1px solid;
			border-right:#ff9933 1px solid;
			padding-left:5px;
		}
		.more-btn{
			padding:0;
			width:230px;
			border-left:#ff9933 1px solid;
			border-right:#ff9933 1px solid;
			height:27px;
			background:#f4dc92;
			font-size:14px;
			font-weight:bolder;
			color:#000;
		}             

html代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>demo</title>
</head>
<body>
	<div class="case">    	
	<div class="entry" id="entry-1">	
	      	显示的内容在此。。。
	</div><!--entry-->
	<div class="entry-1"><input type="button"  class="more-btn" value="更多" /></div>	
	</div> <!--case-->
</body>
</html>

demo:猛击此查看demo