前言

当使用加法/减法/乘法颜色混合,或者使用透明混合的时候,在经过深度测试时可能会引发一些问题。例如现在我们需要使用加法混合来绘制一系列对象,而这些对象彼此之间不会相互阻挡。若我们仍使用原来的深度测试,就必须保证某一像素下的所有片元需要按照从远到近的顺序来进行绘制,但这很难做到,尤其在绘制一些几何体的时候可能会出现在前面的像素片元挡住了后面的像素片元的情况。现在,我们有能力通过调整深度测试这一行为,来改变最终的显示结果。

在此之前需要额外了解的章节如下:

章节回顾
11 混合状态与光栅化状态
12 深度/模板状态、反射绘制
13 动手实现一个简易Effects框架、阴影效果绘制

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

交换律

加法/减法/乘法的混合运算是满足交换律的(对C0到Cn-1来说),这说明我们绘制像素的先后顺序应该是不会对结果产生影响的:

\[\mathbf{B'}=\mathbf{B}+\mathbf{C_0}+\mathbf{C_1}+...+\mathbf{C_{n-1}} \\
\mathbf{B'}=\mathbf{B}-\mathbf{C_0}-\mathbf{C_1}-...-\mathbf{C_{n-1}} \\
\mathbf{B'}=\mathbf{B} \otimes \mathbf{C_0} \otimes \mathbf{C_1} \otimes ... \otimes \mathbf{C_{n-1}}
\]

但是混合的先后顺序会对结果有影响,所以不满足交换律(无论alpha值如何):

\[\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_0}) + 0.5\mathbf{C_1} = 0.25\mathbf{B} + 0.25\mathbf{C_0} + 0.5\mathbf{C_1} \\
\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_1}) + 0.5\mathbf{C_0} = 0.25\mathbf{B} + 0.25\mathbf{C_1} + 0.5\mathbf{C_0}
\]

深度测试

关闭深度测试

在某一个阶段关闭深度测试后,若某一像素位置有新的像素片元出现,那么该像素片元就会直接不经过深度测试来到后面的混合阶段。此时混合没开的话,该像素片元就会直接取代后备缓冲区的对应像素,此时需要按从后到前的顺序来绘制物体才能保证正确的显示效果。但关闭深度测试的一个主要用途是绘制场景内的一系列透明物体。当然,前提是这一堆透明物体中间没有不透明物体在阻挡,否则不透明物体后面的透明物也会被绘制出来。

开启深度测试但关闭深度写入

相比上面的方式,这是一种更为合理的做法。我们只需要在渲染的时候先绘制不透明物体,然后就可以按任意的顺序来绘制透明物体。这是因为当绘制透明物体的时候,若它前面有不透明物体阻挡,则不会通过深度测试。所以十分适合用于处理透明物体和不透明物体相互交叉的情况。

利用深度测试和模板测试来绘制闪电特效与其镜面

一个闪电动画实际上是由60张按顺序播放的位图构成:

然后在绘制的时候,每秒绘制60帧闪电动画,即1秒一个循环。切换下一帧的时候,只需要更换下一张纹理即可。

  1. // 更新闪电动画
  2. mBoltAnim.SetTexture(mBoltSRVs[currBoltFrame].Get());
  3. if (frameTime > 1.0f / 60)
  4. {
  5. currBoltFrame = (currBoltFrame + 1) % 60;
  6. frameTime -= 1.0f / 60;
  7. }
  8. frameTime += dt;

RenderStates类的变化

