第三章 2D Rendering Sprites【已完成】【附带源码】

2D 游戏离开了 精灵的话, 就是2D游戏了; 精灵 是在游戏中用2D图形表示 角色或者 物体.你在游戏中遇到的每一棵树, 宝箱, 或者 地域怪物 都是精灵. 精灵, 是在 2D 游戏编程中 被广泛使用 及 易懂的 资源之一. 精灵通常是是 贴图了的矩形和正方形, 大量的精灵 共同创造了虚拟世界.

 

Z-Ordering

2D 游戏中, 有些对象在后台运行, 有些则在前端运作, 更有些在 前端 和 后端之间运行,  这些( 能够提供更对象在不同位置运行的 "平台") 叫做层, 可以理解为 一张透明的纸张. 例如, 背景层只能进行渲染 背景精灵, 而动作层 可能包含 全部的角色, 武器, 补血丹等. 这些层之间的 先后order顺序, 决定着 精灵 之间是否会被 覆盖渲染; 这就是 Z-ordering 的作用, 通常在对象渲染之前就排好序了; 每个被创建的精灵都将被赋予一个 深度depth, 表示特定绘画深度: Z-order值较小的 精灵将会被 较大值的精灵覆盖; 例如 所有 Z-order 值为 1 的精灵都会 绘制 Z-order值 为0 的 精灵上面, 而所有 Z-order 值为2的精灵都会 绘制在 Z-order 值为 1的精灵上面.

在2D游戏中, 我们是用 layer 和(/或) Z-ordering 来定义渲染顺序; 而在3D游戏中, 一般是用一种叫 "深度检测"的概念来确保 表面以正确的顺序显示; 我们将会在第6章中涉及 3D图形是讨论.

硬件深度检测 是用一种 叫做 深度buffer 的特殊渲染buffer, and it is used to determine if the previous surface's depth at that pixel is farther to the viewer the current surface's depth( 能够确定 一个像素所在 平面 是否比当前平面的深度更远 ? );也就是说, 对于一个位置, 如果 所在的前一个渲染平面 比当前要进行渲染的平面 要 远离 摄像头, 那么这个位置的就要被绘制  靠近摄像头的平面 的数据; 否则的话, 不对该位置进行数据更新( 不进行重绘)( 呃... 英文的语法 和中文还是有些差异的呀.)

在 3D 游戏中, 深度检测 是除了在摄像头视图中进行矩形预排序外, 能够正确渲染物体的的另外一种方式( 这将会在 第6章详细讨论).是用 Direct3D渲染, 绘制 2D精灵可以不适用 3D的 depth buffer. 完全清除屏幕, 直接在layer上绘制精灵, 我们就能够达到适当的目的.

 

Sprite Image

精灵最重要的资源是 图片; 一般来说, 每个精灵都需要一张图片与之关联,

无论是从玩游戏角度还是 技术角度; 图片能够 传达精灵的目的. 例如, 你的RPG主角的精灵可能是一张刀器图片, 甚至是 空战中的一艘航空船的图片. 你可以选择任何的图片, 但是精灵不能没有图片. 这是因为早期的2D游戏是直接将精灵图片是直接绘制在 渲染目标上, 而非现实几何体上. This is known as texture blitting. 在Direct3D中, 我们可以选择 先绘制2D 几何体再 对它进行纹理贴图, 而不用 旧2D游戏那样需要重新排序在进行绘制.

Texture blitting的核心机制是, 在渲染目标的特定位置, 直接拷贝精灵图片上的色值到 渲染目标上.

如果仅仅是开发 给2D游戏使用的渲染API软件, 那就这样做; 但使用 Direct3D 的话, 使用将2D纹理贴到 矩形几何体上的的方式 要 简单得多.

我们已经知道了如何从硬盘加载 纹理, 现在将学到如何 对精灵使用纹理.

 

Getting Sprites to the Screen

绘制精灵时, 需要确定精灵对象所出环境信息, 不仅要知道精灵对象所在位置以防止在 相同layer上与其他对象碰撞, 还要确定绘制的区域( 必须确定精灵的绘制边界); 通常, 绘图边界由 Direct3D设备关联的viewport 确定.

精灵的绘制区域通过 变换矩阵指定. 我们将会在第6章详细讨论矩阵, 但现在我们将大略提及下图形渲染中的几种矩阵. Direct3D中的矩阵一个

