一直想学习Shader,却总是断断续续的,触及不到精髓部分。于是,决定从最基础的开始学习,做好笔记,尽快成为Shader达人.....既然选择了学习,就要风雨兼程,一路向前~
第一篇之环境光:
环境光是没有方向的,在一个空间中所有模型照射到的环境光的颜色都是相同的,比如在一个漆黑的房间里什么也看不到,环境光通常为0,而在一个明亮的房间里,就能够看到物体,此时让物体可以进入你视觉的即是因为环境光不为0。
环境光除了颜色以外,通常还有个用来表示光照的强弱的强度参数
环境光的公式是: I = Aintensity* Acolor
其中I是光的实际颜色,Aintensity是光的强度(通常在0.0和1.0之间),Acolor环境光的颜色,这个颜色可以是硬编码的值,参数或纹理。
接下来用XNA4.0+VS2010来实现一个简单的程序,并贴出其效果图。
第一步:新建fx文件(Ambient.fx)
首先需要在项目的Content目录下建立这个文件,在自动生成好的fx文件中,已经帮我们定义好了基本的模板,包括顶点的输入结构(VertexShaderInput)和顶点输出结构(VertexShaderOutput),同时定义好了顶点着色方法(VertexShaderFunction)以及像素着色方法(PixelShaderFunction)。除此之外还有三个4*4的矩阵,分别是world,view,project。
Vertex shader:顶点渲染,处理从顶点缓冲区加载的模型顶点信息传递到shader中,顶点结构中通常可以包括:位置、颜色、法线、切线等信息,由于环境光只需要对位置进行处理,所以这里我们只定义了一个位置信息。
struct VertexShaderInput { float4 Position : POSITION0; }; struct VertexShaderOutput { float4 Position : POSITION0; };
在shader中,数据之间的传值借助寄存器,比如上面代码中出现得POSITION0,表示位置信息会放在名称叫POSITION这个寄存器里面。
接着,则是处理获取到的模型位置并进行转换 (将默认的VertexShaderFunction改成了VertexShaderAmbient)
VertexShaderOutput VertexShaderAmbient(VertexShaderInput input) { VertexShaderOutput output; float4 worldPosition = mul(input.Position, World); float4 viewPosition = mul(worldPosition, View); output.Position = mul(viewPosition, Projection); return output; }
通过三个矩阵的转换,模型的顶点位置已经正确的转换到当前空间了,下一步则是像素着色,添加光照信息。
Pixel Shader:逐像素渲染,对给定的模型、对象、顶点额所有像素进行处理,将Vertex Shader的输出值作为它的输入值
float4 PixelShaderAmbient(VertexShaderOutput input) : COLOR0 { float aIntensity= 0.4f; //颜色强度 float4 aColor= {1,0,0,1}; return aColor*aIntensity; }
同样,对于返回值,我们保存在COLOR寄存器里面。
最后,我们必须定义technique并将pixel shader和vertex shader函数绑定到technique上
technique AmbientLight { pass Pass0 { VertexShader = compile vs_2_0 VertexShaderAmbient(); PixelShader = compile ps_2_0 PixelShaderAmbient(); } }
到此,一个简单的shader就编写完成了,那么下面我们看看在XNA程序中如何调用。
第一:声明一个Effect对象
/// <summary> /// effect for shader /// </summary> private Effect effect;
第二:加载fx文件
effect = Content.Load<Effect>("Ambient");
第三:利用effect渲染模型
effect.CurrentTechnique = effect.Techniques["AmbientLight"]; //遍历所有的pass foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Apply(); foreach (ModelMesh mesh in model.Meshes) { Matrix worldMatrix = modelTransform[mesh.ParentBone.Index] * Matrix.CreateScale(30.0f) * Matrix.CreateRotationY(angle) * Matrix.CreateRotationX(angle); foreach (ModelMeshPart part in mesh.MeshParts) { effect.Parameters["World"].SetValue(worldMatrix); effect.Parameters["View"].SetValue(viewMatrix); effect.Parameters["Projection"].SetValue(projectMatrix); VertexBuffer vb = part.VertexBuffer; graphics.GraphicsDevice.SetVertexBuffer(part.VertexBuffer); graphics.GraphicsDevice.Indices = part.IndexBuffer; graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList , part.VertexOffset , 0 , part.NumVertices , part.StartIndex , part.PrimitiveCount); } } }
这样就可以达到上面截图的效果,注:一个Shader可以有一个或一个以上的technique。每个technique都有一个唯一的名称,我们可以通过设置Effect类中的CurrentTechnique属性选择使用哪个technique。同时,一个technique可以有一个或多个passes,但请确保处理所有passes以获得我们希望的结果。