今天我们学习一下如何给图元增加一点混合效果,这样看起来会更透明。
混合等式
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