现在RenderStates类有如下可用的状态:

  1. class RenderStates
  2. {
  3. public:
  4. template <class T>
  5. using ComPtr = Microsoft::WRL::ComPtr<T>;
  6. static void InitAll(ID3D11Device * device);
  7. // 使用ComPtr无需手工释放
  8. public:
  9. static ComPtr<ID3D11RasterizerState> RSWireframe; // 光栅化器状态:线框模式
  10. static ComPtr<ID3D11RasterizerState> RSNoCull; // 光栅化器状态:无背面裁剪模式
  11. static ComPtr<ID3D11RasterizerState> RSCullClockWise; // 光栅化器状态:顺时针裁剪模式
  12. static ComPtr<ID3D11SamplerState> SSLinearWrap; // 采样器状态:线性过滤
  13. static ComPtr<ID3D11SamplerState> SSAnistropicWrap; // 采样器状态:各项异性过滤
  14. static ComPtr<ID3D11BlendState> BSNoColorWrite; // 混合状态:不写入颜色
  15. static ComPtr<ID3D11BlendState> BSTransparent; // 混合状态:透明混合
  16. static ComPtr<ID3D11BlendState> BSAlphaToCoverage; // 混合状态:Alpha-To-Coverage
  17. static ComPtr<ID3D11BlendState> BSAdditive; // 混合状态:加法混合
  18. static ComPtr<ID3D11DepthStencilState> DSSWriteStencil; // 深度/模板状态:写入模板值
  19. static ComPtr<ID3D11DepthStencilState> DSSDrawWithStencil; // 深度/模板状态:对指定模板值的区域进行绘制
  20. static ComPtr<ID3D11DepthStencilState> DSSNoDoubleBlend; // 深度/模板状态:无二次混合区域
  21. static ComPtr<ID3D11DepthStencilState> DSSNoDepthTest; // 深度/模板状态:关闭深度测试
  22. static ComPtr<ID3D11DepthStencilState> DSSNoDepthWrite; // 深度/模板状态:仅深度测试,不写入深度值
  23. static ComPtr<ID3D11DepthStencilState> DSSNoDepthTestWithStencil; // 深度/模板状态:关闭深度测试,对指定模板值的区域进行绘制
  24. static ComPtr<ID3D11DepthStencilState> DSSNoDepthWriteWithStencil; // 深度/模板状态:仅深度测试,不写入深度值,对指定模板值的区域进行绘制
  25. };

加法混合

加法混合模式的创建如下:

  1. D3D11_BLEND_DESC blendDesc;
  2. ZeroMemory(&blendDesc, sizeof(blendDesc));
  3. auto& rtDesc = blendDesc.RenderTarget[0];
  4. blendDesc.AlphaToCoverageEnable = false;
  5. blendDesc.IndependentBlendEnable = false;
  6. rtDesc.BlendEnable = true;
  7. // 加法混合模式
  8. // Color = SrcColor + DestColor
  9. // Alpha = SrcAlpha
  10. rtDesc.SrcBlend = D3D11_BLEND_ONE;
  11. rtDesc.DestBlend = D3D11_BLEND_ONE;
  12. rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
  13. rtDesc.SrcBlendAlpha = D3D11_BLEND_ONE;
  14. rtDesc.DestBlendAlpha = D3D11_BLEND_ZERO;
  15. rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;
  16. HR(device->CreateBlendState(&blendDesc, BSAdditive.GetAddressOf()));

关闭深度测试

需要准备好默认情况下的绘制和指定模板值绘制两种情况:

  1. D3D11_DEPTH_STENCIL_DESC dsDesc;
  2. // 关闭深度测试的深度/模板状态
  3. // 若绘制非透明物体,务必严格按照绘制顺序
  4. // 绘制透明物体则不需要担心绘制顺序
  5. // 而默认情况下模板测试就是关闭的
  6. dsDesc.DepthEnable = false;
  7. dsDesc.StencilEnable = false;
  8. HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTest.GetAddressOf()));
  9. // 关闭深度测试
  10. // 若绘制非透明物体,务必严格按照绘制顺序
  11. // 绘制透明物体则不需要担心绘制顺序
  12. // 对满足模板值条件的区域才进行绘制
  13. dsDesc.StencilEnable = true;
  14. dsDesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
  15. dsDesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
  16. dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  17. dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
  18. dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  19. dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
  20. // 对于背面的几何体我们是不进行渲染的,所以这里的设置无关紧要
  21. dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  22. dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
  23. dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  24. dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
  25. HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTestWithStencil.GetAddressOf()));

允许深度测试,但不写入深度值的状态

同理也需要准备好默认绘制和指定模板值绘制两种情况:

  1. // 进行深度测试,但不写入深度值的状态
  2. // 若绘制非透明物体时,应使用默认状态
  3. // 绘制透明物体时,使用该状态可以有效确保混合状态的进行
  4. // 并且确保较前的非透明物体可以阻挡较后的一切物体
  5. dsDesc.DepthEnable = true;
  6. dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
  7. dsDesc.DepthFunc = D3D11_COMPARISON_LESS;
  8. dsDesc.StencilEnable = false;
  9. HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWrite.GetAddressOf()));
  10. // 进行深度测试,但不写入深度值的状态
  11. // 若绘制非透明物体时,应使用默认状态
  12. // 绘制透明物体时,使用该状态可以有效确保混合状态的进行
  13. // 并且确保较前的非透明物体可以阻挡较后的一切物体
  14. // 对满足模板值条件的区域才进行绘制
  15. dsDesc.StencilEnable = true;
  16. dsDesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
  17. dsDesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
  18. dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  19. dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
  20. dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  21. dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
  22. // 对于背面的几何体我们是不进行渲染的,所以这里的设置无关紧要
  23. dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
  24. dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
  25. dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
  26. dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
  27. HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWriteWithStencil.GetAddressOf()));

