【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 10362|回复: 30

[转帖]着色器和效果

[复制链接]
发表于 2008-4-17 20:49:49 | 显示全部楼层 |阅读模式

http://blog.csdn.net/RaymondKing/

概览

到现在为止,我们已经从修改设备状态的配置(如:矩阵变换,光照,纹理,和渲染状态)得到了想要的效果。虽然这些多种多样的受支持的配置提供给了我们一些弹性,但是我们仍然受限于这些预定义的固定操作(因此被称作为“固定功能管线”)。

这部分的主题是顶点着色器和像素着色器,它们用我们实现的自定义程序替换固定功能管线的部件,这个自定义程序叫做“着色器”(shader)。着色器是完全可编程的,并且允许我们实现固定功能管线没有定义的技巧(technique)。这样的结果是,我们能够自己支配的技巧的数量大大的增加。渲染管线的可编程部件通常称为“可编程管线”(programmable pipeline)。各章的概要描述如下:

第一章:介绍高级着色语言——在本章中,我们将探索高级着色语言(High-Level Shading Language),它是我们用在顶点和像素着色器程序中的语言。

第二章:介绍顶点着色器——本章将解释什么是顶点着色器,和怎么在Direct3D中创建并使用它们。本章将会通过解释一个卡通风格着色技巧的实现来展示顶点着色器。

第三章:介绍像素着色器——本章将解释什么是像素着色器,和怎么在Direct3D中创建并使用它们。本章会以显示如何使用像素着色器实现多纹理(multitexturing)作为结束。

第四章:效果框架——在本章中,我们将讨论Direct3D的效果框架。本章将描述效果框架的用途,效果文件的结构和语法,还有如何创建效果文件和如何在Direct3D应用程序中使用效果文件。

[此贴子已经被作者于2008-4-19 9:46:43编辑过]
 楼主| 发表于 2008-4-17 21:06:48 | 显示全部楼层

1.4 关键字、语句和强制转换

阅读此文表明您已同意文末的声明

1.4.1 关键字

为便于参考,这里给出一个HLSL定义的关键字列表:

asm

bool

compile

const

decl

do

double

else

extern

false

float

for

half

if

in

inline

inout

int

matrix

out

pass

pixelshader

return

sampler

shared

static

string

struct

technique

texture

true

typedef

uniform

vector

vertexshader

void

volatile

while

下面的集合显示了被保留并且未使用但是将来可能成为关键字的标识符:

auto

break

case

catch

char

class

const_cast

continue

default

delete

dynamic cast

enum

explicit

friend

goto

long

mutable

namespace

new

operator

private

protected

public

register

reinterpret_cast

short

signed

sizeof

static_cast

switch

template

this

throw

try

typename

union

unsigned

using

virtual

可以了。

 楼主| 发表于 2008-4-17 21:07:06 | 显示全部楼层

1.4.2 基本程序流程

HLSL支持很多与C++相似的选择、重复、和一般程序流程语句。这些语句的语法和C++极为相似。

return语句:

return (expression);

ifif…else语句:

if( condition )

{

statement(s);

}

if( condition )

{

statement(s);

}

else

{

statement(s);

}

for语句:

for(initial; condition; increment)

{

statement(s);

}

while语句:

while( condition )

{

statement(s);

}

do…while语句:

do

{

statement(s);

}while( condition );

