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 阅读次数:6804