菜鸟学堂:
directx9 3d快速上手 3
by sssa2000
4/15/2005
我们这里暂时先跳过,乏味的索引缓冲和深度缓冲的内容,先看看怎么在3d空间中实现一个东西,给自己来点成就感。
正好sdk的向导也是这么安排的,呵呵,那我们就继续从向导出发吧,以tutorial 3为例子。
这个例子主要讲解运用变换矩阵来实现物体的变换,学过图形学或者线性代数的肯定就很容易理解,没学过的,找点这方面的资料看看就可以了。
先看看这个例子实现了什么,编译运行,噢,相比前面两个例子,有耳目一新的感觉,一个三角形绕着y轴旋转。
相比tutorial 2,多了2个函数:onresetdevice和setupmatrices。其中有注释,中文由我翻译,e文太差,翻译得不好不要b4我啊
public void onresetdevice(object sender, eventargs e)
{
device dev = (device)sender;
// turn off culling, so we see the front and back of the triangle
//关闭剔除,所以我们可以看见前面的和背面的三角形
dev.renderstate.cullmode = cull.none;
// turn off d3d lighting, since we are providing our own vertex colors
//关闭灯光,因为我们已经提供了自己的颜色
dev.renderstate.lighting = false;
}
private void setupmatrices()
{
// for our world matrix, we will just rotate the object about the y-axis.
// set up the rotation matrix to generate 1 full rotation (2*pi radians)
// every 1000 ms. to avoid the loss of precision inherent in very high
// floating point numbers, the system time is modulated by the rotation
// period before conversion to a radian angle.
// 在世界坐标中,我们要绕y旋转,建立旋转矩阵产生每秒360度的旋转,为了避免浮点数中的位数造成的时间上的损失,我们在转变为弧度前,强制调整系统时间
int itime = environment.tickcount % 1000;
float fangle = itime * (2.0f * (float)math.pi) / 1000.0f;
device.transform.world = matrix.rotationy( fangle );
// set up our view matrix. a view matrix can be defined given an eye point,
// a point to lookat, and a direction for which way is up. here, we set the
// eye five units back along the z-axis and up three units, look at the
// origin, and define "up" to be in the y-direction.
//建立观察矩阵,它可以被定义为视点,就像一个摄像机一样。这里我们设置在z轴-5,y轴3的位置
device.transform.view = matrix.lookatlh( new vector3( 0.0f, 3.0f,-5.0f ), new vector3( 0.0f, 0.0f, 0.0f ), new vector3( 0.0f, 1.0f, 0.0f ) );
// for the projection matrix, we set up a perspective transform (which
// transforms geometry from 3d view space to 2d viewport space, with
// a perspective divide making objects smaller in the distance). to build
// a perpsective transform, we need the field of view (1/4 pi is common),
// the aspect ratio, and the near and far clipping planes (which define at
// what distances geometry should be no longer be rendered).
//射影矩阵,我们建立一个透视变换(透视变换把3d空间变换到2d视口,造成透视的效果),为了建立透视变换,我们需要视觉空间,(通常是1/4 pi)纵横比 和 远距离剔除(远处物体不渲染)
device.transform.projection = matrix.perspectivefovlh( (float)math.pi / 4, 1.0f, 1.0f, 100.0f );
}
所有的新东西都在这里了,一共不到10条语句,简单吧?有几点要解释一下。
dev.renderstate.cullmode = cull.none;
这一句,或许很多人会疑惑,首先了解一下dx的背面剔除功能,和现实中一样,我们同一时间只能够看到一个物体的一面,看不到它的背面,这就是背面剔除。cull是一个枚举,一共3个值:
counterclockwise
3
cull back faces with counterclockwise vertices.
clockwise
2
cull back faces with clockwise vertices.
none
1
do not cull back faces.
如果我们指定了背面剔除的方式,那么我们就看不到物体的背面,这样当三角形转过来的时候我们就看不到转过来的东西,所以设置为none,如果不理解,可以在程序中修改一下这一句话,就能很直观的理解了。
这个例子就这么多内容,当然你也可以把rotatey改为,rotatex, rotatez等等,可以更加好的理解一下这个例子。
接下来我们来了解一下mesh。
对我们初学者来说mesh应该是一个让人激动的东西,让我们来看看介绍:mesh可以用来储存任何类型的图形数据,但主要用来封装复杂的模型。mesh类同样也有一些用来提高渲染物体性能的方法。用mesh你可以从外部文件读入3d的模型例如.3ds文件,这样我们就可以在3d max中做好模型,然后读入程序,想想看,一个游戏的雏形是不是已经在你脑海了呢?
mesh对象内部也包含了很多几何体的模型,我们来看看怎么使用它,因为使用它比使用顶点要快捷方便得多,一会你就会深刻体会到。
首先我们要先建立一个mesh对象 private mesh mesh = null;
然后mesh = mesh.box(device,2.0f,2.0f,2.0f); 这样我们就建立了一个长宽高都为2的立方体,很简单吧?如果你用顶点来建立的话,最快的方法,即使用索引缓冲器,深度缓冲器也要写8个顶点得值,还要setstreamsource等等繁琐的工作,这一切在mesh中都替我们完成了。当然,mesh内置的几何形体还有很多,比如圆柱,茶壶等等,你可以一个一个的试一下。
我们来看看核心的drawbox函数:
this is equivalent
private void drawbox(float yaw, float pitch, float roll, float x, float y, float z)
{
angle += 0.01f;
device.transform.world = matrix.rotationyawpitchroll(yaw, pitch, roll) * matrix.translation(x, y, z);
material boxmaterial = new material();
boxmaterial.ambient = color.white; //环境色
boxmaterial.diffuse = color.white; //漫反射
device.material = boxmaterial;
mesh.drawsubset(0);
}
先介绍matrix.rotationyawpitchroll方法,看看他的原形:
public static matrix rotationyawpitchroll(
float yaw, //偏移量,即绕y轴的转动角度
float pitch,//斜度,即绕x轴的角度
float roll//滚动,即绕z的角度
);
材质(materials)描述了这样的一种属性。你可以指定物体如何反射环境光以及散射(diffuse)光线,镜面高光(specular highlights)(少后会讨论它)看起来是什么样,以及物体是否完全反射(emit)光线。 这里创建了一个新的材质,它的环境颜色(ambient color)(注:环境颜色和环境光的颜色是不同的^_^)和散射颜色值都被设置为白色。使用白色表示它会反射所有的光线。接下来,我们把材质赋予了device的material属性,这样direct3d就知道渲染时使用那种材质数据。
这里介绍一点关于光线和色彩的知识:环境色(ambient color),当其为黑色时,表示(环境光)不会影响材质的颜色,当环境色变浅时,它就会照亮材质,并将两种颜色混和起来,从而影响材质的颜色。如何场景中有环境光,那么这些光的颜色和亮度就会控制环境色对于最终材质颜色的影响程度)。把材质改为没有红色成分的颜色(比如绿色)会使物体再次变为黑色(注:因为此时物体不会反射红色,红色的光线被物体全部吸收了),改为含一些红色成分的颜色(比如灰色gray)会使物体呈现深灰色。
mesh会被分为一系列的子集(subsets)(依据属性缓冲的大小来分配),同时使用一个叫做“drawsubset”的方法来渲染。使用mesh类创建的普通图元总是只有一个基于0的子集。所以使用了mesh.drawsubset(0)
下面附上这个例子的全部代码:
using system;
using system.drawing;
using system.collections;
using system.componentmodel;
using system.windows.forms;
using system.data;
using microsoft.directx;
using microsoft.directx.direct3d;
namespace chapter5code
{
/// <summary>
/// summary description for form1.
/// </summary>
public class form1 : system.windows.forms.form
{
private device device = null;
private mesh mesh = null;
/// <summary>
/// required designer variable.
/// </summary>
private system.componentmodel.container components = null;
private float angle = 0.0f;
public form1()
{
//
// required for windows form designer support
//
initializecomponent();
this.setstyle(controlstyles.allpaintinginwmpaint | controlstyles.opaque, true);
}
/// <summary>
/// we will initialize our graphics device here
/// </summary>
public void initializegraphics()
{
// set our presentation parameters
presentparameters presentparams = new presentparameters();
presentparams.windowed = true;
presentparams.swapeffect = swapeffect.discard;
presentparams.autodepthstencilformat = depthformat.d16;
presentparams.enableautodepthstencil = true;
// create our device
device = new device(0, devicetype.hardware, this, createflags.softwarevertexprocessing, presentparams);
mesh = mesh.box(device, 2.0f, 2.0f, 2.0f);
}
private void setupcamera()
{
device.transform.projection = matrix.perspectivefovlh((float)math.pi / 4, this.width / this.height, 1.0f, 100.0f);
device.transform.view = matrix.lookatlh(new vector3(0,0, 18.0f), new vector3(), new vector3(0,1,0));
device.renderstate.ambient = color.darkblue;
device.lights[0].type = lighttype.directional;
device.lights[0].diffuse = color.darkblue;
device.lights[0].direction = new vector3(0, -1, -1);
device.lights[0].update ();
device.lights[0].enabled = true;
}
protected override void onpaint(system.windows.forms.painteventargs e)
{
device.clear(clearflags.target | clearflags.zbuffer, color.cornflowerblue, 1.0f, 0);
setupcamera();
device.beginscene();
// draw our boxes
drawbox(angle / (float)math.pi, angle / (float)math.pi * 2.0f, angle / (float)math.pi / 4.0f, 0.0f, 0.0f, 0.0f);
//drawbox(angle / (float)math.pi, angle / (float)math.pi / 2.0f, angle / (float)math.pi * 4.0f, 5.0f, 0.0f, 0.0f);
// drawbox(angle / (float)math.pi, angle / (float)math.pi * 4.0f, angle / (float)math.pi / 2.0f, -5.0f, 0.0f, 0.0f);
// drawbox(angle / (float)math.pi, angle / (float)math.pi * 2.0f, angle / (float)math.pi / 4.0f, 0.0f, -5.0f, 0.0f);
// drawbox(angle / (float)math.pi, angle / (float)math.pi / 2.0f, angle / (float)math.pi * 4.0f, 5.0f, -5.0f, 0.0f);
// drawbox(angle / (float)math.pi, angle / (float)math.pi * 4.0f, angle / (float)math.pi / 2.0f, -5.0f, -5.0f, 0.0f);
// drawbox(angle / (float)math.pi, angle / (float)math.pi * 2.0f, angle / (float)math.pi / 4.0f, 0.0f, 5.0f, 0.0f);
drawbox(angle / (float)math.pi, angle / (float)math.pi / 2.0f, angle / (float)math.pi * 4.0f, 5.0f, 5.0f, 0.0f);
drawbox(angle / (float)math.pi, angle / (float)math.pi * 4.0f, angle / (float)math.pi / 2.0f, -5.0f, 5.0f, 0.0f);
device.endscene();
device.present();
this.invalidate();
}
private void drawbox(float yaw, float pitch, float roll, float x, float y, float z)
{
angle += 0.01f;
device.transform.world = matrix.rotationyawpitchroll(yaw, pitch, roll) * matrix.translation(x, y, z);
material boxmaterial = new material();
boxmaterial.ambient = color.white;
boxmaterial.diffuse = color.white;
device.material = boxmaterial;
mesh.drawsubset(0);
}
/// <summary>
/// clean up any resources being used.
/// </summary>
protected override void dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.dispose();
}
}
base.dispose( disposing );
}
#region windows form designer generated code
/// <summary>
/// required method for designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void initializecomponent()
{
this.components = new system.componentmodel.container();
this.size = new size(800,600);
this.text = "form1";
}
#endregion
/// <summary>
/// the main entry point for the application.
/// </summary>
static void
main
()
{
using (form1 frm = new form1())
{
// show our form and initialize our graphics engine
frm.show();
frm.initializegraphics();
application.run(frm);
}
}
}
}
不知道大家注意到没有,我注释掉了很多drawbox函数的调用,你也可以把它改过来,运行看看,你会发现一个问题,就是如果你画9个box和你画一个box,旋转的速度是不一样的,因为这里并没有像上个例子那样对时间有强制的调整。这个例子里面还有一些是关于灯光的,我们马上就会讲到,其实从字面也可以看出来是什么意思。接下来我们就要去读入我们自己的模型了,激动啊。
2005-4-15