分类目录归档:技术

cocos2d-x 截图保存到本地

游戏推广的一个很重要组成就是玩家分享,所以游戏截图就起到很大作用了。截图功能通过CCRenderTexture实现。

1.CCRenderTexture

CCRenderTexture是一个通用渲染对象,可以通过构建一个CCRenderTexture对象,把要渲染的东西填充进去,在渲染开始前调用call函数,调用cocos的场景的visit函数对其进行渲染,渲染结束后调用end函数。

CCRenderTexture继承于CCNode,所以可以简单地把渲染纹理添加到你的场景中,就像处理其它cocos中的节点一样,当然它还提供了保存功能,可以把渲染纹理保存为PNG或JPG格式。

在实际应用中中,只是获取渲染的纹理还不够,还要考虑不同尺寸设备的截图范围,当然,这个是针对采用kResolutionNoBorder屏幕适配方案来说,下面的粒子就是针对kResolutionNoBorder。

2.API

//创建和初始化函数
   static CCRenderTexture * create(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat);
   static CCRenderTexture * create(int w, int h, CCTexture2DPixelFormat eFormat);
   static CCRenderTexture * create(int w, int h);
   bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat);
   bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat);

   //开始获取
   void begin();

   //开始渲染时清除之前渲染的内容
   void beginWithClear(float r, float g, float b, float a);
   void beginWithClear(float r, float g, float b, float a, float depthValue);
   void beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue);

   //结束获取
   void end();

   //清除纹理
   void clear(float r, float g, float b, float a);
   void clearDepth(float depthValue);
   void clearStencil(int stencilValue);

   //保存纹理为图片文件,可以选择JPG/PNG格式,默认是JPEG格式,成功返回真
   bool saveToFile(const char *szFilePath);
   bool saveToFile(const char *name, tCCImageFormat format);

3.示例

修改HelloWorld中结束菜单的回调函数如下:


void CTestLayer::menuCloseCallback(CCObject* pSender)
{
    SaveScreenShot();
}

//截图功能
void CTestLayer::SaveScreenShot()
{
 //获取屏幕尺寸
//CCDirector::sharedDirector()->getVisibleSize();
CCSize size =  CCDirector::sharedDirector()->getWinSize();
CCPoint origin = VisibleRect::getVisibleRect().origin;
CCSize visableSize = VisibleRect::getVisibleRect().size;

//使用屏幕尺寸初始化一个空的渲染纹理对象
CCRenderTexture* texture = CCRenderTexture::create((int)size.width, (int)size.height);

//设置位置
texture->setPosition(ccp(size.width/2, size.height/2));
CCScene* curScene = CCDirector::sharedDirector()->getRunningScene();

//开始获取
texture->begin();

//遍历场景节点对象,填充纹理到texure中
curScene->visit();

//结束获取
texture->end();

//保存为PNG图
if (origin.x == 0 && origin.y == 0) {
texture->saveToFile("screenshot.png", kCCImageFormatPNG);
return;
}

// 适配不同尺寸设备,只截取屏幕大小的尺寸,没采用kResolutionNoBorder就不用下面的处理

CCRect finalRect = CCRectMake(origin.x, origin.y, visableSize.width, visableSize.height);
CCSprite *sprite = CCSprite::createWithTexture(texture->getSprite()->getTexture(), finalRect);
sprite->setAnchorPoint(CCPoint(0, 0));
sprite->setFlipY(true);
CCRenderTexture *finalRtx = CCRenderTexture::create(visableSize.width, visableSize.height);
finalRtx->begin();
sprite->visit();
finalRtx->end();
finalRtx->saveToFile("screenshot.png", kCCImageFormatPNG);
}

away3d传送门特效

away3d 4.0加入特效系统后,做比较炫的特效已经不是什么难事,away3d官方已经基于内置的particle system做了几个挺炫的粒子特效,需要的可以查看github中实例源码。这个传送门特效且作为away3d 粒子系统练手的的另一个实例。

简单说下,这个特效由三个独立的部分组成,转动的符文圆盘、向上发射的半透明光及不断从符文盘向上浮动的颗粒。效果图如下:

Snip201303_3

废话不说,直接上代码解释。

