【DirectX11】【学习笔记(10)】混合

今天我们学习一下如何给图元增加一点混合效果,这样看起来会更透明。

混合等式

D3D中的混合效果和把render target上的像素与在它之前的透明图元用混合等式执行像素融合

(FC) - Final Color
(SP) - Source Pixel
(DP) - Destination Pixel
(SBF) - Source Blend Factor
(DBF) - Destination Blend Factor
(FA) - Final Alpha
(SA) - Source Alpha
(DA) - Destination Alpha
(+) - Binaray Operator described below
(X) - Cross Multiply Matrices

D3D主要使用两种不同的等式,一个是针对颜色,一个是针对透明度(alpha),我们可以同时处理这两个不同的等式实现效果。

(FC) = (SP) (X) (SBF) (+) (DP) (X) (DPF)


(FA) = (SA)(SBF) (+) (DA)(DBF)

The binary (+) operator can be one of the following:

typedef enum D3D11_BLEND_OP {
  D3D11_BLEND_OP_ADD            = 1,
  D3D11_BLEND_OP_SUBTRACT       = 2,
  D3D11_BLEND_OP_REV_SUBTRACT   = 3,
  D3D11_BLEND_OP_MIN            = 4,
  D3D11_BLEND_OP_MAX            = 5 
} D3D11_BLEND_OP;

SBF和DBF混合因子是D3D11_BLEND枚举值类型。

typedef enum D3D11_BLEND {
  D3D11_BLEND_ZERO               = 1,
  D3D11_BLEND_ONE                = 2,
  D3D11_BLEND_SRC_COLOR          = 3,
  D3D11_BLEND_INV_SRC_COLOR      = 4,
  D3D11_BLEND_SRC_ALPHA          = 5,
  D3D11_BLEND_INV_SRC_ALPHA      = 6,
  D3D11_BLEND_DEST_ALPHA         = 7,
  D3D11_BLEND_INV_DEST_ALPHA     = 8,
  D3D11_BLEND_DEST_COLOR         = 9,
  D3D11_BLEND_INV_DEST_COLOR     = 10,
  D3D11_BLEND_SRC_ALPHA_SAT      = 11,
  D3D11_BLEND_BLEND_FACTOR       = 14,
  D3D11_BLEND_INV_BLEND_FACTOR   = 15,
  D3D11_BLEND_SRC1_COLOR         = 16,
  D3D11_BLEND_INV_SRC1_COLOR     = 17,
  D3D11_BLEND_SRC1_ALPHA         = 18,
  D3D11_BLEND_INV_SRC1_ALPHA     = 19 
} D3D11_BLEND;

D3D11_BLEND_ZERO - 数据是黑色

D3D11_BLEND_ONE - 数据是白色

D3D11_BLEND_SRC_COLOR - 数据来源于PS着色器中的RGB(PS中的数据,就是混合等式中被添加的部分)

D3D11_BLEND_INV_SRC_COLOR - 数据来源于PS着色器中的RGB,但是有预处理,结果为1-RGB

D3D11_BLEND_SRC_ALPHA - 数据为PS中的A值

D3D11_BLEND_INV_SRC_ALPHA - PS中的1-A值

D3D11_BLEND_DEST_ALPHA - 数据来自已经存在render target上的A值

D3D11_BLEND_INV_DEST_ALPHA - 数据来自已经存在render target上的1-A值

D3D11_BLEND_DEST_COLOR  - 数据来自已经存在render target上的RGB值

D3D11_BLEND_INV_DEST_COLOR -  数据来自已经存在render target上的1- RGB值

D3D11_BLEND_SRC_ALPHA_SAT  - 数据来自源于PS着色器中的A,但是blend操作会把数据缩小到1及以下

D3D11_BLEND_BLEND_FACTOR - 数据来源于ID3D10Device::OMSetBlendState函数中设置的belnd factor

D3D11_BLEND_INV_BLEND_FACTOR -同上,结果为1-factor

D3D11_BLEND_SRC1_COLOR - 两个数据都是由PS产生,这个操作支持两个源数据颜色混合,而不是把一个PS上的数据与rendertarget混合

D3D11_BLEND_INV_SRC1_COLOR - 同上,结果为1-RGB

D3D11_BLEND_SRC1_ALPHA - 同上结果为A

D3D11_BLEND_INV_SRC1_ALPHA - 同上,结果为1-A

了解了混合等式之后,我们就要创建我们的混合描述信息了