1.4.3 强制转换(casting

HLSL支持一种非常自由的强制转换设计。HLSL中强制转换的语法和C程序语言中的一样。例如要把float转换到matrix,我们写:

float f = 5.0f;

matrix m = (matrix)f;

从本书的例子中,你就能推断出这个转换语法的意思。但是,如果想要得到更详细的受支持的转换的信息,那么在DirectX SDK里,Content(内容)标签页下,看DirectX Graphics\Reference\Shader Reference\High Level Shading Language\Type

 楼主| 发表于 2008-4-17 21:07:56 | 显示全部楼层

1.5 操作符

阅读此文表明您已同意文末的声明

HLSL支持很多类似C++的操作符。除了很少一些底下注释的例外以外,他们的用法和C++里的完全一样。下表列出了HLSL的操作符:

[]

>

<

< =

> =

! =

= =

!

&&

?:

+

+ =

-

- =

*

*=

/

/=

%

%=

+ +

--

=

()

'

虽然操作符的行为和C++很相似,但是也有一些差异。第一,求模%运算符对整型和浮点型都起作用。为了使用求模操作符,左边的值和右边的值都必须有相同的正负号(如:左边和右边必须都是正或者负)。

第二,要注意HLSL操作是以每个分量为基础的。这是由于实际上向量和矩阵是语言内建的,并且这些类型是由若干个分量组成。通过将这些操作施加在分量级别之上,我们可以像使用数值类型一样完成诸如向量/矩阵的加法,减法和相等测试这些操作(),见下例:

注意:操作符的行为正如对数值操作一样(也就是说,按一般C++的方式)。

vector u = {1.0f, 0.0f, -3.0f, 1.0f};

vector v = {-4.0f, 2.0f, 1.0f, 0.0f};

// adds corresponding components

vector sum = u + v; // sum = (-3.0f, 2.0f, -2.0f, 1.0f)

增量一个向量就是增量其每个分量:

// before increment: sum = (-3.0f, 2.0f, -2.0f, 1.0f)

sum++; // after increment: sum = (-2.0f, 3.0f, -1.0f, 2.0f)

向量相乘也是按分量的:

vector u = {1.0f, 0.0f, -3.0f, 1.0f};

vector v = {-4.0f, 2.0f, 1.0f, 0.0f};

// multiply corresponding components

vector sum = u * v; // product = (-4.0f, 0.0f, -3.0f, 0.0f)

比较操作也是按分量进行的,并且返回一个每个分量都为bool类型的向量或者数组。作为结果的“bool”向量包含了每个分量比较的结果。例如:

vector u = { 1.0f, 0.0f, -3.0f, 1.0f};

vector v = {-4.0f, 0.0f, 1.0f, 1.0f};

vector b = (u == v); // b = (false, true, false, true)

最后,我们以讨论二元操作的变量提升promotion)作为结束:

n 对于二元操作,如果(操作符的)左边和右边维数不同,则维数较少的一边提升(强制转换)到具有和维数较大的一边相同的维数。例如,如果x的类型为float,而y的类型为float3,在表达式(x + y)中变量x被提升到float3,并且计算出来的表达式的值的类型也为float3。提升使用已定义的转换完成。注意,若转换未定义则提升也是未定义的。例如,我们不能转换float2float3,因为没有定义这个转换。

n 对于二元操作,如果左边和右边类型不同,那么较低精度的类型the lower type resolution)被提升(强制转换)到具有同类型的较高精度的类型the higher type resolution)。例如,如果x类型为inty类型为half,则表达式(x + y)中的变量x被提升到half,并且计算出来的表达式的值的类型也为half

 楼主| 发表于 2008-4-17 21:09:07 | 显示全部楼层

1.6 用户定义函数

阅读此文表明您已同意文末的声明

HLSL中的函数有下例属性:

n 函数使用类似C++的语法

n 参数总是按值传递

n 递归不被支持

n 函数总是inline

此外,函数还加上了一些用于其上的额外的关键字。例如,考虑一个写在HLSL中的下面这个函数:

bool foo(in const bool b, // input bool

out int r1, // output int

inout float r2) // input/output float

{

if( b ) // test input value

{

r1 = 5; // output a value through r1

}

else

{

r1 = 1; // output a value through r1

}

// since r2 is inout we can use it as an input

// value and also output a value through it

r2 = r2 * r2 * r2;

return true;

}

函数几乎和C++函数是一样的,除了inoutinout关键字:

n in——指定型参argument,特指传递给实参的变量)应该在函数开始前被拷贝给实参。传入参数不必强制指定,因为实参默认是in的。例如,下面两段是等价的:

float square(in float x)

{

return x * x;

}

也可以不强制指定in

float square(float x)

{

return x * x;

}