首先,必须准备三个部分的纹理和材质了。分别是符文圆盘、光辉、浮动粒子的纹理,在本教程最后提供的源码里有相应的纹理。

[Embed(source="../assets/pan.png")]
public  var panTexture:Class;

[Embed(source="../assets/light.png")]
public  var lightTexture:Class;

[Embed(source="../assets/blueball.png")]
public  var blueballTexture:Class;

材质拆分成五个部分,分别对应五种不同的粒子mesh。简单说为什么有五种,因为:1圆盘+3光辉+1浮动粒子串。粒子对应的五种material、mesh、animator等的声明如下,它们之间的关系我们将在后面代码解释。

//materials
private var matPan:TextureMaterial;
private var matLight1:TextureMaterial;
private var matLight2:TextureMaterial;
private var matLight3:TextureMaterial;
private var matBall:TextureMaterial;

//particle objects
private var particleAnimationSet1:ParticleAnimationSet;
private var particleAnimationSet2:ParticleAnimationSet;
private var particleAnimationSet3:ParticleAnimationSet;
private var particleAnimationSet4:ParticleAnimationSet;
private var particleAnimationSet5:ParticleAnimationSet;
private var particleGeometry1:ParticleGeometry;
private var particleGeometry2:ParticleGeometry;
private var particleGeometry3:ParticleGeometry;
private var particleGeometry4:ParticleGeometry;
private var particleGeometry5:ParticleGeometry;

// scene objects
private var particleMesh1:Mesh;
private var particleMesh2:Mesh;
private var particleMesh3:Mesh;
private var particleMesh4:Mesh;
private var particleMesh5:Mesh;
private var animator1:ParticleAnimator;
private var animator2:ParticleAnimator;
private var animator3:ParticleAnimator;
private var animator4:ParticleAnimator;
private var animator5:ParticleAnimator;

接着就是初始化引擎了,为了便于理解,采用跟官方一样的方法,顺便贴出代码。

//engine variables
private var scene:Scene3D;
private var camera:Camera3D;
private var view:View3D;
private var cameraController:HoverController;

//signature variables
private var Signature:Sprite;
private var SignatureBitmap:Bitmap;

//navigation variables
private var _move:Boolean = false;
private var _lastPanAngle:Number;
private var _lastTiltAngle:Number;
private var _lastMouseX:Number;
private var _lastMouseY:Number;

/**
 * Initialise the engine
 */
private function initEngine():void
{
	stage.scaleMode = StageScaleMode.NO_SCALE;
	stage.align = StageAlign.TOP_LEFT;

	scene = new Scene3D();
	camera = new Camera3D();

	view = new View3D();
	view.scene = scene;
	view.camera = camera;

	//setup controller to be used on the camera
	cameraController = new HoverController(camera, null, 255, 50, 300, 5);
	view.addSourceURL("srcview/index.html");
	addChild(view);

	//add signature
	Signature = Sprite(new SignatureSwf());
	SignatureBitmap = new Bitmap(new BitmapData(Signature.width, Signature.height, true, 0));
	stage.quality = StageQuality.HIGH;
	SignatureBitmap.bitmapData.draw(Signature);
	stage.quality = StageQuality.LOW;
	addChild(SignatureBitmap);

	addChild(new AwayStats(view));
}

引擎准备完后就是进入主题了,开始制作各种粒子。away3d中的生成粒子有几个步骤:

1、定义粒子需要的几何体集合,比如该教程中传送门的转动圆盘需要1个plane, 浮动的粒子需要50个plane来表现;

2、根据几何体几何,生成粒子需要的ParticleGeometry;

3、根据ParticleGeometry、Material,生成粒子mesh;

4、定义particleAnimationSet。粒子中的动作节点分为局部静态动作、局部动态动作还有全局动作,一个particleAnimationSet可以添加以上多种类型的不同动作节点;进一步了解动作细节,点击查看官方文档

5、根据particleAnimationSet定义ParticleAnimator,并把ParticleAnimator装到粒子mesh上。

6、设置粒子的start time,duration time, delay time及局部动作属性。