BasicEffect类的变化

下面四个方法都是专门用于绘制闪电动画的,使用了加法混合。

BasicEffect::SetDrawBoltAnimNoDepthTest方法

该方法关闭了深度测试,用于绘制闪电动画(但默认并不是使用这个,你需要自行修改代码替换调用来查看区别)

  1. void BasicEffect::SetDrawBoltAnimNoDepthTest(ID3D11DeviceContext * deviceContext)
  2. {
  3. deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
  4. deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
  5. deviceContext->RSSetState(RenderStates::RSNoCull.Get());
  6. deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
  7. deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
  8. deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTest.Get(), 0);
  9. deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
  10. }

BasicEffect::SetDrawBoltAnimNoDepthWrite方法

该方法允许深度测试,但关闭深度值写入,用于绘制闪电动画(在程序中默认使用这种模式)

  1. void BasicEffect::SetDrawBoltAnimNoDepthWrite(ID3D11DeviceContext * deviceContext)
  2. {
  3. deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
  4. deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
  5. deviceContext->RSSetState(RenderStates::RSNoCull.Get());
  6. deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
  7. deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
  8. deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWrite.Get(), 0);
  9. deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
  10. }

BasicEffect::SetDrawBoltAnimNoDepthTestWithStencil方法

该方法关闭了深度测试,用于绘制镜面区域的闪电动画(默认不使用这种模式)

  1. void BasicEffect::SetDrawBoltAnimNoDepthTestWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef)
  2. {
  3. deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
  4. deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
  5. deviceContext->RSSetState(RenderStates::RSNoCull.Get());
  6. deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
  7. deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
  8. deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTestWithStencil.Get(), stencilRef);
  9. deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
  10. }

BasicEffect::SetDrawBoltAnimNoDepthWriteWithStencil方法

该方法开启深度测试,但不允许写入深度值,用于绘制镜面区域的闪电动画(默认使用这种模式)

  1. void BasicEffect::SetDrawBoltAnimNoDepthWriteWithStencil(ID3D11DeviceContext * deviceContext, UINT stencilRef)
  2. {
  3. deviceContext->IASetInputLayout(pImpl->m_pVertexLayout3D.Get());
  4. deviceContext->VSSetShader(pImpl->m_pVertexShader3D.Get(), nullptr, 0);
  5. deviceContext->RSSetState(RenderStates::RSNoCull.Get());
  6. deviceContext->PSSetShader(pImpl->m_pPixelShader3D.Get(), nullptr, 0);
  7. deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
  8. deviceContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWriteWithStencil.Get(), stencilRef);
  9. deviceContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
  10. }

场景绘制

现在的场景绘制可以说算是比较复杂的了,需要同时处理透明物体、非透明物体的绘制,以及绘制镜面和阴影的效果。因此严格的按照正确顺序去绘制在这里就变得十分重要。

第1步: 镜面区域写入模板缓冲区

和之前一样,先标记好镜面区域:

  1. // *********************
  2. // 1. 给镜面反射区域写入值1到模板缓冲区
  3. //
  4. m_BasicEffect.SetWriteStencilOnly(m_pd3dImmediateContext.Get(), 1);
  5. m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第2步:绘制不透明的反射物体

  1. // ***********************
  2. // 2. 绘制不透明的反射物体
  3. //
  4. // 开启反射绘制
  5. m_BasicEffect.SetReflectionState(true); // 反射开启
  6. m_BasicEffect.SetRenderDefaultWithStencil(m_pd3dImmediateContext.Get(), 1);
  7. m_Walls[2].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  8. m_Walls[3].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  9. m_Walls[4].Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  10. m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  11. m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第3步:绘制不透明反射物体的阴影

  1. // ***********************
  2. // 3. 绘制不透明反射物体的阴影
  3. //
  4. m_WoodCrate.SetMaterial(m_ShadowMat);
  5. m_BasicEffect.SetShadowState(true); // 反射开启,阴影开启
  6. m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 1);
  7. m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  8. // 恢复到原来的状态
  9. m_BasicEffect.SetShadowState(false);
  10. m_WoodCrate.SetMaterial(m_WoodCrateMat);

第4步:绘制需要混合的反射闪电动画和透明物体

  1. // ***********************
  2. // 4. 绘制需要混合的反射闪电动画和透明物体
  3. //
  4. m_BasicEffect.SetDrawBoltAnimNoDepthWriteWithStencil(m_pd3dImmediateContext.Get(), 1);
  5. m_BoltAnim.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  6. m_BasicEffect.SetReflectionState(false); // 反射关闭
  7. m_BasicEffect.SetRenderAlphaBlendWithStencil(m_pd3dImmediateContext.Get(), 1);
  8. m_Mirror.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第5步:绘制不透明的正常物体

  1. // ************************
  2. // 5. 绘制不透明的正常物体
  3. //
  4. m_BasicEffect.SetRenderDefault(m_pd3dImmediateContext.Get());
  5. for (auto& wall : m_Walls)
  6. wall.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  7. m_Floor.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  8. m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