n out——指定实参应该在函数返回时被拷贝给型参。这样可以通过参数返回值。out关键字是必须的,因为HLSL不允许传递一个引用或一个指针。我们要注意:如果实参标记为out,在函数开始前,型参就不拷贝给实参。换句话说,out实参仅可以被用于输出数据——它不能用于输入。

void square(in float x, out float y)

{

y = x * x;

}

这里,我们输入了要被乘方的数x,并且通过参数y返回了x的乘方。

n inout——这是一个指示实参既用于输入又用于输出的快捷方法。如果要使用实参同时用作输入和输出,就指定inout

void square(inout float x)

{

x = x * x;

}

这里,我们输入了要被乘方的数x,同时又通过x返回了的x的乘方。

 楼主| 发表于 2008-4-17 21:10:38 | 显示全部楼层
1.7 内建函数

HLSL有一个丰富的内建函数的集合,它们对3D图形来说非常有用。下表是一个删减了的列表。在下两章中,我们会使用这些函数中的一些进行实践。而现在,熟悉它们就够了。

注意:要得到更多的参考,可以参看DirectX文档中内建HLSL函数的完整列表,在Content页下,然后到DirectX Graphics\Reference\Shader Reference\High Level Shader Language\Intrinsic Functions

译者注:以下表格中,//<variable>//表示变量variable的模(例如向量的绝对值)。

函数

描述

abs(x)

返回 |x|.

ceil(x)

返回 x 的最小整数.

clamp(x, a, b)

截取x [a, b] 范围内并返回截取后的结果.

cos(x)

返回x的余弦,其中x单位为弧度.

cross(u, v)

返回 u ?v(叉积).

degrees(x)

转换 x 从弧度到角度.

determinant(M)

返回矩阵M的行列式det(M).

distance(u, v)

返回u点和v点之间的距离||v - u||.

dot(u, v)

返回 u . v(点积).

floor(x)

返回 x 的最大整数.

length(v)

返回 ||v||.

lerp(u, v, t)

uv之间线性插值,根据参数 t ? [0, 1 ].

log(x)

返回 ln(x).

log10(x)

返回 log10(x).

log2(x)

返回 log2(x).

max(x, y)

如果x ≥ y,则返回 x;否则返回 y.

min(x, y)

如果 x ≤ y,返回x;否则返回 y.

mul(M, N)

返回矩阵乘积 MN. 注意:矩阵乘积必须是已定义的. 如果M是一个向量,它被作为一个行向量,则向量-矩阵vector-matrix)乘法是已定义的。类似的,如果N 是一个向量,他被作为一个列向量,则矩阵-向量matrix-vector)乘法是已定义的.

 楼主| 发表于 2008-4-17 21:10:57 | 显示全部楼层

normalize(v)

返回 v/v.

pow(b, n)

返回 bn.

radians(x)

转换 x 角度 弧度.

reflect(v, n)

给定向量v和表面法线n,计算其反射向量.

refract(v,n, eta)

给定向量v、表面法线n和两种材质的两个索引的比率eta,计算其折射向量. 翻看一下物理书中Snell的规则或者在互联网上搜索一下关于refraction(反射)的信息.

rsqrt(x)

返回x的平方根的倒数.

saturate(x)

返回clamp(x, 0.0, 1.0).

sin(x)

返回x的正弦,其中x单位为弧度.

sincos(in x, out s, out c)

返回x的正弦和余弦,其中x单位为弧度.

sqrt(x)

返回x的平方根.

tan(x)

返回x的正切,其中 x 单位为弧度.

transpose(M)

返回MT的转置.

大多数函数已经重载以使其可以对所有内建类型有意义。例如,abs对所有数值类型有意义,所以它为所有这些数值类型进行了重载。又例如,叉积的叉乘仅对3D向量有意义,所以它对所有类型的3D向量(比如:intfloatdouble3D向量)进行了重载。另一方面,线性插值——lerp,对于数值、2D3D4D向量有意义,因此重载了这些类型。

 楼主| 发表于 2008-4-17 21:11:11 | 显示全部楼层