文字太抽象,下面是直观的代码:


		/**
		 * 初始化粒子材质
		 */
		private function initMaterials():void
		{
			matPan = new TextureMaterial(Cast.bitmapTexture(panTexture));
			matBall = new TextureMaterial(Cast.bitmapTexture(blueballTexture));
			matLight1 = new TextureMaterial(Cast.bitmapTexture(lightTexture));
			matLight2 = new TextureMaterial(Cast.bitmapTexture(lightTexture));
			matLight3 = new TextureMaterial(Cast.bitmapTexture(lightTexture));

			matPan.bothSides = true;
			matPan.smooth = true;
			matPan.repeat = true;
			matPan.alphaBlending = true;
			matPan.specularColor = 0xFF1161D9;
			matPan.ambientColor = 0xFF1161D9;

			matLight1.bothSides = true;
			matLight1.smooth = true;
			matLight1.alphaBlending = true;

			matLight2.bothSides = true;
			matLight2.smooth = true;
			matLight2.alphaBlending = true;

			matLight3.bothSides = true;
			matLight3.smooth = true;
			matLight3.alphaBlending = true;

			matBall.bothSides = true;
			matBall.repeat = true;
			matBall.smooth = true;
			matBall.alphaBlending = true;
		}

		/**
		 * 初始化粒子
		 */
		private function initParticles():void
		{
			// 设置所有粒子的particleGeometry
			var ball:Geometry = new PlaneGeometry(3,3,1,1,false);
			var light1:Geometry = new CylinderGeometry(50,30,200,16,1,false,false,true);
			var light2:Geometry = new CylinderGeometry(55,30,180,16,1,false,false,true);
			var light3:Geometry = new CylinderGeometry(60,30,160,16,1,false,false,true);
			var pan:Geometry = new PlaneGeometry(65,65,1,1);

			var ballGeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light1GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light2GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light3GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var panGeoSet:Vector.<Geometry> = new Vector.<Geometry>();

			// particle ball
			for (var i:int = 0; i < 40; i++) {
				ballGeoSet.push(ball);
			}
			particleGeometry1 = ParticleGeometryHelper.generateGeometry(ballGeoSet);

			particleAnimationSet1 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet1.addAnimation(new ParticleBillboardNode());
			particleAnimationSet1.addAnimation(new ParticleVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,30,0)));
			particleAnimationSet1.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false,false,new ColorTransform(), new ColorTransform(1,1,1,0)));
			particleAnimationSet1.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.addAnimation(new ParticleOscillatorNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.addAnimation(new ParticleVelocityNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.initParticleFunc = initBallParticleProperties;

			// light 1
			light1GeoSet.push(light1);
			particleGeometry2 = ParticleGeometryHelper.generateGeometry(light1GeoSet);
			particleAnimationSet2 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet2.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,100,0)));
			particleAnimationSet2.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet2.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet2.initParticleFunc = initLightParticleProperties;

			// light 2
			light2GeoSet.push(light2);
			particleGeometry3 = ParticleGeometryHelper.generateGeometry(light2GeoSet);
			particleAnimationSet3 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet3.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,90,0)));
			particleAnimationSet3.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet3.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet3.initParticleFunc = initLightParticleProperties;

			// light 3
			light3GeoSet.push(light3);
			particleGeometry4 = ParticleGeometryHelper.generateGeometry(light3GeoSet);
			particleAnimationSet4 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet4.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,90,0)));
			particleAnimationSet4.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet4.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet4.initParticleFunc = initLightParticleProperties;

			// pan
			panGeoSet.push(pan);
			particleGeometry5 = ParticleGeometryHelper.generateGeometry(panGeoSet);
			particleAnimationSet5 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet5.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,8)));
			particleAnimationSet5.initParticleFunc = initPanParticleProperties;

		}

		/**
		 * 初始化场景
		 */
		private function initObjects():void
		{
			particleMesh1 = new Mesh(particleGeometry1, matBall);
			particleMesh2 = new Mesh(particleGeometry2, matLight1);
			particleMesh3 = new Mesh(particleGeometry3, matLight2);
			particleMesh4 = new Mesh(particleGeometry4, matLight3);
			particleMesh5 = new Mesh(particleGeometry5, matPan);
			scene.addChild(particleMesh1);
			scene.addChild(particleMesh2);
			scene.addChild(particleMesh3);
			scene.addChild(particleMesh4);
			scene.addChild(particleMesh5);

			animator1 = new ParticleAnimator(particleAnimationSet1);
			animator2 = new ParticleAnimator(particleAnimationSet2);
			animator3 = new ParticleAnimator(particleAnimationSet3);
			animator4 = new ParticleAnimator(particleAnimationSet4);
			animator5 = new ParticleAnimator(particleAnimationSet5);

			particleMesh1.animator = animator1;
			particleMesh2.animator = animator2;
			particleMesh3.animator = animator3;
			particleMesh4.animator = animator4;
			particleMesh5.animator = animator5;

			animator1.start();
			animator2.start();
			animator3.start();
			animator4.start();
			animator5.start();
		}

		/**
		 * 初始化浮动粒子属性
		 */
		private function initBallParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = Math.random()*4.1;
			properties.duration = 3;
			var degree1:Number = Math.random() * Math.PI ;
			var degree2:Number = Math.random() * Math.PI;
			var r1:Number = Math.random()*15;
			var r2:Number = Math.random()*20 +10;
			properties[ParticlePositionNode.POSITION_VECTOR3D] = new Vector3D(r1*Math.sin(degree1), 0 , r1*Math.cos(degree1));
			properties[ParticleOscillatorNode.OSCILLATOR_VECTOR3D] = new Vector3D(r2*Math.sin(degree2), 0 , r2*Math.cos(degree2), Math.random()*4+2);
			properties[ParticleVelocityNode.VELOCITY_VECTOR3D] =  new Vector3D(0,Math.random()*100,0);
		}

		/**
		 * 初始化光辉属性
		 */
		private function initLightParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = 1;
			properties.duration = 10;
		}

		/**
		 * 初始化符文转盘属性
		 */
		private function initPanParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = 1;
			properties.duration = 1;
		}

