21.处理Effect

首先还是强调请先看懂Shader系列4:材质和光源,接下去的大部分思路来自于这个示例。

对于普通effect应用XNA框架自带的BasicEffect类就够了,但我想使用自己的Effect实现BasicEffect,如:漫反射、镜面高光、雾化等,而且还要实现BasicEffect不能处理的功能,如:点光源、聚光灯、法线映射等。因此首先在引擎类StunEngine.cs中添加以下变量及其对应的属性:

/// 
/// 使用Generic.fx effect文件的Effect
/// 
private Effect baseEffect; 

/// 
/// 获取使用Generic.fx effect文件的Effect
/// 
public Effect BaseEffect { get { return baseEffect; } } 

并在Initialize方法中初始化这个Effect:

baseEffect = Content.Load("Effects\\Generic");

对应的effect文件是Generic.fx,为了让代码分离易于使用,Generic.fx是由三部分组成的,分别是主体文件Generic.fx:

#include "Standard.inc"
#include "Lights.inc"

//-----------------------------------------------------------------------------------------------
//	Technique:	TexturedLights
//	
//	作者:		E. Riba (Riki) 08.08.2009
//
//	描述:	
//				VS: 传入位置,法线和纹理坐标 
//			    注意:顶点颜色不会被保留
//	
//				PS: 漫反射纹理采样, 
//					细节纹理采样
//					单向光计算 
//					点光源计算		  
//					聚光灯光源计算		  
//					
//-----------------------------------------------------------------------------------------------

// TexturedLights使用的顶点着色器的输入结构
struct TexturedLightsVSIn
{
	float4 Position : POSITION0;
	float3 Normal : NORMAL;
	float2 TexCoords : TEXCOORD0;
};

// TexturedLights使用的顶点着色器的输出结构
struct TexturedLightsVSOut
{
    float4 Position				: POSITION0;
    float2 TexCoords			: TEXCOORD0;
    float3 WorldNormal			: TEXCOORD1;
    float3 WorldPosition		: TEXCOORD2;     
};

TexturedLightsVSOut TexturedLightsVS(TexturedLightsVSIn input)
{
	TexturedLightsVSOut Output = (TexturedLightsVSOut)0;    
	
	//生成world-view-projection矩阵
	float4 worldPosition =  mul(input.Position, gWorld);
    float4x4 wvp = mul(mul(gWorld, gView), gProjection);
     
    // 转换输入顶点的位置、法线
    Output.Position = mul(input.Position, wvp);
    Output.WorldPosition = worldPosition / worldPosition.w;
    Output.WorldNormal =  mul(input.Normal, gWorld);    
     
    Output.TexCoords = input.TexCoords;     
	
    //返回输出结构
    return Output;
}

float4 TexturedLightsPS(TexturedLightsVSOut input) : COLOR
{
	float3 finalColor = 0;
    float4 diffuseColor = 0;    
    
    //	漫反射颜色来自于漫反射纹理或材质,首先将漫反射颜色设置为材质的漫反射颜色。
    diffuseColor = float4(gMaterialDiffuse, 1.0);
    
    //如果包含纹理,则gDiffuseEnabled为true,漫反射颜色取自纹理的颜色
    if(gDiffuseEnabled)   
    {
		float4 textureColor = tex2D(textureSampler, input.TexCoords* gDiffuseUVTile);    
        diffuseColor = textureColor;        
    }   
    
    //而细节颜色只取自细节纹理
    if(gDetailUVTile.x > 0&&gDetailUVTile.y>0)
    {	
		float4 detail = tex2D(detailSampler, input.TexCoords * gDetailUVTile);
		//将细节颜色调制到漫反射颜色上
		diffuseColor.rgb *= detail.rgb;
	}
    
    //归一化法线
    float3 N = normalize(input.WorldNormal);     
	
	//----------------------------
	//	遍历所有光源
	//----------------------------   
	for(int i=0; i < gTotalLights; i++)
	{    
		//只处理可用的光源
		if(gLights[i].enabled)
		{
			finalColor+= CalculateSingleLight(gLights[i], input.WorldPosition, N,diffuseColor);
		}
	}    
    
	//----------------------------------------
	//	添加环境光颜色和自发光颜色
	//----------------------------------------
	finalColor += (gAmbient * diffuseColor.rgb);
	finalColor += gMaterialEmissive;
    
     
	//---------------------------------------------------------------------------------
	//	如果打开雾化,则计算雾化颜色,基于发表在Xna社区论坛的John Watts公式
	//---------------------------------------------------------------------------------
	if(gFogEnabled)
		finalColor = LinearFog(finalColor, input.WorldPosition);	

	return float4(finalColor, diffuseColor.a);
}

