环境:VS2017 语言:C++
这一章主要对应红龙书的第九章。
工程地址:https://github.com/anguangzhihen/Dx11。主要以Chapter 9_1 Blend Demo作为讲解的基础。
本次的文章主要讲解三个知识点:Blend混合、Clip剪裁、Fog雾的实现。
首先我们不看Dx11中的Blend,我从Unity的常用的混合公式开始说起。
为什么要混合呢?在不透明的世界中混合是没有必要,但是一旦涉及到透明的物体玻璃、水瓶等,就要考虑到当前物体和背景物体是如何进行混合处理的。
最常用的公式:Blend SrcAlpha OneMinusSrcAlpha。
意思就是当前物体颜色乘以物体Alpha值,而背景的颜色乘以1减去物体Alpha值,再将两者相加就获得了混合后的结果。
还有一种常用的是在特效上使用的颜色叠加效果公式:Blend SrcAlpha One。
我们来看一下本章能完成的效果:
上述效果图中水和山就是使用Alpha混合。
关键代码:
// 创建混合状态 D3D11_BLEND_DESC transparentDesc = { 0 }; transparentDesc.AlphaToCoverageEnable = false; transparentDesc.IndependentBlendEnable = false; transparentDesc.RenderTarget[0].BlendEnable = true; transparentDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; transparentDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; transparentDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; transparentDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; transparentDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; transparentDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; transparentDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; HR(device->CreateBlendState(&transparentDesc, &TransparentBS)); // DrawScene中进行混合 float blendFactor[] = { 0.0f, 0.0f, 0.0f, 0.0f }; md3dImmediateContext->OMSetBlendState(RenderStates::TransparentBS, blendFactor, 0xffffffff); md3dImmediateContext->DrawIndexed(3 * mWaves.TriangleCount(), 0, 0); md3dImmediateContext->OMSetBlendState(0, blendFactor, 0xffffffff);
我们可以看到类似Unity中的混合公式,没错一模一样。
D3D11_BLEND_DESC:
1.AlphaToCoverageEnable,在开启Multi-sample多重采样时,在透明和不透明之间达到抗锯齿的效果;
2.IndependentBlendEnable,Dx11支持8张RenderTarget的渲染,如果该值为true,则对于每张RenderTarget的渲染单独制定其描述,否则所有RenderTarget都使用第一个描述。
3.RenderTarget,指定描述,即混合公式(唯一要注意的是RenderTargetWriteMask参数可以指定输出颜色)。
ID3D11DeviceContext::OMSetBlendState:
1.pBlendState,设置想要的混合状态;
2.BlendFactor,在混合公式中使用到了D3D11_BLEND_BLEND_FACTOR或D3D11_BLEND_INV_BLEND_FACTOR时作为参考值;
3.SampleMask,强制指定需要的多重采样(如果将第5个bit位改为0,则第5个采样会被禁用,当然此时你必须要有5个以上的采样才行,只有4或以下是没有效果的)。
我们看到效果图中的箱子是中空的,这种效果在片元着色器中使用到了clip技术,判断舍弃Alpha值在0.1以下的像素点。
代码:
// clip透明的部分 bool gAlphaClip = true; if (gAlphaClip) { clip(texColor.a - 0.1f); }
如果clip中的表达式小于0,则会强制舍弃该像素点,并且直接跳过之后代码的执行,所以clip的判断越早执行Shader运行的效率越高。
这种雾的实现非常简单,是一种类似屏幕的后处理效果,实际场景中是没有具体的雾的。
按照距离,如果物体离相机越远,则该物体混合的雾颜色越多。
具体的计算公式:foggedColor = (1-s)·litColor+s·fogColor。
其中s的计算:s=saturate((dist(p,E)-fogStart)/fogRange)。(saturate相当于clamp(0,1),dist(p,E)代表相机和像素的距离)
让我们来看看具体的代码:
// 雾 bool gFogEnabled = true; if (gFogEnabled) { float fogLerp = saturate((distToEye - gFogPos.x) / gFogPos.y); litColor = lerp(litColor, gFogColor, fogLerp); }