猛击下载源码

Away3d粒子系统学习与深入I

最近在做基于away3D引擎的粒子编辑器,因此,有了这部分文章作为mark。

Away3D从4.1alpha之后开始加入了粒子系统功能,再次感谢国内牛人LiaoCheng,是他向away3d贡献的这部分功能,大家鼓掌。

简介

所谓粒子系统是表示三维计算机图形学中模拟一些特定的模糊现象的技术,游戏开发中经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者像发光轨迹这样的抽象视觉效果等等。

Away3D中的粒子系统实现思路

跟大部分游戏的引擎的粒子系统实现思路类似,Away3D的粒子系统遵循以下基本原则:

  1. 每个粒子由带纹理的多边形或几何体构成;
  2. 粒子按一定的规则运动;
  3. 每个粒子有自己的属性,如出生时间、生命周期、位置、速度、加速度、尺寸、颜色、运动轨迹等等;

粒子生成步骤

在Away3D中,由n个粒子组成的粒子特效(为了与广泛的粒子系统区分,我们下面都称为粒子特效)主体,被抽象为一个mesh,而mesh的材质就是给粒子添加的材质,再往mesh中添加animator就组成了一个完整的粒子特效。下面我们就如何构建一个粒子mesh,以及如何为mesh添加动作器展开分析。

构建ParticleGeometry

在上面的的粒子系统基本思路说过:每个粒子由带纹理的多边形或几何体构成,而组成一个完整的粒子特效,则需要n个这样的多边形或几何体,这边我们且称为Geometry Set,我们可以往这个geometry set添加各种Away3D内置的几何体或者外部定义的几何体。出于性能的考虑,建议粒子特效里边最好不要添加太复杂的几何体。

举一个简单的栗子,我们要构建一个向上喷发的小球,就可以如下构建该粒子特效:

var ball:Geometry = new PlaneGeometry(3,3,1,1,false);
var ballGeoSet:Vector.<Geometry> = new Vector.<Geometry>();
// particle ball
for (var i:int = 0; i < 50; i++) {
     ballGeoSet.push(ball);
}

上面的代码,我们添加了包含50个Plane的geometry set,填充这个set可以视情况添加,如果有需要,你也可以同时添加plane、cube、sphere等几何体到这个set里边。准备好geometry set后,我们将使用Away3D提供的particle geometry helper将geometry set转化为最终的ParticleGeometry:

particleGeometry = ParticleGeometryHelper.generateGeometry(ballGeoSet);

这样,我们就完成的一个尚未添加动作的ParticleGeometry实例。