4*4 的矩阵,可以看作是 4行4列 总共包含16个元素的表; 通过这4行4列, 我们能够处理一些列的游戏图形图像处理 和 模拟.

通常在图形图像处理中, 我们由三种 变换矩阵 开始: 投影变换, 视图变换 和 世界变换( projection transformation, view transformation and world transformation).

通过矩阵对 顶点vertices进行变换, 就是按照 旋转矩阵的不同特性修改 vertices的特定数据:  旋转矩阵 对 对象进行旋转, 位移矩阵 对 对象进行 位置变换, 缩放矩阵 进行缩放. 但是我们为什么必须要这样做呢?

3D 空间中的对象 一般是使用 ( 如Autodesk's 3D Studio Max 的)3D 建模应用软件 创建的; 当模型 和 对象建模出来后, 将被赋予 所谓的模型空间, 既是说 模型的顶点位置 在此刻有 建模工具确定:模型建立在原点上, 就像我们在 上一节章使用三角形和矩形一样, 原点(0,0,0)作为形状的中心.

 

在游戏中, 我们可以使用一种或者一种以上模型的对象实例, 这些实例对象能移动, 和场景进行交互; 但是这些模型的属性数据是在 模型空间确定的, 如果模型对象要在实际世界中进行移动, 就必须将顶点位置更新到新(实际世界中)新的 位置. 不仅是使用 level/map编辑器 移动模型对象位置, 还是在游戏中进行交互( 如物理, 人工智能等), 也使用 矩阵对模型 移动,旋转 和 缩放.这种机制 让我们加载一份模型数据到内存, 并多次渲染到对象身上.假如游戏中 需要1000个石头, 我们仅仅需要 加载一个石头模型 创建 1000个带有位置和方向的矩阵(对应到 1000个对象上), 并且这些矩阵可以放在同一个buffer中( We could take it a step further and use Direct3D's instancing feature to perform one draw call and supply in a buffer the per-instance transformation matrices of all 1000 rocks).

 

简单的说, 变换矩阵允许我们定义了物体的虚拟位置 和 方向. 通过3D Studio Max软件创建的 模型处在模型空间中 , 当我们通过 (表示位置, 缩放 和方向的)变换矩阵处理模型的集合提示, 就需要从模型空间 变换成 实际世界空间.

 

摄像头在游戏,尤其是3D游戏中扮演着很重要的作用;摄像头的方向必须要有效关联到 几何体上, 才能模拟出摄像头效果.摄像头通过 视图矩阵表示, 视图矩阵将模型从其当前空间 变换到 可视空间; 在组合世界矩阵后产生一个 "三合一"的 world-view 矩阵.

除了模拟摄像头的位置和方向, 我们还添加投影.投影矩阵模拟正交投影或透视投影. 窥视管的缩放效果也能通过投影矩阵模拟.

第6章将详细讨论投影矩阵,现在先进行更高角度来看看.正交投影适合于2D对象, 因为可视深度并不对2D对象的渲染产生效果.有两个 10单元大小 Z轴上相距 100 单元的箱子, 运用正交投影在深度角度看这两个箱子好像是相邻着并未分开. 不仅能用在2D游戏, 3D游戏中的血条, 子弹数量, 计时 和 文本等都可以运用正交投影.

透视投影对渲染的对象添加透视效果: 远离摄像头的物体变得更小, 靠近摄像头的物体变得等大; 现实生活中往远处看时, 物体占据视野的尺寸变得更小; 但眼前的建筑要明显大于在1英里时看到的大小.

 

组合 模型-视图矩阵-投影矩阵 , 产生的 model-view 投影矩阵, shader处理后, 能够变换输入的几何体到最终目的位置; vertex shader 让使模型从本地位置(相对位置)变换到真实位置( 绝对位置); 动画运动也采用这种方式, 矩阵定义了几何体动画骨骼的位置 .

 

XNA Math库有一些函数能够进行创建 投影矩阵( 将在第6章讨论更多细节).现在来以 XMMatriOrthographicOffCenterLH() 作为例子进行讨论.

XMMatrixOrthographicOffCenterLH() 函数创建 左手坐标体系中的 正交投影矩阵, 返回值是 包含 投影矩阵的 XMMATRIX结构体.原型是:

XMMATRIX  XMMatrixOrthgraphicOffCenterLH(
    FLOAT   ViewLeft, 
    FLOAT   ViewRight,
    FLOAT   ViewBottom,
    FLOAT   ViewTop, 
    FLOAT   NearZ,
    FLOAT   FarZ

);

函数的参数确定了 投影视景: 第一个确定X轴最小值, 第二个X的最大值, 第三个为Y轴最小值, 第四个为Y轴最大值, 最后两个参数 划定最近视野的最近切面 和 最远切面.处于"最近切面" 前面的所有对象将不会被渲染绘制, "最远切面" 后面的所有对象都将不会被渲染. 这6个参数最终确定了视野通量. 剪切面在 深度重要的3D图形中起着很重要的作用.

投影矩阵 告知系统投放 精灵的 格子大小; 例如, 对于640像素宽度, 480像素高度的视景, 投影矩阵将严格地限制精灵的位置在此范围内; 这些将由Direct3D硬件处理.

投影矩阵一般不会变化, 除非视景的大小改变, 或者需要 切换投影矩阵以展示特殊的镜头效果.(我们暂时不需要考虑 XNA Math 函数的具体内部实现细节, 暂时只需要明白能够完成当下的主要功能实现.)

 

Positioning and Scaling Sprites

现在精灵处于明确的外部环境中, 移动他们将得以实现.在空间中移动物体 叫做translation ,另外一种形式的position. 自然界中的二维精灵能够在 X轴 和 Y轴两个方向上移动; 在Direct3D中, 甚至2D物体也能够在 3D 空间中移动.在范围为 640*480 的显示区域中心防止 精灵, 需要将它的X,Y坐标设置为

(320, 240); 需要在左上角坐标为(0,0)的正交投影视景内 水平移动精灵320像素, 纵轴方向移动240像素.

精灵移动是基于一种叫"变换点"的 "内部点". 精灵的"变换点"默认是精灵"模型空间"的原点; 游戏角色精灵的"变换点"通常为 左上角位置.

变换精灵时, 有必要在创建另一个矩阵: 位移矩阵(在前提及过的).Direct3D使用 位移矩阵 定位精灵(或者其他类型的几何体对象).XMMatrixTranslation()函数创建 位移矩阵, 函数原型如下:

XMMATRIX  XMMatrixTranslation( FLOAT OffsetX, FLOAT OffsetY, FLOAT OffSetZ)

参数 X, Y 和Z 是三个轴上的移动量. 在第六章,还可以看到如何创建 旋转和 缩放矩阵.

This was a bird's-eye view of matrices, but it should be clear as to their purpose.位移矩阵定位对象, 旋转矩阵旋转 对象, 缩放矩阵 缩放(收缩/放大)对象. 这三个矩阵连接组合成 world transformation matrix 世界变换矩阵. 连接 世界矩阵 和 视图矩阵 与 投影矩阵 产生 model-view  projection matrix( 模型-视图 投影矩阵).  vertex shader 使用这一矩阵( 变换输入的参数顶点), 矩阵变换 是 vertex shader完成修改输入顶信息的方式之一. 现在先不讨论 摄像头 和 视图, 知道 第6,7 和 8章.

 

The Game Sprite Demo

该 demo的目的是 创建一个能够简单表示精灵实例 精灵结构体.编写在屏幕上绘制精灵, 将引领我们开始创建 2D游戏 . 到此位置, 我们需要一种类来代表每一个精灵, 具有如下成员:

Position
Rotation
Scale
Sprite image
Vertex buffer 

但, 这是一本入门书籍, 我们只创建一份vertex buffer  和 纹理 供 场景中每一个精灵共享使用, 并独立渲染. 商业游戏中存在大量的绘画过程 和 状态改变, 都将消耗很大性能, 这需要批量处理几何体和纹理地图集.

游戏精灵类相当简单, 主要目的渲染时是 使用位移,旋转 和 缩放量创建 精灵的世界矩阵 .创建好的 世界矩阵, 以constant buffer方式作为参数 传进vertex shader; 为避免重复创建 相同的纹理 和 vertex buffer, 在本demo中这些都将会被创建一次, 并重复给 每个精灵使用/绘制.demo类中, 有需要绘制的 精灵资源数据, 一个传递给 vertex shader使用的 constant buffer, 一个属兔投影矩阵( XMMATRIX), 和 一个新的 模糊状态对象.

--GameSprite.h

#include <xnamath.h>

class GameSprite
{
public:
    GameSprite();
    virtual ~GameSprite();

    XMMATRIX GetWorldMatrix();

    void SetPosition( XMFLOAT2 &position);
    void SetRotation( float rotation);
    void SetScale( XMFLOAT2 &scale);

private:
    XMFLOAT2 position_;
    float rotation_;
    XMFLOAT2 scale_;
};

--GameSpriteDemo.h

#include "Dx11DemoBase.h"
#include "GameSprite.h"

class GameSpriteDemo : public Dx11DemoBase
{
    public:
        GameSpriteDemo();
        virtual ~GameSpriteDemo();

        bool LoadContent();
        void UnloadContent();

        void Update( float dt);
        void Render();

    private:
        ID3D11VertexShader *solidColorVS_;
        ID3D11PixelShader  *solidColorPS_;

        ID3D11InputLayout  *inputLayout_;
        ID3D11Buffer        *vertexBuffer_;

        ID3D11ShaderResourceView    *colorMap_;
        ID3D11SamplerState            *colorMapSampler_;
        ID3D11BlendState            *alphaBlendState_;

        GameSprite sprites_[ 2];
        ID3D11Buffer *mvpCB_;
        XMMATRIX vpMatrix_;
};

ID3D11BlendState 对象 blend state 在渲染时体现 alpha 透明 ; 这就是说, 我们使用的 32位纹理图片将附带 alpha 通道. 如在photoshop里看到的下图:

constant buffer 用以传送模型-视图投影矩阵到 shader, 而shader将使用 constant buffer的矩阵变换几何体. 鉴于这里没有摄像头投影矩阵也不会改变, 除非我们调整窗体的大小, 视图-投影矩阵可以计算一次后在整个渲染函数中使用, 而整个模型-视图投影矩阵都需要使用这个 "视图-投影矩阵", 最好的方式是将其成为一个 XMMATRIX成员.

 

Creating and Rendering the Game Sprite

GameSprite.cpp 源文件实现了 GameSprite 的成员函数; 函数只是简单存储了创建精灵世界矩阵, 模型矩阵所需的位置, 旋转和缩放.本例中每个轴的缩放被设置为 1.0f , 因为小于 1.0时物体将会被压缩, 大于1.0将被扩展, 而1.0将保持不变.

#include <d3d11.h>
#include <d3dx11.h>
#include "GameSprite.h"

GameSprite::GameSprite() : rotation_(0 )
{
    scale_.x = scale_.y = 1.0f;
}

GameSprite::~GameSprite()
{

}

XMMATRIX GameSprite::GetWorldMatrix()
{
    XMMATRIX translation = XMMatrixTranslation( position_.x, position_.y, 0.0f);
    XMMATRIX rotationZ = XMMatrixRotationZ( rotation_);
    XMMATRIX scale = XMMatrixScaling( scale_.x, scale_.y, 1.0f);

    return translation * rotationZ * scale;
}

void GameSprite::SetPosition( XMFLOAT2 &position)
{
    position_ = position;
}

void GameSprite::SetRotation( float rotation)
{
    rotation_ = rotation;
}

void GameSprite::SetScale( XMFLOAT2 &scale)
{
    scale_ = scale;
}

为渲染游戏精灵, 我们先要创建 world matrix , 调用 VSSetConstantBuffer()函数存储 world matrix 到 vertex shader的 constant buffer 内; 接着给每个游戏精灵绑定纹理和 shader, 然后使用sprite resource 渲染几何体.当然, 使用一个循环可以为每一游戏精灵同样进行渲染. 这是 GameSpriteDemo 的渲染函数:

void GameSpriteDemo::Render()
{
    if( d3dContext_ == 0 )
        return;

    float clearColor[ 4] = { 0.0f, 0.0f, 0.25f, 1.0f};
    d3dContext_->ClearRenderTargetView( backBufferTarget_, clearColor);

    unsigned int stride = sizeof( VertexPos);
    unsigned int offset = 0;

    d3dContext_->IASetInputLayout( inputLayout_);
    d3dContext_->IASetVertexBuffers( 0, 1, &vertexBuffer_, &stride, &offset);
    d3dContext_->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    d3dContext_->VSSetShader( solidColorVS_, 0, 0);
    d3dContext_->PSSetShader( solidColorPS_, 0, 0);
    d3dContext_->PSSetShaderResources( 0, 1, &colorMap_);
    d3dContext_->PSSetSamplers( 0, 1, &colorMapSampler_);


    for( int i=0 ; i< 2; ++i)
    {
        XMMATRIX world = sprites_[ i].GetWorldMatrix();
        XMMATRIX mvp = XMMatrixMultiply( world, vpMatrix_);
        mvp = XMMatrixTranspose( mvp);
        
        d3dContext_->UpdateSubresource( mvpCB_, 0, 0, &mvp, 0, 0);
        d3dContext_->VSSetConstantBuffers( 0, 1, &mvpCB_);

        d3dContext_->Draw( 6, 0);
    }

    swapChain_->Present( 0, 0);
}

每个精灵之间的区别只有 model-view projection matrix, 这就需要在绘画循环中进行修改; 每次调用 Draw()函数绘制精灵时, 使用的位置都是刚设置好的 model-view projection matrix.

在Render()函数中,调用 VSSetConstantBuffers() 函数设置vertex shader 的constant buffer.constant buffer 和所有的DirectX11 buffer 一样是 ID3D11BUFFER 类型, 通过LoadContent() 函数创建; 正如前面提到的, constant buffer的创建需要设置 buffer描述结构体的 BindFlags 上有 D3D11_BIND_CONSTANT_BUFFER.

LoadContent() 函数创建游戏精灵和它使用的各种资源;

UnloadContent()函数释放这些资源;

GameSpriteDemo 直接在 TextureMapping demo上抽取, 绝大部分代码都是一样的:

bool GameSpriteDemo::LoadContent()
{
    ID3DBlob    *vsBuffer = 0;
    
    // 第一步 加载并创建 shader
    bool compileResult = CompileD3DShader( "TextureMap.fx", "VS_Main", "vs_4_0", &vsBuffer);
    if( compileResult == false)
    {
        MessageBox( 0, "Error loading vertex shader !", "Compile Error", MB_OK);
        return false;
    }


    HRESULT d3dResult;
    d3dResult = d3dDevice_->CreateVertexShader( vsBuffer->GetBufferPointer(), vsBuffer->GetBufferSize(), 0, &solidColorVS_);
    if( FAILED( d3dResult))
    {
        if( vsBuffer)
            vsBuffer->Release();

        return false;
    }

    // 第二步, 创建 inputlayout等信息
    D3D11_INPUT_ELEMENT_DESC solidColorLayout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, 
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0,12, D3D11_INPUT_PER_VERTEX_DATA, 0}
    };

    unsigned int totalLayoutElements = ARRAYSIZE( solidColorLayout);
    d3dResult = d3dDevice_->CreateInputLayout( solidColorLayout, totalLayoutElements, vsBuffer->GetBufferPointer(), vsBuffer->GetBufferSize(), &inputLayout_);
    vsBuffer->Release();

    if( FAILED( d3dResult))
    {
        return false;
    }


    // pixel shader 部分
    ID3DBlob *psBuffer = 0;
    compileResult = CompileD3DShader( "TextureMap.fx", "PS_Main", "ps_4_0", &psBuffer);
    if( compileResult == false)
    {
        MessageBox( 0, "Error loading pixel shader !", "Compile Error", MB_OK);
        return false;
    }

    d3dResult = d3dDevice_->CreatePixelShader( psBuffer->GetBufferPointer(), psBuffer->GetBufferSize(), 0, &solidColorPS_);
    psBuffer->Release();
    if( FAILED( d3dResult))
    {
        return false;
    }


    d3dResult = D3DX11CreateShaderResourceViewFromFile( d3dDevice_, "decal2.dds", 0, 0, &colorMap_, 0);
    if( FAILED( d3dResult))
    {
        DXTRACE_MSG( "Failed to load the texture image!");
        return false;
    } 

    D3D11_SAMPLER_DESC colorMapDesc;
    ZeroMemory( &colorMapDesc, sizeof( colorMapDesc));
    colorMapDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    colorMapDesc.AddressV = D3D11_TEXTURE_ADDRESS_MIRROR;
    colorMapDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    colorMapDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    colorMapDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    colorMapDesc.MaxLOD = D3D11_FLOAT32_MAX;
    colorMapDesc.BorderColor[0] = 0.25;

    d3dResult = d3dDevice_->CreateSamplerState( &colorMapDesc, &colorMapSampler_);
    if( FAILED( d3dResult))
    {
        DXTRACE_MSG( "Failed to create color map sampler state!");
        return false;
    }


    ID3D11Resource *colorTex;
    colorMap_->GetResource( &colorTex);

    D3D11_TEXTURE2D_DESC colorTexDesc;
    ( ( ID3D11Texture2D*) colorTex)->GetDesc( &colorTexDesc);
    colorTex->Release();

    float halfWidth = ( float)colorTexDesc.Width / 2.0f;
    float halfHeight = ( float )colorTexDesc.Height / 2.0f;

    // 要绘制的几何体
    VertexPos vertices[] = {
        { XMFLOAT3( halfWidth, halfHeight, 1.0f), XMFLOAT2( 1.0f, 0.0f)},
        { XMFLOAT3( halfWidth, -halfHeight, 1.0f), XMFLOAT2( 1.0f, 1.0f)},
        { XMFLOAT3( -halfWidth, -halfHeight, 1.0f), XMFLOAT2( 0.0f, 1.0f)},

        { XMFLOAT3( -halfWidth, -halfHeight, 1.0f), XMFLOAT2( 0.0f, 1.0f)},
        { XMFLOAT3( -halfWidth, halfHeight, 1.0f), XMFLOAT2( 0.0f, 0.0f)},
        { XMFLOAT3( halfWidth, halfHeight, 1.0f), XMFLOAT2( 1.0f, 0.0f)},
    
    };

    D3D11_BUFFER_DESC vertexDesc;
    ZeroMemory( &vertexDesc, sizeof( vertexDesc) );
    vertexDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexDesc.ByteWidth = sizeof( VertexPos) * 6;

    D3D11_SUBRESOURCE_DATA resourceData;
    ZeroMemory( &resourceData, sizeof( resourceData));
    resourceData.pSysMem = vertices;

    d3dResult = d3dDevice_->CreateBuffer( &vertexDesc, &resourceData, &vertexBuffer_);
    if( FAILED( d3dResult))
    {
        return false;
    }

    D3D11_BUFFER_DESC constDesc;
    ZeroMemory( &constDesc, sizeof( constDesc));
    constDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    constDesc.ByteWidth = sizeof( XMMATRIX);
    constDesc.Usage = D3D11_USAGE_DEFAULT;
    d3dResult = d3dDevice_->CreateBuffer( &constDesc, 0, &mvpCB_);
    if( FAILED( d3dResult))
    {
        return false;
    } 

    XMFLOAT2 sprite1Pos( 100.0f, 300.0f);
    sprites_[ 0].SetPosition( sprite1Pos);

    XMFLOAT2 sprite2Pos( 400.0f, 100.0f);
    sprites_[ 1].SetPosition( sprite2Pos);

    XMMATRIX view = XMMatrixIdentity();
    XMMATRIX projection = XMMatrixOrthographicOffCenterLH( 0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f );
    vpMatrix_ = XMMatrixMultiply( view, projection);


    D3D11_BLEND_DESC blendDesc;
    ZeroMemory( &blendDesc, sizeof( blendDesc));
    blendDesc.RenderTarget[ 0].BlendEnable = TRUE;
    blendDesc.RenderTarget[ 0].BlendOp = D3D11_BLEND_OP_ADD;
    blendDesc.RenderTarget[ 0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
    blendDesc.RenderTarget[ 0].DestBlend = D3D11_BLEND_ONE;
    blendDesc.RenderTarget[ 0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
    blendDesc.RenderTarget[ 0].SrcBlendAlpha = D3D11_BLEND_ZERO;
    blendDesc.RenderTarget[ 0].DestBlendAlpha = D3D11_BLEND_ZERO;
    blendDesc.RenderTarget[ 0].RenderTargetWriteMask = 0x0F;

    float blendFactor[ 4] = { 0.0f, 0.0f, 0.0f, 0.0f};
    d3dDevice_->CreateBlendState( &blendDesc, &alphaBlendState_);
    d3dContext_->OMSetBlendState( alphaBlendState_, blendFactor, 0xFFFFFFFF);


    return true;
}


void GameSpriteDemo::UnloadContent()
{
    if( colorMapSampler_) colorMapSampler_->Release();
    if( colorMap_) colorMap_->Release();
    if( solidColorVS_) solidColorVS_->Release();
    if( solidColorPS_) solidColorPS_->Release();
    if( inputLayout_) inputLayout_->Release();
    if( vertexBuffer_) vertexBuffer_->Release();
    if( mvpCB_) mvpCB_->Release();
    if( alphaBlendState_) alphaBlendState_->Release();

    colorMapSampler_ = 0;
    colorMap_ = 0 ;
    solidColorVS_ = 0;
    solidColorPS_ = 0;
    inputLayout_ = 0;
    vertexBuffer_ = 0;
    mvpCB_ = 0;
    alphaBlendState_ = 0;
}

LoadContent() 函数首先是 存取加载的纹理, 以便能够获取 纹理的宽度和高度; 这些信息将在创建 vertex buffer时使用, 因为需要 知道显示在屏幕上的精灵有多大 .创建了 vertex buffer之后, 创建constant buffer. 因为 constant buffer在每一帧中设置 , constant buffer 将保持现状, 直至 在渲染时 被填充数据.

LoadContent() 函数的剩下部分 设置两个精灵的位置, 创建正交的 view-projection matrix, 创建 blend state( 模糊状态?); blend state 用来控制颜色的透明度: 1.0 全可见, 0.0不可见, 中间值时有半透明效果.

 

blend state 通过device 的 CreateBlendState() 函数创建, 通过 context的 OMSetBlendState() 函数设置. CreateBlendState() 函数接受一个 blend 描述结构体, 一个 创建的blend state对象 内存地址. OMSetBlendState() 函数 接受三个参数: CreateBlendState() 创建的

blend state对象, 每个颜色通道的模糊因子 和 一个模糊掩码; 但后两个参数用于高级效果的表现, 现在只是设置成默认值.

blend description 类型为 D3D11_BLEND_DESC . D3D11_BLEND_DESC 的大部分成员用来设置高级模糊效果, 例如 多次采样, alpha-to-coverage 等; 但最基本要设置 用于 render target's source and destination blend ( 渲染对象 源/目的). 从代码中可以看到, 我们在 渲染对象中开启了 模糊, 并且模糊值为 反相

( 1 - alpha) :  原颜色 被设置了alpha值 为 0.0f 变得不可见, 设置了 1.0f 的变得可见( 保持原样), 而 位于 0.0f 到 1.0f 之间部分将有 模糊半透明效果.

 

最后的神秘部分是 HLSL 代码部分:

/*
    Beginning DirectX 11 Game Programming
    By Allen Sherrod and Wendy Jones

    Texture Mapping Shader for the Game Sprite Demo
*/


cbuffer cbChangesPerFrame : register( b0 )
{
    matrix mvp_;
};


Texture2D colorMap_ : register( t0 );
SamplerState colorSampler_ : register( s0 );


struct VS_Input
{
    float4 pos  : POSITION;
    float2 tex0 : TEXCOORD0;
};

struct PS_Input
{
    float4 pos  : SV_POSITION;
    float2 tex0 : TEXCOORD0;
};


PS_Input VS_Main( VS_Input vertex )
{
    PS_Input vsOut = ( PS_Input )0;
    vsOut.pos = mul( vertex.pos, mvp_ );
    vsOut.tex0 = vertex.tex0;

    return vsOut;
}


float4 PS_Main( PS_Input frag ) : SV_TARGET
{
    return colorMap_.Sample( colorSampler_, frag.tex0 );
}

( 为什么constant buffer 需要在 HLSL代码中使用, 而 vertex buffer 不是呢? 因为 constant buffer是 与颜色相关部分, vertex buffer 是 几何体模型部分, 按照

model-word-view project matrix 顺序, 颜色部分是最近输出到 显示硬件部分, 而shader正是 在这时处理).在 HLSL代码中, 使用 cbuff 设置 constant buffer ; 使用 cbuff 关键字可以在  HLSL 代码中创建简单的constant buffer,

constant buffer 里的内容是可以随时存取的, 就好像它是一个全局变量, 正如

vertex shader 中直接使用 mvp_ 一样. 我们创建的 constant buffer 绑定到 b0 输入寄存器, texture 在 t0 或更到, sampler 在s0 或更高.

全部 HLSL 代码的主要改变是 vertex shader 对输入的顶点使用 model-view projection 矩阵 改变; 这只是简单地 让 vertex  乘以 matrix矩阵.变化后的顶点输入到 剩下的管线; pixel shader( 像素着色器) 没有改变, 依旧和 Texture Map 一样.

 

下面是  源码

posted @ 2012-12-07 03:05  Wilson-Loo  阅读(1489)  评论(0编辑  收藏  举报