注意:如果你传递进去一个非数值类型到一个(要求)数值类型的函数,也就是一个仅能对数值类型进行操作的函数(比如:cos(x)),那么这个函数会对传进去的每个分量进行操作。例如,你写:

floats v = float3 (0.0f, 0.0f, 0.0f);

v = cos(v);

那么函数将会对每个分量进行操作:v=(cos(x),cos(y),cos(z))

下例展示了这些固有的函数可能被调用的方式:

float x = sin(1.0f); // sine of 1.0f radian.

float y = sqrt(4.0f); // square root of 4.

vector u = {1.0f, 2.0f, -3.0f, 0.0f};

vector v = {3.0f, -1.0f, 0.0f, 2.0f};

float s = dot(u, v); // compute dot product of u and v.

float3 i = {1.0f, 0.0f, 0.0f};

float3 j = {0.0f, 1.0f, 0.0f};

float3 k = cross(i, j); // compute cross product of i and j.

matrix<float, 2, 2> M = {1.0f, 2.0f, 3.0f, 4.0f};

matrix<float, 2, 2> T = transpose(M); // compute transpose

 楼主| 发表于 2008-4-17 21:11:39 | 显示全部楼层

1.8 小结

n 我们在ASCII文本文件中编写了HLSL程序,并且在我们的应用程序中使用D3DXCompileShaderFromFile函数编译了它们。

n ID3DXConstantTable接口允许我们在应用程序中对着色器程序中的变量进行设置。这种通信是必须的,因为被着色器使用的变量可以按一帧一帧的变化而改变。例如,如果应用程序中的视图矩阵发生了改变,我们需要使用新的视图矩阵更新着色器的视图矩阵变量。我们可以用ID3DXConstantTable完成这种更新。

n 对于每个着色器,我们必须定义一个输入和一个输出结构,这些结构分别描述了着色器中输入和输出数据的格式。

n 每个着色器有一个入口点函数,它有一个输入结构参数用于传递数据进着色器。此外,每个着色器返回一个输出结构的实例,它用于从着色器输出数据。

 楼主| 发表于 2008-4-17 21:12:04 | 显示全部楼层

第二章 介绍顶点着色器

阅读此文表明您已同意文末的声明

[译者序]经过上一章对HLSL的知识梳理,现在的你应该只有两种状态,第一种是已经焦头烂额没有兴趣再读下去了;另一种是觉得前面说的太容易了,读起来索然无味。如果你是第一种情况,那么建议你先补习一下计算机图形学和C++的知识,然后再来学习;如果你是第二种情况,那就对了!我们将从现在开始学习这最激动人心的三章。这三张可以说是涵盖了实用的Direct3D最新图形技术的基础,准备好了吗?LET’S GO

概览

顶点着色器vertex shader)是一个在图形卡的GPU上执行的程序,它替换了固定功能管线fixed function pipeline)中的变换transformation)和光照lighting)阶段。(这不是百分之百的正确,因为顶点着色器可以被Direct3D运行时Direct3D runtime)以软件模拟,如果硬件不支持顶点着色器的话)。图2.1说明了管线中顶点着色器替换的部件。


2.1:顶点着色器替换固定功能管线的光照和变形阶段

从图2.1,我们知道,顶点以局部坐标输入到顶点着色器,并且顶点着色器必须输出点亮的(着色的)顶点进齐次剪裁空间。(为了保持简单的原因,本书中我们没有深入研究投影变换的细节。但是投影矩阵变换顶点到的空间称作齐次剪裁空间homogeneous clip space)。因此,要把一个顶点从局部空间变换到齐次坐标空间,我们必须应用下列变换序列:世界变换world transformation),视图变换view transformation)和投影变换projection transformation),它们分别由世界矩阵,视图矩阵和投影矩阵来完成。)对于点元point primitive),顶点着色器也被用于操作每个顶点的顶点大小。

由于顶点着色器是我们(在HLSL中)写的一个自定义程序,因此我们在图形效果方面获得了我们能够达到的极大的自由性。我们不再受限于Direct3D的固定光照算法。此外,应用程序操纵顶点位置的能力也有了多样性,例如:cloth simulation,粒子系统的点大小操纵,还有顶点混合/morphing。此外,我们的顶点数据结构更自由了,并且可以在可编程管线中包含比在固定功能管线中多的多的数据。