technique TexturedLights
{
    pass Pass0
    {       
        vertexshader = compile vs_3_0 TexturedLightsVS();	
        pixelshader = compile ps_3_0 TexturedLightsPS();
    }
}
//--------------------------------------------
//	technique:TexturedLights的结尾
//--------------------------------------------

//-----------------------------------------------------------------------------------------------
//	Technique:	SimpleTextured
//	
//	作者:		E. Riba (Riki) 13.02.2009
//
//	描述:		VS: 传入(position,uv)
//				PS: 采样颜色纹理 + 采样细节纹理(如果enabled)
//-----------------------------------------------------------------------------------------------

// SimpleTextured使用的顶点着色器输入结构
struct SimpleTexturedVSIn
{
    float4 Position			: POSITION0;
    float2 TexCoords		: TEXCOORD0;
};

// SimpleTextured使用的顶点着色器输出结构
struct SimpleTexturedVSOut
{
    float4 Position			: POSITION0;
    float2 TexCoords		: TEXCOORD0;  
    float3 WorldPosition	: TEXCOORD1; 
};

SimpleTexturedVSOut SimpleTexturedVS(SimpleTexturedVSIn input)
{
	SimpleTexturedVSOut Output=(SimpleTexturedVSOut)0;
	
	//将顶点坐标转换到世界坐标系
	float4x4 viewProjection = mul (gView, gProjection);
	float4x4 worldViewProjection = mul (gWorld, viewProjection);
	Output.Position = mul(input.Position, worldViewProjection);
	
	//纹理坐标直接输出
	Output.TexCoords = input.TexCoords; 
	
	//计算顶点在世界空间中的位置
	float4 worldPosition =  mul(input.Position, gWorld);	
	Output.WorldPosition = worldPosition / worldPosition.w;
    
    return Output;
}

float4 SimpleTexturedPS(SimpleTexturedVSOut input) : COLOR0
{	
	float4 color;
	
	//	漫反射颜色来自于漫反射纹理或材质,首先将漫反射颜色设置为材质的漫反射颜色。
    color = float4(gMaterialDiffuse, 1.0);
    
    // 如果包含纹理,则gDiffuseEnabled为true,漫反射颜色取自纹理的颜色
    if(gDiffuseEnabled)
		color = tex2D(textureSampler, input.TexCoords* gDiffuseUVTile);
	
	//如果细节纹理平铺因子不为零,则采样细节纹理,并将细节纹理的颜色调制到最终颜色中   
    if(gDetailUVTile.x > 0&&gDetailUVTile.y>0)
    {		
		float4 detail = tex2D(detailSampler, input.TexCoords * gDetailUVTile);
		color.rgb *=  detail;		
    }
    
    // 如果打开雾化,则计算雾化颜色
    if(gFogEnabled)
		color.rgb = LinearFog(color.rgb, input.WorldPosition);
    
    return color;
}

technique SimpleTextured
{
    pass Pass0
    {       
        vertexshader = compile vs_3_0 SimpleTexturedVS();	
        pixelshader = compile ps_3_0 SimpleTexturedPS();
    }
}

//--------------------------------------------
//	technique: SimpleTextured的结尾
//--------------------------------------------

和两个include文件,其中standard.inc文件包含最常用的一些effect参数:

//----------------------------------------------------
//	共享变量,下列变量会被缓存到EffectPool中。
//----------------------------------------------------
shared uniform extern float4x4	gProjection : PROJECTION;     //投影矩阵

shared uniform extern float4	gFogColor = {0.1, 0.2, 0.3, 1.0};		// 雾化颜色
shared uniform extern float		gFogStart = 50;							// 雾化的起始距离
shared uniform extern float		gFogEnd = 400;							// 雾化的结束距离

shared uniform extern float		gTime;									// 时间

//----------------------------------------------------
//	下列变量在每一帧都要被设置
//----------------------------------------------------
uniform extern float4x4	gView		: VIEW;		//  视矩阵
uniform extern float4x4	gWorld : WORLD;			//	世界矩阵
uniform extern float3	gCameraPos;	            //	相机位置

uniform extern bool		gDiffuseEnabled;		//	是否从纹理采样漫反射颜色
uniform extern texture	gDiffuseTexture;		//	漫反射纹理,覆盖在整个模型上
uniform extern float2	gDiffuseUVTile;         //  漫反射纹理的平铺次数
uniform extern texture	gDetailTexture;			//	细节(噪点)纹理,平铺在模型上
uniform extern float2	gDetailUVTile;	        //	细节纹理平铺次数

