渲染地形

很多3D游戏都需要地形,建立并渲染地形,再加上物理效果,比如驾驶一辆小汽车在场景行使,你会觉得这很困难。而这篇文章将讲解这种技术。
为了编译其中的程序代码,你需要:
一个C#编译器,最好是Visual Studio .NET
DIRECTX9.0c开发工具
一块显卡并且支持shader2.0以上,因为除非你不想它运行的很慢。(比如只有10FPS)
我只是推荐你,最好精通C#,和Managed DirectX。
开始,
首先你在渲染地形时,通常是用到一张带灰度信息的位图。这通常是一个容易的手段。这篇文章介绍的是使用一种灰度高度比例贴图。这就是我用到的贴图,你也可以自己去修改它。


我们将用两个纹理,一个草地,一个土地。地形比较高的部分用草地文理。我们将两种纹理混合在一起,这就是根据位图的灰度做到的。
这里用到一种语言High Level Shader Language
熟悉它你就会知道这是使用更高级的顶点和纹理的渲染方式。由于HLSL和C很相似,所以懂C的可以不费劲掌握它。以下的就是将两个纹理混合的HLSL程序:

float4x4 WorldViewProj;float4 light;
void Transform(
    in float4 inPos     : POSITION0,
    in float2 inCoord     : TEXCOORD0,
    in float4 blend     : TEXCOORD1,
    in float3 normal     : NORMAL,
    out float4 outPos     : POSITION0,
    out float2 outCoord     : TEXCOORD0,
    out float4 Blend     : TEXCOORD1,
    out float3 Normal     : TEXCOORD2,
    out float3 lightDir     : TEXCOORD3 )
{
    outPos = mul(inPos, WorldViewProj);
    outCoord = inCoord;
    Blend = blend;
    Normal = normalize(mul(normal,WorldViewProj));
    lightDir = inPos.xyz - light;
}

看上去HLSL和C的语法结构相似:再调节名称时,input,output变量前经常会有语意的定义。input通常是从程序中接受变量然后传导output。你也许注意到TEXCOORD被使用了很多次。这是因为TEXCOORD在程序中使用了多次,一个变量不提供位置,法线等。而HLSL包含了计算函数如mul(),normalize().在MSDN里可以知道使用方法。想知道更多关于HLSL方面的知识。推荐去访问相关的一些网站。

现在解释一下HLSL代码含义:首先input接受position,position在这里是和投影矩阵相乘之后得到的。顶点将被移动到相机的空间。input接受纹理坐标并混合两种纹理。法线也被移动。最后平行光被减去光源的坐标(该例,世界空间是和对象空间一致的所以不会有改变)将其传给像素渲染,以下是像素渲染代码:
Texture Texture1;
Texture Texture2;

sampler samp1 = sampler_state { texture = <Texture1>;
minfilter = LINEAR; mipfilter = LINEAR; magfilter = LINEAR;};
sampler samp2 = sampler_state { texture = <Texture2>;
minfilter = LINEAR; mipfilter = LINEAR; magfilter = LINEAR;};
float4 TextureColor(
    in float2 texCoord     : TEXCOORD0,
    in float4 blend     : TEXCOORD1,
    in float3 normal     : TEXCOORD2,
    in float3 lightDir     : TEXCOORD3) : COLOR0
{
    float4 texCol1 = tex2D(samp1, texCoord*4) * blend[0];
    float4 texCol2 = tex2D(samp2, texCoord) * blend[1];
    return (texCol1 + texCol2) * (saturate(dot(normalize(normal),
                      normalize(light)))* (1-ambient) + ambient);
}

正象你所看到的,像素渲染器使用了所有顶点渲染器中的变量,除了POSITION0,因为POSITION0是所有输出所用到的。首先两个纹理将用tex2D()做计算,tex2D()不影响文理只是一个处理器而已。这些颜色根据混合值相乘并加上光线的密度,然后反回结果。返回值是float4由COLOR0语法做了标记。每个像素渲染器都要返回一个这样的值。