顶点着色器仍然是相对新的特性,并且许多图形卡不支持它们,特别是随DirectX 9发布的较新版本的顶点着色器。通过检查D3DCAPS9结构的VertexShaderVersion成员,可以测试顶点着色器的版本。下列代码段展示了这一点:

// If the device's supported version is less than version 2.0

if( caps.VertexShaderVersion < D3DVS VERSION(2, 0) )

// Then vertex shader version 2.0 is not supported on this

// device.

我们看到D3D_VERSION的两个参数分别接收主和次版本号。现在,D3DXCompileShaderFromFile函数支持顶点着色器版本1.12.0

目标

n 学习如何在可编程管线中定义顶点结构的分量

n 了解顶点分量的不同用法

n 学习如何创建、设置和销毁一个顶点着色器

n 学习如何使用顶点着色器实现卡通动画渲染效果


 楼主| 发表于 2008-4-17 21:12:29 | 显示全部楼层

2.1 顶点声明

阅读此文表明您已同意文末的声明

到现在为止,我们已经使用自由顶点格式(flexible vertex formatFVF)来描述顶点结构中的各分量。但是,在可编程管线中,我们的顶点数据可以包含比用FVF所能表达的多的多的数据。因此,我们通常使用更具表达性的并且更强有力的顶点声明vertex declaration)。

注意:我们仍然可以在可编程管线中使用FVF——如果我们的顶点格式可以这样描述。不管怎样,这只是为了方便,因为FVF会在内部被转换为一个顶点声明。

2.1.1 描述顶点声明

我们将一个顶点声明描述为一个D3DVERTEXELEMENT9结构的数组。D3DVERTEXELEMENT9数组中的每个元素描述了一个顶点的分量。所以,如果你的顶点结构有三个分量(例如:位置、法线、颜色),那么其相应的顶点声明将会被一个D3DVERTEXELEMENT9结构数组描述。这个D3DVERTEXELEMENT9结构定义如下:

typedef struct _D3DVERTEXELEMENT9 {

BYTE Stream;

BYTE Offset;

BYTE Type;

BYTE Method;

BYTE Usage;

BYTE UsageIndex;

} D3DVERTEXELEMENT9;

n Stream——指定关联到顶点分量的流

n Offset——偏移,按字节,相对于顶点结构成员的顶点分量的开始。例如,如果顶点结构是:

struct Vertex

{

D3DXVECTOR3 pos;

D3DXVECTOR3 normal;

};

……pos分量的偏移是0,因为它是第一个分量;normal分量的偏移是12,因为sizeof(pos) == 12。换句话说,normal分量以Vertex的第12个字节为开始。

n Type——指定数据类型。它可以是D3DDECLTYPE枚举类型的任意成员;完整列表请参见文档。常用类型如下:

l D3DDECLTYPE_FLOAT1——浮点数值

l D3DDECLTYPE_FLOAT2——2D浮点向量

l D3DDECLTYPE_FLOAT3——3D浮点向量

l D3DDECLTYPE_FLOAT4——4D浮点向量

l D3DDECLTYPE_D3DCOLOR—D3DCOLOR类型,它扩展为RGBA浮点颜色向量(r g b a),其每一分量都是归一化到区间[0, 1]了的。

n Method——指定网格化方法。我们认为这个参数是高级的,因此我们使用默认值,标识为D3DDECLMETHOD_DEFAULT.

n Usage——指定已计划的对顶点分量的使用。例如,它是否准备用于一个位置向量、法线向量、纹理坐标等?有效的用途标识符usage identifier)是D3DDECLUSAGE枚举类型的:

typedef enum _D3DDECLUSAGE {

D3DDECLUSAGE_POSITION = 0, // Position.

D3DDECLUSAGE_BLENDWEIGHTS = 1, // Blending weights.

D3DDECLUSAGE_BLENDINDICES = 2, // Blending indices.

D3DDECLUSAGE_NORMAL = 3, // Normal vector.

D3DDECLUSAGE_PSIZE = 4, // Vertex point size.

D3DDECLUSAGE_TEXCOORD = 5, // Texture coordinates.

D3DDECLUSAGE_TANGENT = 6, // Tangent vector.

D3DDECLUSAGE_BINORMAL = 7, // Binormal vector.

D3DDECLUSAGE_TESSFACTOR = 8, // Tessellation factor.

D3DDECLUSAGE_POSITIONT = 9, // Transformed position.

D3DDECLUSAGE_COLOR = 10, // Color.

D3DDECLUSAGE_FOG = 11, // Fog blend value.

D3DDECLUSAGE_DEPTH = 12, // Depth value.

D3DDECLUSAGE_SAMPLE = 13 // Sampler data.

} D3DDECLUSAGE;

 楼主| 发表于 2008-4-17 21:12:45 | 显示全部楼层

D3DDECLUSAGE_PSIZE类型用于指定一个顶点的点的大小。它用于点精灵,因此我们可以基于每个顶点控制其大小。一个D3DDECLUSAGE_POSITION成员的顶点声明意味着这个顶点已经被变换,它通知图形卡不要把这个顶点送到顶点处理阶段(变形和光照)。

注意:这些中的少数用途类型usage type)未在本书中提及,例如BLENDWEIGHTS, BLENDINDICES, TANGENT, BINORMAL, TESSFACTOR

n UsageIndex——用于标识多个相同用途的顶点分量。这个用途索引是位于区间[0, 15]间的一个整数。例如,假设我们有三个用途为D3DDECLUSAGE_NORMAL顶点分量。我们可以为第一个指定用途索引为0,为第二个指定用途索引为1,并且为第三个指定用途索引为2。按这种方式,我们可以通过其用途索引标识每个特定的法线。

顶点描述声明的例子:假设我们想要描述的顶点格式由位置向量和三个法线向量组成。顶点声明可以指定如下:

D3DVERTEXELEMENT9 decl[] =

{

{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

POSITION, 0},

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 0},

{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 1},

{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 2},

D3DDECL_END()

};

D3DDECL_END宏用于初始化D3DVERTEXELEMENT9数组的最后一个顶点元素。同样的,注意法线向量的用途索引标签。

2.1.2 创建顶点声明

一旦你描述了一个顶点声明为D3DVERTEXELEMENT9数组,我们就可以使用下面的方法获得一个IDirect3DVertexDeclaration9接口指针:

HRESULT IDirect3DDevice9::CreateVertexDeclaration(

CONST D3DVERTEXELEMENT9* pVertexElements,

IDirect3DVertexDeclaration9** ppDecl

);

n pVertexElements——D3DVERTEXELEMENT9结构数组,它描述我们想要创建的顶点声明。

n ppDecl——用于返回创建的IDirect3DVertexDeclaration9接口指针

例子调用,其中decl是一个D3DVERTEXELEMENT9数组:

IDirect3DVertexDeclaration9* _decl = 0;

hr = _device->CreateVertexDeclaration(decl, &_decl);

2.1.3 允许一个顶点声明

回忆一下:自由顶点格式是一个方便的特性并且在内部转换成了顶点声明。因此,当直接使用顶点声明,我们不再需要调用:

Device->SetFVF( fvf );

相反,我们调用:

Device->SetVertexDeclaration( _decl );

其中,_decl是一个IDirect3DVertexDeclaration9接口指针。

 楼主| 发表于 2008-4-17 21:13:12 | 显示全部楼层

2.2 顶点数据用途

虑这个顶点声明:

D3DVERTEXELEMENT9 decl[] =

{

{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

POSITION, 0},

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 0},

{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 1},

{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_

NORMAL, 2},

D3DDECL_END()

};

我们需要一种方式来定义一个顶点声明的元素到顶点着色器输入结构的数据成员的映射。我们在输入结构中通过指定每个数据成员的语义(: usage-type [usage-index])定义这个映射。语义通过元素的用途类型和用途索引标识顶点声明中的一个元素。由数据成员的语义标识的顶点元素是是得以映射到数据成员的元素。例如,对应于前面的顶点声明的输入结构是:

struct VS_INPUT

{

vector position : POSITION;

vector normal : NORMAL0;

vector faceNormal1 : NORMAL1;

vector faceNormal2 : NORMAL2;

};

注意:如果我们遗漏了用途索引,就意味着用途索引为零。例如,POSITIONPOSITION0是同一样东西。

这里decl中的元素0,由用途POSITION和用途索引0标识,它映射到positiondecl中的元素1,由用途NORMAL和用途索引0标识,它映射到normaldecl中的元素2,由NORMAL和用途索引1标识,它映射到faceNormal1decl中的元素3,由用途NORMAL和用途索引2标识,它映射到faceNormal2

受支持的顶点着色器输入用途input usage)是:

n POSITION [n]——位置

n BLENDWEIGHTS [n]——混合权重

n BLENDINDICES [n]——混合索引

n NORMAL [n]——法线向量

n PSIZE[n]——顶点大小

n DIFFUSE [n]——散射颜色

n SPECULAR [n]——镜面颜色

n TEXCOORD [n]——纹理坐标

其中,n是一个位于区间[0, 15]可选整数。

注意:再重复一遍,这些用途类型中的少数未在本书中提及,如:BLENDWEIGHTS, TANGENT, BINORMAL, BLENDINDICES, TESSFACTOR

此外,对于输出结构,我们必须指定每个成员是用来做什么的。例如,数据成员应该被作为位置向量、颜色、纹理坐标等对待吗?图形卡没主意,除非你强制的告诉它。这也需要通过语法的语义来完成:

struct VS_OUTPUT

{

vector position : POSITION;

vector diffuse : COLOR0;

vector specular : COLOR1;

};

受支持的顶点着色器输出用途是:

n POSITION位置

n PSIZE顶点大小

n FOG雾混合值

n COLOR [n]顶点颜色。注意:可以有多个顶点颜色被输出,并且这些颜色可以被混合在一起以产生最终的颜色。

n TEXCOORD [n]顶点纹理坐标。注意:多个顶点纹理坐标可以被输出。

其中,n是一个位于区间[0, 15]可选整数。

 楼主| 发表于 2008-4-17 21:13:42 | 显示全部楼层

2.3 使用顶点着色器的步骤

下面的列表概括了创建和使用顶点着色器的必须步骤。

1. 编写并编译顶点着色器

2. 创建一个IDirect3DVertexShader9接口以代表基于已编译着色器代码上的顶点着色器。

3. IDirect3DDevice9:: SetVertexShader方法允许这个顶点着色器。

当然,在我们做完这些之后,我们还得销毁这个顶点着色器。下面的各小节将更详细的迈入这些步骤。

2.3.1 编写并编译顶点着色器

首先,我们必须编写一个顶点着色器程序。在本书中的HLSL一章中,我们已经编写了我们的着色器(译者注:参见我翻译的译文第一章中各节)。一旦着色器代码写好之后,我们就使用D3DXCompileShaderFromFile函数编译这个着色器,如1.2.2所述。回忆一下,这个函数返回一个ID3DXBuffer指针,它包含了已编译的着色器代码。

2.3.2 创建顶点着色器

一旦我们拥有了编译好的着色器代码,我们就能够获得一个IDirect3DVertexShader9接口的指针,它代表一个顶点着色器——通过使用下面的方法:

HRESULT IDirect3DDevice9::CreateVertexShader(

const DWORD *pFunction,

IDirect3DVertexShader9** ppShader

);

pFunction——已编译着色器代码的指针

ppShader——返回一个IDirect3DVertexShader9接口的指针

例如,假设变量shader是一个包含已编译着色器代码的ID3DXBuffer(的接口指针)。然后要获得一个IDirect3DVertexShader9接口,我们可以写:

IDirect3DVertexShader9* ToonShader = 0;

hr = Device->CreateVertexShader(

(DWORD*)shader->GetBufferPointer(),

&ToonShader);

注意:重申一遍,D3DXCompileShaderFromFile是一个返回已编译着色器代码(shader)的函数。