uniform extern bool		gFogEnabled;			//	如果设为true,则计算雾化

//	漫反射颜色纹理采样器
sampler2D textureSampler = sampler_state
{
    Texture =
;
    magfilter = LINEAR;
    minfilter = LINEAR;
    mipfilter = LINEAR;
    AddressU = mirror; 
    AddressV = mirror;     
};

//	细节纹理采样器
sampler2D detailSampler = sampler_state
{
    Texture = ;
    magfilter = LINEAR; 
    minfilter = LINEAR; 
    mipfilter = LINEAR; 
    AddressU = wrap; 
    AddressV = wrap;   
};

//-----------------------------------------------------------------------------------------------
//	函数:	LinearFog
//	
//	作者:	E. Riba (Riki) 19.02.2009
//
//	描述:	计算线性雾化颜色
//
//	Shared参数: gFogStart,gFogEnd,gFogColor
//				
//-----------------------------------------------------------------------------------------------
float3 LinearFog(float3 color, float3 worldPosition)
{			
	float d = length(worldPosition - gCameraPos);  
	float l = clamp((d - gFogStart) / (gFogEnd - gFogStart), 0, 1);
	color.rgb = lerp(color, gFogColor.rgb, l);  
	return color;	
}

而Light.inc包含与光照相关的effect参数和计算光照的函数:

#define MAX_TOTAL_LIGHTS	8

// 光源类型
#define DIR_LIGHT			0
#define POINT_LIGHT			1
#define SPOT_LIGHT			2
#define NO_COLOR			float4(0,0,0,0);


//----------------------------------------------------
//	共享变量
//----------------------------------------------------
shared uniform extern int		gTotalLights;							//	光源数量
shared uniform extern float3	gAmbient;								//	环境光颜色

//----------------------------------------------------
//	材质参数
//----------------------------------------------------
uniform extern float3			gMaterialDiffuse;						//	材质漫反射颜色
uniform extern float3			gMaterialSpecular;						//	材质镜面高光颜色
uniform extern float			gMaterialSpecPower;						//	材质镜面高光强度
uniform extern float3			gMaterialEmissive;						//	材质自发光颜色

// 包含光源数据的结构
struct Light
{
    float enabled;      //光源是否打开    
    float lightType;	//光源类型
    float3 color;		//光源颜色
    float3 position;	//光源位置
    float3 direction;	//光线方向
    float4 spotData;	//四个分量分别保存range,falloff,theta,phi数据
};

//光源数组
shared Light gLights[MAX_TOTAL_LIGHTS];


//-----------------------------------------------------------------------------------------------
//	函数:	Attenuation
//	
//	作者:	E. Riba (Riki) 12.08.2009
//
//	描述:	计算随距离增大而减小的衰减值,例如lRange为10,falloff为1,则距离光源为5的顶点的光照只有一半
//				
//-----------------------------------------------------------------------------------------------
float Attenuation(float lDistance, float lRange, float falloff)
{
	float att = pow(saturate((lRange - lDistance) / lRange), falloff);	
    // 以下为简化计算方程
    //float att = 1 - saturate(lDistance*falloff / lRange);
    return att;    
}


//-----------------------------------------------------------------------------------------------
//	函数:	Lambertian因子
//	
//	作者:	E. Riba (Riki) 07.08.2009
//
//	描述:	基本的lambertian光照方程
//				
//-----------------------------------------------------------------------------------------------
float3 LambertianComponent(in float3 directionToLight, in float3 worldNormal, in float3 lightColor)
{
	float3 NdotL = saturate(dot(worldNormal,directionToLight));
	return NdotL * lightColor;
}

//-----------------------------------------------------------------------------------------------
//	函数:	Specular分量
//	
//	作者:	E. Riba (Riki) 08.08.2009
//
//	描述:	基于Phong公式的镜面高光公式
//				
//-----------------------------------------------------------------------------------------------
float3 SpecularComponent(in float3 directionToLight, in float3 worldNormal, in float3 worldPosition, in float3 lightColor, in float3 specularColor, in float specularPower)
{
	float3 specular = 0;
	if(length(specularColor)>0)
	{
		// 计算反射光方向
		float3 reflectionVector = normalize(reflect(-directionToLight, worldNormal));
		
		// 计算像素指向相机的向量,即视线反方向
		float3 directionToCamera = normalize(gCameraPos - worldPosition);
		
		// 计算视线反方向和反射光方向的夹角
		float  VdotR = saturate(dot(directionToCamera,reflectionVector));
		
		// 计算镜面高光颜色
		specular = lightColor * specularColor *pow(VdotR,specularPower);
		specular = saturate(specular);
	}	
	return specular;
}