再回到C#
为了使程序和SHADER通信,必须有顶点声明。将通知DIRECTX数据在顶点缓冲以及相关的input顶点渲染变量,以下是顶点声明代码:
VertexElement[] v = new VertexElement[]
{
    new VertexElement(0,0,DeclarationType.Float3,DeclarationMethod.Default,
        DeclarationUsage.Position,0),
    new VertexElement(0,12,DeclarationType.Float3,DeclarationMethod.Default,
        DeclarationUsage.Normal,0),
    new VertexElement(0,24,DeclarationType.Float2,DeclarationMethod.Default,
        DeclarationUsage.TextureCoordinate,0),
    new VertexElement(0,32,DeclarationType.Float4,DeclarationMethod.Default,
        DeclarationUsage.TextureCoordinate,1),
    VertexElement.VertexDeclarationEnd
};

decl = new VertexDeclaration(device,v);

正如你看到的,顶点声明包含一个数组VertexElements描述了顶点的结构。说道结构,你不能自己定义一个CustomVertex,因为每一个文理参数必须精确到每一顶点。所以结构必须是以下:
public struct Vertex
{
    Vector3 pos;
    Vector3 nor;
    float tu,tv;
    float b1,b2,b3,b4;
    public Vertex(Vector3 p,Vector3 n,
        float u,float v,float B1,float B2,
        float B3, float B4, bool normalize)
    {
        pos = p;nor = n;tu = u;tv = v;
        b1=B1; b2=B2; b3=B3;b4 = B4;
        float total = b1 + b2 + b3 + b4;
        if ( normalize)
        {
            b1 /= total;
            b2 /= total;
            b3 /= total;
            b4 /= total;
        }
    }
    public static VertexFormats Format =
       VertexFormats.Position | VertexFormats.Normal |
       VertexFormats.Texture0 | VertexFormats.Texture1;
}

它包含了Vector3描述位置,法线。floats描述文理坐标和混合后的值。标记到结构成员,还包含了一个构造器。还包含了一个变量格式。

为了和DIRECTX的EFFECT通信。要用Effect类建立Effect:
The Effect class
String s = null;

effect = Effect.FromFile(device, @"../../simple.fx", null,
ShaderFlags.None, null, out s);
if ( s != null)
{
    MessageBox.Show(s);
    return;
}

通常,不需要调试shaders,如果其中代码有bug,可能要花几小时去找,因为没有任何的线索。为了避免如此,需要Effect过载构造器,这将输出参数并且虽然有effect抛出CompilationErrors。过载也会成功编译,只是在output不是null时会有出错的对话框出现。

好,除了包含所有入口点的类,程序还包含地形类。该类读取位图数据,然后建立VertexBuffer 和IndexBuffer。我们指定地形高度,包围盒。 获得暗度和亮度,每个位图的像素成为了地形顶点高度。所有矩形分成三角形,假如索引面列表。渲染时使用effect.BeginScene(),当渲染被调用时effect.BeginScene()将通知effect要得到数据来渲染,effect.EndScene()将停止顶点数据的处理。另外VertexDeclaration, VertexBuffer, 和 IndexBuffer被设置,最后DrawPrimitives渲染地形。

为了修改shader的全局变量,Effect类包含SetValue()方法,你可以输入一段字符做标记:
effect.SetValue("Texture1", t1);
An other way is to create an EffectHandle, like this:

EffectHandle handle = effect.GetParameter(null,"ambient");
effect.SetValue(handle,0.5f);

用这种方法你不必向方法赋值,所以会更快。所以变量只能设置一次,但如果需要变化多次,最好使用techniques,当使用了techniques,shaders将自动调用每一次。Effect文件能包含许多顶点和像素处理器,而且至少要有一个technique,technique是如下做声明:
technique TransformTexture
{
    pass P0
    {
        VertexShader = compile vs_2_0 Transform();
        PixelShader = compile ps_2_0 TextureColor();
    }
}

一个technique由一个或多个passes构成,这里只有一个P0;每个pass,你要安排顶点和像素处理器(这里有很多HLSL处理器的版本:1_1,2_0,3_0。更高版本,更可能有人提供,但是显卡能支持的却很少)Transform() 和 TextureColor() 是顶点和像素处理器的名称。
在一个pass中,你也可以设置RenderState,如果你想这样要将Device.RenderState.Cullmode 设为 Cull.None,然后放到pass的第一行:
CullMode = None;  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值