Windows 8 Directx 开发学习笔记(十二)利用混合实现浮在水面的木箱

在场景中绘制多个不透明物体时很简单,哪个物体离得近,看到的就是哪个物体。但如果加入一个透明的物体,像玻璃,如何渲染就有些麻烦。拿一块红色的玻璃挡住眼睛,看到的物体都偏红,换成蓝色的玻璃,物体都偏蓝。DirectX中的“混合(Blending)”技术可以解决这个问题。混合技术其实也不难,但是通过不同运算方式和系数的组合,它能实现很多效果。它的基本原理就是混合方程:


其中乘号表示向量对应元素相乘,C和A分别表示颜色和Alpha的混合结果,其它参数可以自由指定。详细的介绍可以看DirectX 10游戏编程入门,虽然版本不同,但是原理没有区别。继续拿玻璃做例子,个人感觉混合技术就像一个调色板,只有两种颜料,分别是玻璃的颜色Csrc和它后面对应位置的物体颜色Cdst,而最终的颜色C是两种颜料按不同比例混合后的颜色,其中Fsrc控制玻璃的颜料比例,Fdst控制物体的颜料比例。Alpha通道的混合原理与颜色混合类似,不过少了两个分量。

因为Csrc和Cdst随物体不同而变化,所以实际使用混合时每个方程需要指定三个参数:混合方式、Fsrc和Fdst。在DirectX 11中,与混合相关的结构体定义如下:

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

typedef struct D3D11_RENDER_TARGET_BLEND_DESC
{
    BOOL BlendEnable;
    D3D11_BLEND SrcBlend;   // Csrc
    D3D11_BLEND DestBlend;  // Cdst
    D3D11_BLEND_OP BlendOp; // 颜色混合方式
    D3D11_BLEND SrcBlendAlpha;  // Fsrc
    D3D11_BLEND DestBlendAlpha; // Fdst
    D3D11_BLEND_OP BlendOpAlpha;   // Alpha混合方式
UINT8 RenderTargetWriteMask;
}   D3D11_RENDER_TARGET_BLEND_DESC;

其他参数的说明可以参照MSDN

http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ff476087(v=vs.85).aspx

了解混合技术的原理之后就要着手将混合效果添加到上一篇文章中已实现的模型中去,给水面添加透明的效果,使它更真实些。首先从修改顶点着色器代码开始。打算将Alpha纹理一整张铺在水面上,不需要复制。所以在顶点着色器的输出中添加一个texa成员,用于保存Alpha纹理座标。

struct VertexShaderOutput
{
    float4 posH : SV_POSITION;
    float3 posW   : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
};

main方法中,正常纹理座标需要变换,但是Alpha纹理座标不需要。

// 纹理座标变换
output.tex = mul(float4(input.tex, 0.0f, 1.0f),texTransform).xy;
 
// Alpha纹理不进行变换
output.texa = input.tex;

然后就要修改像素着色器部分,像素着色器的输入自然与顶点着色器的输出对应。同时,像素着色器还要添加一个全局变量texAlpha存储Alpha纹理资源。

Texture2D texDiffuse : register(t0);
Texture2D texAlpha : register(t1);
 
struct PixelShaderInput
{
    float4 posH : SV_POSITION;
    float3 posW : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
};

注意texDiffusetexAlpha后面的register,它用来说明资源的存储位置。Alpha纹理的使用与普通纹理使用没有区别,不过这次需要用采样器采的是纹理的Alpha分量,用.a表示。

// 只有水模型会设置并使用Alpha纹理
// 绘制其他模型时不保证texAlpha一定有内容
// 不能实际应用
// 按原始透明度显示太透明所以乘4
finalColor.a = texAlpha.Sample(samplerLinear,input.texa).a * 4;      

接下来就是修改C++代码。需要改动的地方只有Renderer.h和Renderer.cpp两个文件而已。这次载入的Alpha纹理资源是dds格式的,所以向项目中添加DDSTextureLoader.h和DDSTextureLoader.cpp文件,并在Renderer.h中包含DDSTextureLoader.h。此外,还要在Renderer类中添加两个成员,用来保存Alpha纹理视图和混合状态。

Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_WaterAlphaSRV;
Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendState;

完成后就可以在CreateDeviceResources方法中的createTexTask部分添加Alpha纹理载入代码并初始化混合状态。

DX::ThrowIfFailed(
    CreateDDSTextureFromFile(
    m_d3dDevice.Get(),
    L"Texture/water_alpha.dds",
    &tex,
    m_WaterAlphaSRV.GetAddressOf()
    )
    );
 
// 初始化混合状态
D3D11_BLEND_DESC blendDesc = {0};
blendDesc.RenderTarget[0].BlendEnable = TRUE;
blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; // Color_Fsrc
blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; // Color_Fdst
blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; // Color_Operation
blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; // Alpha_Fsrc
blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; // Alpha_Fdst
blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; // Alpha_Operation
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
 
DX::ThrowIfFailed(
    m_d3dDevice->CreateBlendState(
    &blendDesc,
    &m_BlendState
    )
    );

主要说明下颜色混合的设置。颜色的混合参数Fsrc和Fdst的说明可以从MSDN找到

http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ff476086(v=vs.85).aspx

D3D11_BLEND_SRC_ALPHA

The blend factoris (As, As, As, As), that is alpha data (A) from a pixel shader. No pre-blendoperation.

D3D11_BLEND_INV_SRC_ALPHA

The blend factoris ( 1 - As, 1 - As, 1 - As, 1 - As), that is alpha data (A) from a pixelshader. The pre-blend operation inverts the data, generating 1 - A.

D3D11_BLEND_OP_ADD

Add source 1 andsource 2.

将其代入混合方程可得出实际的计算公式:


假设当前玻璃的透明度是0.2,那么最终的混合结果就是0.2份的玻璃颜色加上0.8份的物体颜色。创建好混合状态之后就能够使用了。不过这时要注意物体的绘制顺序,需要遵守下面的规则:

首先绘制不透明物体。然后,根据透明物体与摄像机之间的距离进行排序,按照从后向前的顺序绘制透明物体。因为混合时,透明物体要在不透明物体的前面,从而能够透过它看到后面的物体。如果这时不透明物体还没有画出来,那什么都看不到。所以,必须将透明物体后面的物体绘制出来后再进行混合。

这样,在Render方法中,要完成陆地渲染后再设置混合状态,并设置Alpha纹理,最后渲染水面,完成后清除混合状态。

// 设置水面纹理和Alpha纹理
m_d3dContext->PSSetShaderResources(
    0,  // 对应像素着色器中register(t0)
    1,
    m_WaterSRV.GetAddressOf()
    );
 
m_d3dContext->PSSetShaderResources(
    1,  // 对应像素着色器中register(t1)
    1,
    m_WaterAlphaSRV.GetAddressOf()
);
 
// 设置混合状态
FLOAT blendFactors[4] = { 0, };
m_d3dContext->OMSetBlendState(m_BlendState.Get(),blendFactors, 0xffffffff);
 
……
 
// 清除混合模式状态
m_d3dContext->OMSetBlendState(0, blendFactors,0xffffffff);

实际运行效果如下图:


图1是使用Alpha纹理贴图的效果,图2是统一水面的Alpha为0.6。蓝圈中是两图类似的部分,红圈则是不同部分,不是特别明显。

还可以在这个模型中加入之前实现的木箱,细节不用多说,注意要在绘制水面之前渲染木箱。效果如下图:

 

本篇文章的源代码:Direct3DApp_HillWaveBlend

原文地址:http://blog.csdn.net/raymondcode/article/details/8456714

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章