//-----------------------------------------------------------------------------------------------
//	函数:	BlinnPhongSpecular
//	
//	作者:	E. Riba (Riki) 12.08.2009
//
//	描述:	基于Bilnn Phong公式的镜面高光公式
//				
//-----------------------------------------------------------------------------------------------
float3 BlinnPhongSpecular( in float3 directionToLight, in float3 worldNormal, in float3 worldPosition, in float3 lightColor, in float3 specularColor, in float3 specularPower)
{    
	float3 specular = 0;
	if(length(specularColor)>0)
	{
		//计算像素指向相机的向量,即视线反方向
		float3 viewer = normalize(gCameraPos - worldPosition);
		
		// 计算入射光反方向和视线反方向之间的角平分线方向
		float3 half_vector = normalize(directionToLight + viewer);
	 
		// 计算角平分线与法线方向的夹角
		float  NdotH = saturate(dot( worldNormal, half_vector));
	 
		// 计算镜面高光颜色
		specular = lightColor * specularColor * pow( NdotH, specularPower) ;
		specular = saturate(specular);
    }   
    return specular;
}
//////////////////////////////////////////////////////////////
// 这个函数计算一个像素上的一个光源的effect                                //
// 它要考虑镜面高光颜色、漫反射颜色还有材质属性             //
// 这个函数计算Lambertian漫反射光照和Phong镜面高光	     //
// 并返回它们的和。                                         //
//////////////////////////////////////////////////////////////
float3 CalculateSingleLight(Light light, float3 worldPosition, float3 worldNormal, float3 diffuseColor,float3 specularColor,float specularPower)
{
	//获取当前光源的颜色、光源类型
	float3 lightColor = light.color;
	float  lightType = light.lightType;
	//将衰减值初始化为0
	float  attenuation = 0;
	//顶点指向光源的方向,即入射光线反方向
	float3 directionToLight ;
	
	//单向光源的衰减设为1即不衰减
	if(lightType == DIR_LIGHT)
	{
		directionToLight = -normalize(light.direction);
        attenuation = 1;
	}
	//如果不是单向光源,则获取光源的range,falloff,theta,phi
	else
	{
		float range =light.spotData.x;       // 光源作用距离
		float falloff = light.spotData.y;    // 聚光灯内外光锥之间的衰减或点光源距离衰减
		float cosTheta	= cos(light.spotData.z);	  // 内光锥弧度的余弦值
		float cosPhi = cos(light.spotData.w);	      // 外光锥弧度的余弦值
		
		//计算光源与当前顶点的距离
		float lightDist	= distance(light.position, worldPosition);
		
		directionToLight    = normalize(light.position - worldPosition);
		
		//如果是点光源且顶点位于光源作用范围之内,则计算距离衰减值attenuation
		if( (lightType == POINT_LIGHT) && (range > lightDist) )
		{
			attenuation = Attenuation(lightDist, range, falloff); 
		}
		
		//如果是聚光灯光源,则首先判断顶点是否在光照范围之内
		else if( lightType == SPOT_LIGHT)
		{
			//如果在光照范围之内
			if(lightDist < range)
			{
				//首先计算距离衰减
				float fOuterAtten = Attenuation(lightDist, range, falloff);
				
				// 计算当前光源指向顶点方向和聚光灯光线方向的夹角Alpha的余弦值
				float cosAlpha = saturate(dot(-directionToLight, normalize(light.direction)));
				
				// 计算聚光灯内外光锥间的衰减因子
				float fSpotAtten = 0.0f;
				//如果夹角Alpha小于内光锥顶角,即cosAlpha大于cosTheta,则不衰减,衰减因子为1
				if( cosAlpha > cosTheta)
					fSpotAtten = 1.0f;
				//如果夹角Alpha大于内光锥顶角小于外光锥顶角,则需要根据falloff指数计算衰减因子
				else if( cosAlpha > cosPhi)
					fSpotAtten = pow((cosAlpha - cosPhi)/(cosTheta - cosPhi), falloff);
				//将距离衰减因子乘以光锥间衰减因子得到最终的衰减值						
				attenuation = fOuterAtten * fSpotAtten;
			}
			else
			{
				attenuation = 0;
			}
		}
	}
	//计算漫反射光照分量
	float3 Lc = LambertianComponent(directionToLight, worldNormal, lightColor);
	float3 Sc = SpecularComponent(directionToLight, worldNormal, worldPosition, lightColor, specularColor, specularPower);
	//使用BlinnPhone光照公式计算镜面高光分量
	//float3 Sc = BlinnPhongSpecular(directionToLight, worldNormal, worldPosition, lightColor, specularColor, specularPower);

	return (Lc + Sc) * (attenuation * diffuseColor.rgb);  
}