第6步:绘制不透明正常物体的阴影

  1. // ************************
  2. // 6. 绘制不透明正常物体的阴影
  3. //
  4. m_WoodCrate.SetMaterial(m_ShadowMat);
  5. m_BasicEffect.SetShadowState(true); // 反射关闭,阴影开启
  6. m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 0);
  7. m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
  8. m_BasicEffect.SetShadowState(false); // 阴影关闭
  9. m_WoodCrate.SetMaterial(m_WoodCrateMat);

第7步:绘制需要混合的闪电动画

  1. // ************************
  2. // 7. 绘制需要混合的闪电动画
  3. m_BasicEffect.SetDrawBoltAnimNoDepthWrite(m_pd3dImmediateContext.Get());
  4. m_BoltAnim.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);

最终动画效果如下:

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--14 深度测试的更多相关文章

  1. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

  3. DirectX11 With Windows SDK--11 混合状态与光栅化状态

    前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...

  4. DirectX11 With Windows SDK--11 混合状态

    原文:DirectX11 With Windows SDK--11 混合状态 前言 这一章会着重讲述混合状态,在下一章则会讲述深度/模板状态 DirectX11 With Windows SDK完整目 ...

  5. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的 ...

  6. DirectX11 With Windows SDK--29 计算着色器:内存模型、线程同步;实现顺序无关透明度(OIT)

    前言 由于透明混合在不同的绘制顺序下结果会不同,这就要求绘制前要对物体进行排序,然后再从后往前渲染.但即便是仅渲染一个物体(如上一章的水波),也会出现透明绘制顺序不对的情况,普通的绘制是无法避免的.如 ...

  7. DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用

    前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用. ...

  8. DirectX11 With Windows SDK--23 立方体映射:动态天空盒的实现

    前言 上一章的静态天空盒已经可以满足绝大部分日常使用了.但对于自带反射/折射属性的物体来说,它需要依赖天空盒进行绘制,但静态天空盒并不会记录周边的物体,更不用说正在其周围运动的物体了.因此我们需要在运 ...

  9. DirectX11 With Windows SDK--01 DirectX11初始化

    前言 由于个人觉得龙书里面第4章提供的Direct3D 初始化项目封装得比较好,而且DirectX SDK Samples里面的初始化程序过于精简,不适合后续使用,故选择了以Init Direct3D ...

随机推荐

  1. SSIS的部署和配置

    参考:http://www.cnblogs.com/JasonLiao/p/SSISDeploy.htmlhttps://msdn.microsoft.com/en-us/library/ms1401 ...

  2. 使用C++对物理网卡/虚拟网卡进行识别(包含内外网筛选)

    简介 在Socket编程的时候,我们需要实时获取我们所需要的IP地址.例如在编写后门的时候,我们可能需要获得有效的外网IP或内网IP:有时候我们可能需要判断我们获取的是否是虚拟机网卡,这时候就需要对每 ...

  3. Bootstrap -- 表格样式、表单布局

    Bootstrap -- 表格样式.表单布局 1. 表格的一些样式 举例: <!DOCTYPE html> <html> <head> <meta http- ...

  4. Django 【orm】或

    方式一: q=Q() q.connection="or" q.children.append(("pk",1)) q.children.append((&quo ...

  5. redis常用命令大全

    1.基于内存的key-value数据库 2.基于c语言编写的,可以支持多种语言的api //set每秒11万次,取get 81000次 3.支持数据持久化 4.value可以是string,hash, ...

  6. 【转】在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()

    在子线程中new一个Handler为什么会报以下错误? java.lang.RuntimeException:  Can't create handler inside thread that has ...

  7. ubuntu安装docker-ce

    Docker Community Edition (CE)/Docker社区版非常适合希望开始使用Docker并尝试使用基于容器的应用程序的开发人员和小型团队.Docker CE有2个版本:stabl ...

  8. Golang 入门系列(三)Go语言基础知识汇总

    前面已经了 Go 环境的配置和初学Go时,容易遇到的坑,大家可以请查看前面的文章 https://www.cnblogs.com/zhangweizhong/category/1275863.html ...

  9. C#帮助类:将List转换成Datatable

    public class ListToDatatable { public static DataTable ToDataTable <T> (List <T> items) ...

  10. kafka原理深入研究 (转 )

    一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险. ...