Shader编程教程
2010-05-13 11:37:14| 分类: DirectX 3D学习|举报|字号 订阅
您好,欢迎来到XNA Shader教程1。我的名字叫Petri Wilhelmsen,是Dark Codex Studios的成员。我们经常会参加各种图形/游戏开发的竞赛,如Gathering,Assembly,Solskogen,Dream-Build-Play和NGA等。
本XNA Shaders编程教程将讨论XNA的不同方面的知识以及如何使用XNA和GPU编写HLSL。我将从一些基本理论开始,然后深入到shader编程的实际方法。
理论部分不会面面俱到,但足以让你开始使用shader并自己实践。它将涵盖HLSL的基础,HLSL语言如何工作和一些必须知道的关键字。
今天,我将介绍XNA和HLSL以及一个简单的环境光照算法。
先决条件
XNA的编程基础,因为我们将涉及到加载纹理,三维模型,矩阵和一些数学知识。
Shader简史
在DirectX8之前,GPU使用固定方式变换像素和顶点,即所谓的“固定管道”。这使得开发者不可能改变像素和顶点转化和处理的进程,使大多数游戏的图像表现看起来非常相似。
DirectX8提出了顶点和像素着色器,这让开发者可以在管道中决定如何处理顶点和像素,使他们获得了很强的灵活性。
一开始shader编程使用汇编语言程序使用的着色器,这对shader开发者来说相当困难,Shader Model 1.0是唯一支持的版本。但DirectX9发布后这一切改变了,开发者能够使用高级着色语言(HLSL)取代了汇编语言,HLSL语法类似C语言,这使shader更容易编写,阅读和学习。
DirectX 10.0提出了一个新的shader——Geometry Shader作为Shader Model 4.0的组成部分。但这需要一个最先进的显卡和Windows Vista才能支持。
XNA支持Shader Model 1.0至3.0,可以在XP,Vista和XBox360!上运行。
Shader?
嗯,历史已经说得够多了……那么什么是shader?
正如我所说的,shader可以用来定制管道的步骤,使开发者能够决定如何处理像素/顶点。
如下图所示,应用程序在渲染时启动并使用shader,顶点缓冲区通过向pixel shader发送所需的顶点数据与pixel shader协同工作,并在帧缓冲中创建了一个图像。
但请注意许多GPU不支持所有的shader模式,在开发shader时应引起足够重视。一个shader最好要有一个类似/简单的效果,使程序在较旧的计算机上也能工作正常。
Vertex shader
Vertex shaders用来逐顶点地处理顶点数据。例如可以通过将模型中的每个顶点沿着法线方向移动到一个新位置使一个模型变“胖”(这称之为deform shaders)。
Vertex shaders从应用程序代码中定义的一个顶点结构获取数据,并从顶点缓冲区加载这个结构传递到shader。这个结构描述了每个顶点的属性:位置,颜色,法线,切线等。
接着Vertex shader将输出传递到pixel shader。可以通过在shader中定义一个结构包含你想要存储的数据,并让Vertex shader返回这个实例来决定传递什么数据,或通过在shader中定义参数,使用out关键字来实现。输出可以是位置,雾化,颜色,纹理坐标,切线,光线位置等。
struct VS_OUTPUT { float4 Pos: POSITION; }; VS_OUTPUT VS( float4 Pos: POSITION ) { VS_OUTPUT Out = (VS_OUTPUT) 0; ... return Out; } // or float3 VS(out float2 tex : TEXCOORD0) : POSITION { tex = float2(1.0, 1.0); return float3(0.0, 1.0, 0.0); }
Pixel Shader
Pixel Shader对给定的模型/对象/一组顶点处理所有像素(逐像素)。这可能是一个金属盒,我们要自定义照明的算法,色彩等等。Pixel Shader从vertex shaders的输出值获取数据,包括位置,法线和纹理坐标:
float4 PS(float vPos : VPOS, float2 tex : TEXCOORD0) : COLOR { ... return float4(1.0f, 0.3f, 0.7f, 1.0f); }
pixel shader可以有两个输出值:颜色和深度。
HLSL
HLSL是用来开发shader的。在HLSL中,您可以声明变量,函数,数据类型,测试(if/else/for/do/while+)以及更多功能以建立一个顶点和像素的处理逻辑。下面是一些HLSL的关键字。这不是全部,但是最重要的。
数据类型:
- bool true or false
- int 32-bit integer
- half 16bit integer
- float 32bit float
- double 64bit double
向量:
- float3 vectorTest
- float vectorTest[3]
- vector vectorTest
- float2 vectorTest
- bool3 vectorTest
矩阵:
- float3x3: 3x3矩阵,float类型
- float2x2: 2x2矩阵, float类型
还有很多辅助函数处理复杂的数学表达式:
- cos( x ) 返回x的余弦值
- sin( x) 返回x的正弦值
- cross( a, b ) 返回向量a和向量b的叉乘
- dot( a,b ) 返回向量a和向量b的点乘
- normalize( v ) 返回一个归一化的向量v(v / |v|)
完整列表请看:http://msdn2.microsoft.com/en-us/library/bb509611.aspx (译者:推荐看clayman的博客中的The Complete Effect and HLSL Guide连载)
HSLS提供了大量的函数让你使用!它们能帮助你解决不同的问题。
Effect文件
Effect文件(.fx)让开发shader变得更容易,你可以在.fx文件中存储几乎所有关于着色的东西,包括全局变量,函数,结构,vertex shader,pixel shader,不同的techniques/passes,纹理等等。
我们前面已经讨论了在shader中声明变量和结构,但什么是technique/passes?这很简单。一个Shader可以有一个或一个以上的technique。每个technique都有一个唯一的名称,我们可以通过设置Effect类中的CurrentTechnique属性选择使用哪个technique。
effect.CurrentTechnique = effect.Techniques["AmbientLight"];
在这里,我们设置“effect”使用technique“AmbientLight”。一个technique可以有一个或多个passes,但请确保处理所有passes以获得我们希望的结果。
这个例子包含一个technique和一个pass:
technique Shader { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_1_1 PS(); } }
这个例子包含一个technique和两个pass:
technique Shader { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_1_1 PS(); } pass P1 { VertexShader = compile vs_1_1 VS_Other(); PixelShader = compile ps_1_1 PS_Other(); } }
这个例子包含二个technique和一个pass:
technique Shader_11 { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_1_1 PS(); } } technique Shader_2a { pass P0 { VertexShader = compile vs_1_1 VS2(); PixelShader = compile ps_2_a PS2(); } }
我们可以看到,一个technique有两个函数,一个是pixel shader,另一个是vertex shader。
VertexShader = compile vs_1_1 VS2(); PixelShader = compile ps_1_1 PS2();
这告诉我们,这个technique将使用VS2()作为vertex shader,PS2()作为pixel shader,并且支持Shader Model 1.1或更高版本。这就让GPU支持更高版本的shader变得可能。在XNA中实现Shader 在XNA中实现Shader很简单。事实上,只需几行代码就可以加载和使用shader。下面是步骤:
1. 编写shader
2. 把shader文件(.fx)导入到“Contents”
3. 创建一个Effect类的实例
4. 初始化Effect类的实例。
5. 选择使用的technique
6. 开始shader
7. 传递不同的参数至shader
8. 绘制场景
9. 结束shader
更详细的步骤:
1.记事本和Visual Studio等都可以用来编写shader。也有一些shader的IDE可用,我个人喜欢使用nVidias的FX Composer:http://developer.nvidia.com/object/fx_composer_home.html 。(译者:还推荐一个shader的IDE:AMD公司的RenderMonkey,可在http://ati.amd.com/developer/rendermonkey/downloads.html下载最新版本1.81(93.9MB,2008年4月8日),个人用下来的感觉好像nvidia实力更强一些,文档也很详实,而RenderMonkey上手更容易。)
2.当shader建立后,将其拖放到“Content”目录,自动生成素材名称。
3.XNA框架有一个Effect类用于加载和编译shader。要创建这个类的实例可用以下代码:
Effect effect;
Effext属于Microsoft.Xna.Framework.Graphics类库,因此,记得添加using语句块:
using Microsoft.Xna.Framework.Graphics
4.要初始化shader,我们可以使用Content从项目或文件中加载:
effect = Content.Load("Shader");
“Shader”是你添加到Content目录的shader名称。
5.选择使用何种technique:
effect.CurrentTechnique = effect.Techniques ["AmbientLight" ];
6.要使用Effect,请调用Begin()函数:
effect.Begin();
此外,您必须启动所有passes。
foreach (EffectPass pass in effect.CurrentTechnique.Passes) { // Begin current pass pass.Begin();
7.有很多方法可以设置shader的参数,但对这个教程来说下列方法够用了。注:这不是最快的方法,在以后的教程中我将回到这里:
effect.Parameters["matWorldViewProj"].SetValue(worldMatrix * viewMatrix*projMatrix);
其中“matWorldViewProj”是在shader中定义的:
float4x4 matWorldViewProj;
将matWorldViewProj设置为worldMatrix * viewMatrix * projMatrix。
SetValue设置参数并将它传递到shader,GetValue 从shader获取值,Type是获取的数据类型。例如,GetValueInt32()得到一个整数。
8.渲染你想要这个shader处理/转换的场景/对象。
9.要停止pass,调用pass.End(),要停止shader,调用Effect的End()方法:
pass.End(); effect.End();
为了更好地理解步骤,可参见源代码。
环境光照(Ambient light)
OK,我们终于到了最后一步,实现shader!不坏吧?首先,什么是“Ambient light” ? 环境光是场景中的基本光源。如果你进入一个漆黑的屋子,环境光通常是零,但走到外面时,总是有光能让你看到。环境光没有方向(译者:所以也将其称为“全局光照模型”),在这里应确保对象不会自己发光,它有一个基本的颜色。环境光的公式是:
I = Aintensity* Acolor
其中I是光的实际颜色, Aintensity是光的强度(通常在0.0和1.0之间),Acolor环境光的颜色,这个颜色可以是固定值,参数或纹理。好吧,现在开始实现Shader。首先,我们需要一个矩阵表示世界矩阵:
float4x4 matWorldViewProj ;
在shader顶端声明这个矩阵(作为全局变量)然后,我们需要知道vertex shader向pixel shader传递了哪些值。这可以通过建立一个结构(可以命名为任何值)实现:
struct OUT { float4 Pos: POSITION; };
我们创建了一个名为OUT的结构,其中包含一个float4类型的名叫Pos的变量。“:”后面的POSITION告诉GPU在哪个寄存器(register)放置这个值?嗯,什么是寄存器?寄存器是GPU中保存数据的一个容器。GPU使用不同的寄存器保存位置,法线,纹理坐标等数据,当定义一个shader传递到pixel shader的变量时,我们必须决定在GPU的何处保存这个值。 看一下vertex shader:
OUT VertexShader( float4 Pos: POSITION ) { OUT Out = (OUT) 0; Out.Pos = mul(Pos, matWorldViewProj); return Out; }
我们创建了一个OUT类型的函数,它的参数是float4类型的Pos:POSITION。这是模型文件/应用程序/游戏中定义的顶点位置。然后,我们建立一个名叫OUT的OUT结构实例。这个结构必须被填充并从函数返回,以便后继过程处理。输入参数中的Pos不参与后继过程的处理,但需要乘以worldviewprojection矩阵使之以正确放置在屏幕上。由于Pos是OUT中的唯一变量,我们已经返回它并继续前进。现在开始处理pixel shaders,我们声明为一个float4类型的函数,返回存储在GPU中的COLOR寄存器上的float4值。我们在pixel shader中进行环境光的算法:
float4 PixelShader() : COLOR { float Ai = 0.8f; float4 Ac = float4(0.075, 0.075, 0.2, 1.0); return Ai * Ac; }
这里我们使用上面的公式计算目前像素的颜色。Ai是环境光强度,Ac是环境光颜色。最后,我们必须定义technique并将pixel shader和vertex shader函数绑定到technique上:
technique AmbientLight { pass P0 { VertexShader = compile vs_1_1 VertexShader(); PixelShader = compile ps_1_1 PixelShader(); } }
好了,完成了!现在,我建议你看看源代码,并调整各个参数更好地理解如何使用XNA实现shader。
Shader编程教程的更多相关文章
- L老师 Shader编程教程 学习
Shader "VoidGame/FixedShader" { Properties{ //颜色 _Color("Color",Color)=(1,1,1,1) ...
- 【译】Unity3D Shader 新手教程(2/6) —— 积雪Shader
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你是一个shader编程的新手,并且你想学到下面这些酷炫的技术,我觉得你可以看看这篇教程: 实现一个积雪效果的 ...
- 【译】Unity3D Shader 新手教程(1/6)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 刚开始接触Unity3D Shader编程时,你会发现有关shader的文档相当散,这也造成初学者对Unity3D ...
- 【浅墨Unity3D Shader编程】之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 【浅墨Unity3D Shader编程】之二 雪山飞狐篇:Unity的基本Shader框架写法&颜色、光照与材质
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40955607 作者:毛星云(浅墨) ...
- 【浅墨Unity3D Shader编程】之中的一个 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 超全面的.NET GDI+图形图像编程教程
本篇主题内容是.NET GDI+图形图像编程系列的教程,不要被这个滚动条吓到,为了查找方便,我没有分开写,上面加了目录了,而且很多都是源码和图片~ (*^_^*) 本人也为了学习深刻,另一方面也是为了 ...
- 【译】Unity3D Shader 新手教程(3/6) —— 更加真实的积雪
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 如果你满足以下条件,我建议你阅读这篇教程: 你想知道如何在表面着色器中进行混色(blend colour) 你想实 ...
- Unity3d之Shader编程:子着色器、通道与标签的写法 & 纹理混合
一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器 ...
随机推荐
- 洛谷 P1739 表达式括号匹配【STL/stack/模拟】
题目描述 假设一个表达式有英文字母(小写).运算符(+,-,*,/)和左右小(圆)括号构成,以"@"作为表达式的结束符.请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则返 ...
- Buffer源码深入分析
博客园对MarkDown显示的层次感不是很好,大家可以看这里:Buffeer. 本机环境: Linux 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33 ...
- FZU-2216 The Longest Straight(尺取法)
Problem 2216 The Longest Straight Accept: 523 Submit: 1663Time Limit: 1000 mSec Memory Limit ...
- c# await 到底等待的是什么?
static void Main(string[] args) { Print(); Console.WriteLine("5 :::" + Thread.CurrentThrea ...
- POJ 2566:Bound Found(Two pointers)
[题目链接] http://poj.org/problem?id=2566 [题目大意] 给出一个序列,求一个子段和,使得其绝对值最接近给出值, 输出这个区间的左右端点和区间和. [题解] 因为原序列 ...
- 使用Windows Live Writer开始发布cnblogs日志的方法
1.下载Windows Live Writer http://www.microsoft.com/zh-cn/download/confirmation.aspx?id=8621 2.安装Window ...
- iOS 耳机线控
当你使用iphone的时候听音乐的时候,播放器在后台运行的时候,你仍然可以通过耳机来进行操作,完成曲目切换,快进,快退等功能!当然你的程序不一定是播放器应用,但是我们仍然可以让它具有这个功能,让用户通 ...
- 关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-切入点(pointcut)API
本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.N ...
- Visual Studio Package扩展——vsct文件简介
首先我们使用向导生成一个package的扩展,里面就会发现一个vsct文件.vsct文件的全称是Visual Studio Command Table,它其实就是一个xml文件,通过一定的规则来描述v ...
- DOM系统学习-基础
DOM介绍 DOM介绍: D 网页文档 O 对象,可以调用属性和方法 M 网页文档的树型结构 节点: DOM将树型结构理解为由节点组成. 节点种类: 元素节点.文本节点.属性节点等 查找元 ...