构建ParticleAnimationSet

接下来就是创建粒子动作集合了。类似传统的粒子系统,一个粒子是有其生命周期的,在这个周期中,粒子控制着它所有的属性,位置、轨迹等等。

在Away3D中,一个粒子有关联系统时间的startTime属性,代表粒子出生时间;有可选的duration属性(若没设置duration属性,粒子将无限延续),代表粒子的持续存活时间,还有可选的delay属性(若没设置,将无间隔时间),代表两个重复的粒子周期之间的间隔时间。这些属性基于GPU代码的时间计算,我们必须对要使用的属性进行预定义。

Away3D粒子系统中为粒子动作提供了动作集合ParticleAnimationSet类,构造ParticleAnimationSet需要三个bool参数,第一个参数表示是否使用粒子的duration属性,第二个表示是否不断循环粒子的生命周期,第三个表示是否使用粒子的delay属性,使用则当一个粒子周期执行完成,粒子会消失delay的时间后再进行新的周期。

还是上面的栗子,我们把三个属性都设置为true。

var animationSet:ParticleAnimationSet = new ParticleAnimationSet(true, true, true);
animationSet.initParticleFunc = initParticleParam;

值得注意的是,startTime、duration、delay并不是ParticleAnimationSet的属性,粒子特效中的每个粒子都有自己特有的以上三个属性,在Away3D中,他们被设定为local static属性,我们使用一个函数来定义以上的属性,然后赋给ParticleAnimationSet的initParticleFunc,这样,每个粒子都会调用这个函数一遍。通常,使用ParticleProperties::index这个值设置startTime、duration、delay三个属性,达到每个粒子有不同的以上属性的目的。栗子如下:

private function initParticleParam(prop:ParticleProperties):void {
    prop.startTime = prop.index * 0.005;
    prop.duration = 10;
    prop.delay = 5;
}

以上代码中,我们实现了每5毫秒产生一个粒子,每个粒子延续10秒,然后休眠5秒。

在之后的local static动作属性的设置中,我们也将所用动作的属性设置添加到这个独立的函数中。

Flash和JavaScript互相调用

1.Flash调用JavaScript中的方法
ExternalInterface.call(“js方法名”,“方法入参”);

2.JavaScript调用Flash中的方法
Flash中注册ExternalInterface.addCallback(“供js调用的方法”, flash方法);

例子:

exp.html

<html>
<head>
<title>swfobject demo</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
function jsAlert(txt)
{
alert(txt);
}
function flashAlert()
{
var obj = document.getElementById("demo");
obj.flashAlert();
}
</script>
</head>
<body>
<div id="content">
NO Flash Player or NO Support This Flash Player!
</div>
<script type="text/javascript">
var so = new SWFObject("Testjs.swf", "demo", "640", "480", "10", "#000000");
so.addParam("wmode", "transparent");
so.addParam("allowFullScreen", "true");
so.write("content");
</script>
<input type="Button" value="警告" onclick="flashAlert()"/>
</body>
</html>

exp.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="<a href="http://www.adobe.com/2006/mxml" rel="nofollow">http://www.adobe.com/2006/mxml</a>" layout="absolute" creationComplete="init()">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import flash.external.ExternalInterface;
private function init():void
{
ExternalInterface.addCallback("flashAlert", flashAlertTest);
}
private function doAlert(evt:Event):void
{
ExternalInterface.call("jsAlert", "js Alert");
}
private function flashAlertTest():void
{
Alert.show("flash Alert");
}
]]>
</mx:Script>
<mx:Button x="36" y="97" label="提示" click="doAlert(event)"/>
<mx:Label x="36" y="64" text="Flash调用js" width="135" height="25"/>
</mx:Application>

注意:
1. exp.html, exp.mxml, swfobject.js必须放到web服务器中,直接在本地打开,会出错:SecurityError:Error #2060:安全沙箱冲突:ExternalInterface调用者…

2. ExternalInterface.addCallback(“flashAlert”, flashAlertTest); 必须包含在方法中,然后触发它;否则,会出错:1120:访问的属性flashAlertTest未定义。
如:

private function init():void
{
ExternalInterface.addCallback("flashAlert", flashAlertTest);
}

init();

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

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的函数。

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自定义搜索结果的效果哦。

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