typedef struct D3D11_BLEND_DESC {
  BOOL                           AlphaToCoverageEnable;
  BOOL                           IndependentBlendEnable;
  D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;

AlphaToCoverageEnable  - 多重采样在这里很有用,现在只是将它设为true

IndependentBlendEnable - 我们能够一次和多个render target进行混合,最多为8个,设为false默认与第一个进行混合

RenderTarget[8] - 数组中的每一个结构体都是一个对render target 的blending描述

typedef struct D3D11_RENDER_TARGET_BLEND_DESC {
  BOOL           BlendEnable;
  D3D11_BLEND    SrcBlend;
  D3D11_BLEND    DestBlend;
  D3D11_BLEND_OP BlendOp;
  D3D11_BLEND    SrcBlendAlpha;
  D3D11_BLEND    DestBlendAlpha;
  D3D11_BLEND_OP BlendOpAlpha;
  UINT8          RenderTargetWriteMask;
} D3D11_RENDER_TARGET_BLEND_DESC;

BlendEnable - 是否能进行混合

SrcBlend  - 等式中的SBF,可以设为D3D11_BLEND 中的一个

DestBlend - DBF,同上

BlendOp D3D11_BLEND_OP 枚举值

SrcBlendAlpha - SBF(alpha channel),D3D11_BLEND 枚举值

DestBlendAlpha - DBF,同上

BlendOpAlpha  - 这里我们设为D3D10_BLEND_OP 枚举值

RenderTargetWriteMask - 指定要混合的channel

typedef enum D3D11_COLOR_WRITE_ENABLE {
  D3D11_COLOR_WRITE_ENABLE_RED     = 1,
  D3D11_COLOR_WRITE_ENABLE_GREEN   = 2,
  D3D11_COLOR_WRITE_ENABLE_BLUE    = 4,
  D3D11_COLOR_WRITE_ENABLE_ALPHA   = 8,
  D3D11_COLOR_WRITE_ENABLE_ALL     = 
      ( D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN |  
        D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_ALPHA ) 
} D3D11_COLOR_WRITE_ENABLE;

Transparent Object's Depth Order.

当我们想要看到一个透明物体的背面的时候,我们可能会遇到一点问题。

当我们执行blend的时候,我们先假设此时render target上已经存在像素(被渲染好的物体)了。

先渲染不透明的物体,再渲染透明的物体,这一点很重要,下面就是我们的问题

eg:当我们第一次渲染框时,绕第二个盒子运行的第一个盒子开始于第二个盒子之后,并在代码中首先渲染。因此,当渲染第二个盒子时,它能够与第一个盒子混合,因为第一个盒子已经在渲染目标上。但是,当第一个盒子围绕第二个盒子旋转,并且位于第二个盒子的前面时,它将不会与第二个盒子混合

为了解决这个问题,我们需要每帧都计算物体到相机的距离,来决定先渲染哪个物体

除此之外,我们还有一些其他的问题,D3D是默认裁剪掉逆时针的平面,也就是说,当一个三角形面对摄像机是逆时针绘制的,即不可见。所以我们一般看不到背面,所以当绘制了Box的前面的时候,就不能和背面进行混合,因为背面其实看不到。

如果我们更改裁剪顺序,也不能满足我们的问题,此外,如果同一个box的一个距离相机更近的面先被绘制,他们就不能和其他面进行混合。所以一个box的面有时候不透明,有时候不可见

为了解决上面这个问题,我们需要绘制box两次,这样我们就能看的到背面,并且绘制前面的时候,背面已经在render target上了

首先我们要创建两个render states,一个顺时针,一个逆时针。

然后我们先用逆时针绘制一遍,因为我们的面试顺时针的,这样就能把box内部的面绘制出来(从里面看是逆时针的)

上述问题搞定了之后,我们开始写本节的代码

Global Declarations

ID3D11BlendState* Transparency;
ID3D11RasterizerState* CCWcullMode;
ID3D11RasterizerState* CWcullMode;

Clean Up

别忘了删除指针

    Transparency->Release();
    CCWcullMode->Release();
    CWcullMode->Release();

The Blending Equation

首先我们要创建render target blending desc,然后创建blend desc(结构太多,要看仔细,不然容易搞混)

D3D11_BLEND_DESC blendDesc;
ZeroMemory( &blendDesc, sizeof(blendDesc) );

D3D11_RENDER_TARGET_BLEND_DESC rtbd;
ZeroMemory( &rtbd, sizeof(rtbd) );

rtbd.BlendEnable             = true;
rtbd.SrcBlend                 = D3D11_BLEND_SRC_COLOR;
rtbd.DestBlend                 = D3D11_BLEND_BLEND_FACTOR;
rtbd.BlendOp                 = D3D11_BLEND_OP_ADD;
rtbd.SrcBlendAlpha             = D3D11_BLEND_ONE;
rtbd.DestBlendAlpha             = D3D11_BLEND_ZERO;
rtbd.BlendOpAlpha             = D3D11_BLEND_OP_ADD;
rtbd.RenderTargetWriteMask     = D3D10_COLOR_WRITE_ENABLE_ALL;

blendDesc.AlphaToCoverageEnable = false;
blendDesc.RenderTarget[0] = rtbd;

d3d11Device->CreateBlendState(&blendDesc, &Transparency);

CW & CCW Culling

为了绘制两遍,我们要创建两个RSstate

这部分的内容我们之前已经讲过一些

D3D11_RASTERIZER_DESC cmdesc;
ZeroMemory(&cmdesc, sizeof(D3D11_RASTERIZER_DESC));
    
cmdesc.FillMode = D3D11_FILL_SOLID;
cmdesc.CullMode = D3D11_CULL_BACK;

cmdesc.FrontCounterClockwise = true;
hr = d3d11Device->CreateRasterizerState(&cmdesc, &CCWcullMode);

cmdesc.FrontCounterClockwise = false;
hr = d3d11Device->CreateRasterizerState(&cmdesc, &CWcullMode);

DrawScene() Function

最后就是利用blend等式和创建好的RS state来进行绘制啦,别忘了实时计算物体到相机的位置

void DrawScene()
{
    //Clear our backbuffer
    float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);

    //Refresh the Depth/Stencil view
    d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);

    ///**************new**************
    //"fine-tune" the blending equation
    float blendFactor[] = {0.75f, 0.75f, 0.75f, 1.0f};

    //Set the default blend state (no blending) for opaque objects
    d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff);

    //Render opaque objects//

    //Set the blend state for transparent objects
    d3d11DevCon->OMSetBlendState(Transparency, blendFactor, 0xffffffff);

    //*****Transparency Depth Ordering*****//
    //Find which transparent object is further from the camera
    //So we can render the objects in depth order to the render target
    
    //Find distance from first cube to camera
    XMVECTOR cubePos = XMVectorZero();

    cubePos = XMVector3TransformCoord(cubePos, cube1World);

    float distX = XMVectorGetX(cubePos) - XMVectorGetX(camPosition);
    float distY = XMVectorGetY(cubePos) - XMVectorGetY(camPosition);
    float distZ = XMVectorGetZ(cubePos) - XMVectorGetZ(camPosition);

    float cube1Dist = distX*distX + distY*distY + distZ*distZ;

    //Find distance from second cube to camera
    cubePos = XMVectorZero();

    cubePos = XMVector3TransformCoord(cubePos, cube2World);

    distX = XMVectorGetX(cubePos) - XMVectorGetX(camPosition);
    distY = XMVectorGetY(cubePos) - XMVectorGetY(camPosition);
    distZ = XMVectorGetZ(cubePos) - XMVectorGetZ(camPosition);

    float cube2Dist = distX*distX + distY*distY + distZ*distZ;

    //If the first cubes distance is less than the second cubes
    if(cube1Dist < cube2Dist)
    {
        //Switch the order in which the cubes are drawn
        XMMATRIX tempMatrix = cube1World;
        cube1World = cube2World;
        cube2World = tempMatrix;
    }
    ///**************new**************

    //Set the WVP matrix and send it to the constant buffer in effect file
    WVP = cube1World * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    ///**************new**************
    //Counter clockwise culling first because we need the back side of
    //the cube to be rendered first, so the front side can blend with it
    d3d11DevCon->RSSetState(CCWcullMode);
    ///**************new**************
    //Draw the first cube
    d3d11DevCon->DrawIndexed( 36, 0, 0 );

    ///**************new**************
    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 36, 0, 0 );
    ///**************new**************

    WVP = cube2World * camView * camProjection;
    cbPerObj.WVP = XMMatrixTranspose(WVP);    
    d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
    d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
    d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
    d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

    ///**************new**************
    d3d11DevCon->RSSetState(CCWcullMode);
    ///**************new**************
    //Draw the second cube
    d3d11DevCon->DrawIndexed( 36, 0, 0 );

    ///**************new**************
    d3d11DevCon->RSSetState(CWcullMode);
    d3d11DevCon->DrawIndexed( 36, 0, 0 );
    ///**************new**************

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}

本节内容就到这里拉。

本节内容代码可以在我的Github找到

游戏开发路途遥远,但我相信只要坚持,总能到达彼岸!

如果我的文章对于你学习DirectX11有点帮助,欢迎评论给出建议,让我们一起学习进步!

                                                                                               ———————— 小明 2018.12.4 11.56

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值