关于include文件

include文件虽然是Game Studio项目文件的一部分,但它不是content项目的一部分。因为它被effect引用,所以会自动被生成,所以无需将它复制到content项目中。我将它们包含在content项目中是为了可以容易地找到这个文件进行编辑,但是在属性面板中要将“生成操作”设置为“无”,不然通不过shader的编译。include文件的后缀其实可以取任何名称,有时我将后缀改成.fx,这样我的shader高亮插件NShader(http://nshader.codeplex.com/)可以发挥作用,让调试更容易。

关于输入和输出结构

如果你在Visual Studio中添加一个Effect文件,它默认会有以下两个顶点着色器输入和顶点着色器输出结构:

struct VertexShaderInput
{
    float4 Position : POSITION0;

    // TODO: add input channels such as texture
    // coordinates and vertex colors here.
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;

    // TODO: add vertex shader outputs such as colors and texture
    // coordinates here. These values will automatically be interpolated
    // over the triangle, and provided as input to your pixel shader.
};

这不是必须的,例如:如果你使用VertexShaderInput,则代码可以写成:

VertexShaderOutput VertexShaderFunction(VertexShaderInput input) 
{
    ......
}

如果不用VertexShaderInput,也可以写成:

VertexShaderOutput VertexShaderFunction(float3 position : POSITION0)
{
    ......
}

在我的effect文件中使用了诸如TexturedLightsVSIn、TexturedLightsVSOut等的结构,虽然对于简单的shader来说这样做有点啰嗦,但为了代码可读性,建议使用结构。

关于uniform extern

你能看到许多参数之前都有uniform extern的前缀,有些参数之前还有shared,它们的意义可见下表:

前缀 描述
static static用于全局变量,表示值为一个内部变量,仅能被当前shader中的代码访问。用于局部变量则表示这个值将在多次调用间驻留。静态变量只能进行一次初始化操作,如果没有使用特定值进行初始化,则默认为0。
uniform 使用uniform声明的全局变量表示对整个shader来说,它是一个统一的输入值,即不能在shader运行期间改变。所有非静态全局变量都被认为是统一的。
extern 使用extern声明的全局变量表示对shader来说,它是一个外部输入的值。所有非静态全局变量都被认为是外部的。
volatile 这个关键字提示编译器变量将频繁改变。
shared 这个关键字用来告诉编译器这个值将被多个effect共享。
const 声明为const的变量表示在初始化之后,就不能改变它的值。

表中说到非静态全局变量默认是uniform和extern的,所以你不在参数前加uniform和extern也可以,我加上的目的是为了看起来清楚点。

关于effect参数的设置

我学习Shader系列4:材质和光源最大的收获是知道了原来无需每帧都设置每个effect参数,以前看到的所有代码都是在Draw方法中对每个参数进行设置,但是很多参数是不会频繁改变的,这样做会拖慢程序。图像设备就像一个状态机,每个状态都有一个初始值,状态设置后就不会发生改变,直到设置一个新的状态。

因此我是这样设置effect参数的:

Generic.fx共有23个参数,其中世界矩阵gWorld,视矩阵gView,相机位置gCameraPos变动频繁,所以在材质类的SetEffectParameters方法中调用,每帧都要设置。

共享参数投影矩阵gProjection,时间gTime在SceneManager类中的Draw方法中调用,每帧都要设置。

共享参数环境光颜色gAmbient,雾化颜色gFogColor,雾化开始距离gFogStart,雾化结束距离gFogEnd 在Scene类的相应属性中设置,光源总数gTotalLights在Scene类的AddLight和RemoveLightAt方法中设置。

共享的光源数组gLights的各个变量在Light类中设置。

有关材质的自发光颜色gMaterialEmissive,漫反射颜色gMaterialDiffuse,镜面反射颜色gMaterialSpecular,镜面反射强度gMaterialSpecPower,开启漫反射颜色gDiffuseEnabled,漫反射纹理gDiffuseTexture,漫反射纹理平铺次数gDiffuseUVTile,细节纹理gDetailTexture,细节纹理平铺次数gDetailUVTile,是否开启雾化gFogEnabled在材质类GenericMaterial中设置。

还有两个参数漫反射纹理采样器textureSampler,细节纹理采样器detailSampler无需设置。

Generic.fx代码的具体解释请见下一个教程。

文件下载(已下载 1466 次)

发布时间:2010/4/21 下午2:07:52  阅读次数:6748

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号