2.3.3 建立顶点着色器

在我们获得了一个代表我们的顶点着色器的IDirect3DVertexShader9接口的指针之后,我们就能够使用下面的方法允许(enable)它:

HRESULT IDirect3DDevice9::SetVertexShader(

IDirect3DVertexShader9* pShader

);

这个方法仅接受一个参数,我们在其中传递一个想要允许的顶点着色器的指针。要允许这个我们在2.3.2创建的着色器,我们可以写:

Device->SetVertexShader(ToonShader);

2.3.4 销毁顶点着色器

和所有的Direc3D接口一样,要清除他们,我们就必须在用完它们之后调用其的Release方法。仍然以我们在2.3.2创建的顶点着色器为例,我们写:

d3d::Release<IDirect3DVertexShader9*>(ToonShader);

 楼主| 发表于 2008-4-17 21:14:56 | 显示全部楼层

2.4 样例应用程序:散射光照(上)

作为创建并使用顶点着色器的热身,我们写一个顶点着色器,它用一个方向(平行)光对每个顶点进行标准的散射光照。简而言之,散射光照根据顶点法线和光线向量(它指向光源方向)的角度计算顶点接收到的光线的数量。角度越小,则顶点接收到的光线就越多;而角度越大,则顶点接收到的光线就越少。如果角度大于等于90度,顶点就接收不到光线了。

我们以检阅着色器代码作为开始:

// File: diffuse.txt

// Desc: Vertex shader that does diffuse lighting.

// Global variables we use to hold the view matrix, projection matrix,

// Global variables we use to hold the view matrix, projection matrix,

// ambient material, diffuse material, and the light vector that

// describes the direction to the light source. These variables are

// initialized from the application.

//

matrix ViewMatrix;

matrix ViewProjMatrix;

vector AmbientMtrl;

vector DiffuseMtrl;

vector LightDirection;//

// Global variables used to hold the ambient light intensity (ambient

// light the light source emits) and the diffuse light

// intensity (diffuse light the light source emits). These

// variables are initialized here in the shader.

vector DiffuseLightIntensity = {0.0f, 0.0f, 1.0f, 1.0f};

vector AmbientLightIntensity = {0.0f, 0.0f, 0.2f, 1.0f};

//

//

// Input and Output structures.

//

struct VS_INPUT

{

vector position : POSITION;

vector normal : NORMAL;

};

struct VS_OUTPUT

{

vector position : POSITION;

vector diffuse : COLOR;

};

//

// Main

//

VS_OUTPUT Main(VS_INPUT input)

{

// zero out all members of the output instance.

VS_OUTPUT output = (VS_OUTPUT)0;

//

// Transform position to homogeneous clip space

// and store in the output.position member.

//

output.position = mul(input.position, ViewProjMatrix);

//

// Transform lights and normals to view space. Set w

// components to zero since we're transforming vectors

// here and not points.

//

LightDirection.w = 0.0f;

input.normal.w = 0.0f;

LightDirection = mul(LightDirection, ViewMatrix);

input.normal = mul(input.normal, ViewMatrix);

//

// Compute cosine of the angle between light and normal.

//

float s = dot(LightDirection, input.normal);

//

// Recall that if the angle between the surface and light

// is greater than 90 degrees the surface receives no light.

// Thus, if the angle is greater than 90 degrees we set

// s to zero so that the surface will not be lit.

//

if( s < 0.0f )

s = 0.0f;

//

// Ambient light reflected is computed by performing a

// component-wise multiplication with the ambient material

// vector and the ambient light intensity vector.

//

// Diffuse light reflected is computed by performing a

// component-wise multiplication with the diffuse material

// vector and the diffuse light intensity vector. Further

// we scale each component by the shading scalar s, which

// shades the color based on how much light the vertex received

// from the light source.

//

// The sum of both the ambient and diffuse components give

// us our final vertex color.

//

output.diffuse = (AmbientMtrl * AmbientLightIntensity) +

(s * (DiffuseLightIntensity * DiffuseMtrl));

return output;

}

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 07:03

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表