仅供个人学习使用,请勿转载。谢谢!

10、混合

本章将研究混合技术,混合技术可以让我们将当前需要光栅化的像素(也称为源像素)和之前已经光栅化到后台缓冲区的像素(也称为目标像素)进行融合。因此,该技术可以用来渲染如水和玻璃之类的半透物体。

学习目标

  1. 理解混合技术的工作原理,并且在Direct3D中运用该技术
  2. 学习Direct3D所支持的不同混合模式
  3. 探究如何使用alpha分量来调节图元的透明度
  4. 学会仅通过调用HLSL中的clip函数来阻止向后台缓冲区中绘制像素

10.1、混合方程

设C为像素着色器输出的当前正在光栅化的第i行、第j列像素(源像素)的颜色值,D为目前在后台缓冲区中与之对应的第i行、第j列像素(目标像素)的颜色值。如果不使用混合技术,C将会直接覆盖D,从而使后台缓冲区中第i行、第j列的像素更新为新的颜色值C,如果使用混合技术,C将会和D混合成新的颜色值再去覆盖D。

Direct3D使用下列混合方程来使源像素和目标像素融合:

\[new = C·F1*D·F2
\]

其中F1和F2分别为源混合因子目标混合因子,我们将会在10.3节介绍,运算符 · 表示针对颜色向量而定义的分量式乘法,* 表示将会在10.2节介绍的二元运算符。

上述混合方程仅用于RGB分量,而alpha分量由另一条混合方程处理。这里不介绍了。两条混合方程的本质是一样的,之所以分开处理RGB分量和alpha分量主要是希望可以由此产生更多不同的混合效果

10.2、混合运算

下列枚举项将会用作混合方程中的二元运算符 * :

typedef enum D3D12_BLEND_OP
{
D3D12_BLEND_OP_ADD = 1, //加
D3D12_BLEND_OP_SUBTRACT = 2, //源像素·源混合因子-目标像素·目标混合因子
D3D12_BLEND_OP_REV_SUBTRACT = 3, //减数和被减数调换位置
D3D12_BLEND_OP_MIN = 4, //取最小值
D3D12_BLEND_OP_MAX = 5 //取最大值
}D3D12_BLEND_OP;

上述二元运算符也可以用于alpha分量的混合运算。

Direct3D最近几个版本开始加入了一项新的特性,即通过逻辑运算符对源颜色和目标颜色进行混合,用以取代传统的混合方程。这些逻辑运算符如下:

typedef enum D3D12_LOGIC_OP
{
D3D12_LOGIC_OP_CLEAR = 0,
D3D12_LOGIC_OP_SET = ( D3D12_LOGIC_OP_CLEAR + 1 ) ,
D3D12_LOGIC_OP_COPY = ( D3D12_LOGIC_OP_SET + 1 ) ,
D3D12_LOGIC_OP_COPY_INVERTED = ( D3D12_LOGIC_OP_COPY + 1 ) ,
D3D12_LOGIC_OP_NOOP = ( D3D12_LOGIC_OP_COPY_INVERTED + 1 ) ,
D3D12_LOGIC_OP_INVERT = ( D3D12_LOGIC_OP_NOOP + 1 ) ,
D3D12_LOGIC_OP_AND = ( D3D12_LOGIC_OP_INVERT + 1 ) ,
D3D12_LOGIC_OP_NAND = ( D3D12_LOGIC_OP_AND + 1 ) ,
D3D12_LOGIC_OP_OR = ( D3D12_LOGIC_OP_NAND + 1 ) ,
D3D12_LOGIC_OP_NOR = ( D3D12_LOGIC_OP_OR + 1 ) ,
D3D12_LOGIC_OP_XOR = ( D3D12_LOGIC_OP_NOR + 1 ) ,
D3D12_LOGIC_OP_EQUIV = ( D3D12_LOGIC_OP_XOR + 1 ) ,
D3D12_LOGIC_OP_AND_REVERSE = ( D3D12_LOGIC_OP_EQUIV + 1 ) ,
D3D12_LOGIC_OP_AND_INVERTED = ( D3D12_LOGIC_OP_AND_REVERSE + 1 ) ,
D3D12_LOGIC_OP_OR_REVERSE = ( D3D12_LOGIC_OP_AND_INVERTED + 1 ) ,
D3D12_LOGIC_OP_OR_INVERTED = ( D3D12_LOGIC_OP_OR_REVERSE + 1 )
}D3D12_LOGIC_OP;

注意点:我们不同同时使用传统混合方程和逻辑运算符这两种混合手段,我们只能二选一。如果我们要使用逻辑运算符混合技术,就一定要选择它所支持的渲染目标格式——UNIT(无符号整数),否则会报错

10.3、混合因子

通过为源混合因子和目标混合因子设置不同的混合运算符,我们就可以实现各种各样的混合效果。这里就不列举混合因子了,如果有兴趣的可以查看SDK文档中的D3D12_BLEND枚举类型

10.4、混合状态

前面已经介绍过混合运算符和混合因子了,那么我们要如何使用Direct3D来设置这些数值?

答:混合状态也是PSO(流水线状态对象)的一部分,只不过我们之前没有去设置它,即没有开启混合技术。

opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);

为了配置非默认混合状态,我们必须填写D3D12_BLEND_DESC结构体,该结构体的定义如下:

typedef struct D3D12_BLEND_DESC{
BOOL AlphaToCoverageEnable; //默认为false
BOOL IndependentBlendEnable; //默认为false
D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[ 8 ];
}

参数介绍:

  1. AlphaToCoverageEnable:如果指定为true,则启用alpha-to-coverage功能,这是一种在渲染叶片或者门等纹理时极其有用的一种多重采样技术。(开启此项技术需要开启多重采样)
  2. IndependentBlendEnable:Direct3D最多同时支持8个渲染目标,如果该标志被设置为ture,则表明可以向每一个渲染目标执行不同的混合操作,如果该标志为false,则每个渲染目标将会采用D3D12_BLEND_DESC::RenderTarget数组中第一个元素所描述的方式进行混合操作。(多渲染目标技术常用于高级算法,我们现在每次仅向一个渲染目标进行绘制)
  3. RenderTarget:具有8个D3D12_RENDER_TARGET_BLEND_DESC元素的数组,其中第i个元素描述了如何对第i个渲染目标进行混合处理。

结构体D3D12_RENDER_TARGET_BLEND_DESC结构体的定义如下:

typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; //是否开启常规混合运算
BOOL LogicOpEnable; //是否开启逻辑混合运算(常规混合运算和逻辑混合运算只能二选一)
D3D12_BLEND SrcBlend; //指定RGB混合运算中的源混合因子
D3D12_BLEND DestBlend; //指定RGB混合运算中的目标混合因子
D3D12_BLEND_OP BlendOp; //指定RGB混合运算中的混合运算符
D3D12_BLEND SrcBlendAlpha; //指定alpha混合运算中的源混合因子
D3D12_BLEND DestBlendAlpha; //指定alpha混合运算中的目标混合因子
D3D12_BLEND_OP BlendOpAlpha;//指定alpha混合运算中的混合运算符
D3D12_LOGIC_OP LogicOp; //指定源颜色和目标颜色所使用的逻辑运算符
UINT8 RenderTargetWriteMask;//控制混合后的数据可以被写入后台缓冲区中的那些颜色通道
}D3D12_RENDER_TARGET_BLEND_DESC;

下面的代码将会展示如何创建和设置混合状态:

//创建开启混合功能的PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc; D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));

和其他的PSO一一昂,我们应该在应用程序的初始化期间创建他们,然后根据需求通过ID3D12GraphicsCommandList::SetPipelineState方法在不同的状态之间来回切换。

10.5、混合示例

在下面的各个小结中,我们将讨论一些用于获取特效的混合因子组合,在这些示例中,我们暂时只关注RGB分量,不考虑alpha分量。(RGB分量和alpha分量的处理方式类似)

10.5.1、禁止颜色的写操作

如果希望原始的目标像素保持不变,即不对目标像素进行覆盖,也不和目标像素进行混合。为了实现这个功能,我们只需要把源混合因子设置为D3D12_BLEND_ZERO,将目标混合因子设置为D3D12_BLEND_ONE,在将混合运算符设置为D3D12_BLEND_OP_DAA即可,此时混合方程为:

\[new = C·(0,0,0)+D·(1,,1,1)
\]

然而实际上我们有一个更简便的方法实现上述功能,即将成员D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask设置为0,便可以禁止向任何颜色通道执行写操作。

10.5.2、加法混合和减法混合

如果希望源像素和目标像素实现加法运算,我们可以将源混合因子和目标混合因子都设置为D3D12_BLEND_ONE,然后把混合运算符设置为D3D12_BLEND_OP_ADD即可,此时混合方程为

\[new = C·(1,1,1)+D·(1,,1,1)
\]

如果继续使用上述混合因子,但是将混合运算符替换为D3D12_BLEND_OP_SUBTRACT,我们便可以实现减法混合。

10.5.3、乘法混合

如果希望源像素和目标像素实现乘法运算,我们可以将源混合因子设置为D3D12_BLEND_ZERO,目标混合因子设置为D3D12_BLEND_SRC_COLOR,在将混合运算符设置为D3D12_BLEND_OP_ADD,此时混合方程为

\[new = C·(0,0,0)+D·C
\]

10.5.4、透明混合

源alpha分量a是一种可以控制源像素不透明度的百分比的分量,假设我们希望基于源像素的不透明度,将源像素和目标像素进行混合,为了实现此效果,我们可以将源混合因子设置为D3D12_BLEND_SRC_ALPHA,目标混合因子设置为D3D12_BLEND_INV_SRC_ALPHA,并且将混合运算符设置为D3D12_BLEND_OP_ADD,此时混合方程为:

\[new = C·(a,a,a)+D(1-a,1-a,1-a)
\]

例如,a = 0.25,则源像素的不透明度为25%,也就是说,源像素和目标像素进行混合的时候,最终颜色将会由25%的源像素和75%的目标像素组成(源像素会位于目标像素的后侧)。

如果使用上述透明混合方法,我们需要考虑物体的绘制顺序,首先要绘制无序混合处理的物体,然后根据混合物体到摄像机的距离对他们进行排序,最后按由远到近的顺序通过混合的方式依次绘制这些物体。因为每一个透明的物体都应该要可以看到它后面的物体,而进行混合运算时,每一个物体都会和其后所有的物体进行混合运算,因此我们需要将透明物体后面的物体的像素都事先写入后台缓冲区中,然后将透明物体的源像素与其后面的目标像素进行混合。

105.5、混合与深度缓冲区

在使用加法/减法/乘法运算进行混合时,会涉及到深度测试这一问题,这里将会通过加法混合进行讲解。

如果要使用加法混合来渲染一个物体集合S,且不希望S中的物体相互遮挡,这时我们不希望在S中开启深度测试,如果在S中开启深度测试,且没有从后到前进行绘制,那么S中的俩个物体如果存在遮挡,靠后的像素片段将会被丢弃,这意味该物体的像素颜色将不会被累加到混合求和的结果中。

为了解决上面的问题,我们可以在渲染S中的物体的时候,通过禁止向深度缓冲区的写操作来禁用S中的物体之间的加法测试,因为向深度缓冲区的写操作被禁止之后,物体的深度信息将不会写入深度缓冲区中,从而避免了深度测试。

DirectX12 3D 游戏开发与实战第十章内容(上)的更多相关文章

  1. DirectX12 3D 游戏开发与实战第十章内容(下)

    仅供个人学习使用,请勿转载.谢谢! 10.混合 本章将研究混合技术,混合技术可以让我们将当前需要光栅化的像素(也称为源像素)和之前已经光栅化到后台缓冲区的像素(也称为目标像素)进行融合.因此,该技术可 ...

  2. DirectX12 3D 游戏开发与实战第八章内容(下)

    DirectX12 3D 游戏开发与实战第八章内容(下) 8.9.材质的实现 下面是材质结构体的部分代码: // 简单的结构体来表示我们所演示的材料 struct Material { // 材质唯一 ...

  3. DirectX12 3D 游戏开发与实战第八章内容(上)

    8.光照 学习目标 对光照和材质的交互有基本的了解 了解局部光照和全局光照的区别 探究如何用数学来描述位于物体表面上某一点的"朝向",以此来确定入射光照射到表面的角度 学习如何正确 ...

  4. DirectX12 3D 游戏开发与实战第九章内容(上)

    仅供个人学习使用,请勿转载. 9.纹理贴图 学习目标: 学习如何将局部纹理映射到网格三角形上 探究如何创建和启用纹理 学会如何通过纹理过滤来创建更加平滑的图像 探索如何使用寻址模式来进行多次纹理贴图 ...

  5. DirectX12 3D 游戏开发与实战第一章内容

    DirectX12 3D 第一章内容 学习目标 1.学习向量在几何学和数学中的表示方法 2.了解向量的运算定义以及它在几何学中的应用 3.熟悉DirectXMath库中与向量有关的类和方法 1.1 向 ...

  6. DirectX12 3D 游戏开发与实战第二章内容

    矩阵代数 学习目标 理解矩阵及其相关运算的定义 探究为何能把向量和矩阵的乘法视为一种线性组合 学习单位矩阵.转置矩阵.行列式以及矩阵的逆等概念 逐步熟悉DirectXMath库中提供的关于矩阵计算的类 ...

  7. DirectX12 3D 游戏开发与实战第九章内容(下)

    仅供个人学习使用,请勿转载.谢谢! 9.纹理贴图 学习目标 学习如何将局部纹理映射到网格三角形中 探究如何创建和启用纹理 学会如何通过纹理过滤来创建更加平滑的图像 探索如何使用寻址模式来进行多次贴图 ...

  8. DirectX12 3D 游戏开发与实战第五章内容

    渲染流水线 学习目标: 了解用于在2D图像中表现出场景立体感和空间深度感等真实效果的关键因素 探索如何用Direct3D表示3D对象 学习如何建立虚拟摄像机 理解渲染流水线,根据给定的3D场景的几何描 ...

  9. DirectX12 3D 游戏开发与实战第四章内容(上)

    Direct3D的初始化(上) 学习目标 了解Direct3D在3D编程中相对于硬件所扮演的角色 理解组件对象模型COM在Direct3D中的作用 掌握基础的图像学概念,例如2D图像的存储方式,页面翻 ...

随机推荐

  1. SpringCloud微服务实战——搭建企业级开发框架(六):使用knife4j集成Swagger2接口文档

    knife4j是为集成Swagger生成api文档的增强解决方案,前后端Java代码以及前端Ui模块进行分离,在微服务架构下使用更加灵活, 提供专注于Swagger的增强解决方案,不同于只是改善增强前 ...

  2. MarkDown之Typora使用

    Typora:所见即所得 常用快捷键 加粗:ctrl + B 标题:ctrl + 16,对于与16级标题 插入公式:ctrl + Shift + m 插入代码:ctrl + Shift + K 插入图 ...

  3. luogu P4243 [JSOI2009]等差数列 题解

    前言: 这题真ex... 强烈谴责在题解里面放毒瘤题链接的屑出题人! 吐 ️ 解析: 这题分成两步走. 首先,既然题目中的修改操作是区间加等差数列,那么就容易想到在差分数组上进行操作. 然后就是相当恶 ...

  4. web性能检测工具lighthouse

    About Automated auditing, performance metrics, and best practices for the web. Lighthouse 可以自动检查Web页 ...

  5. jquery正则表达式验证【是否带有小数、是否中文名称组成、是否全由8位数字组成、电话码格式、邮件地址】

    1 <form name="myform" action="" onsubmit="return fun1()"> 2 < ...

  6. linux 内核源代码情景分析——i386 的页式内存管理机制

    可以看出,在页面目录中共有210 = 1024个目录项,每个目录项指向一个页面表,而在每个页面表中又共有1024个页面描述项. 由图看出来,从线性地址到物理地址的映射过程为: 1)从CR3取得页面目录 ...

  7. Serverless 工程实践|自建 Apache OpenWhisk 平台

    作者 | 刘宇(江昱) 前言:OpenWhisk 是一个开源.无服务器的云平台,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构. OpenWhisk 简介 Open ...

  8. Spring Boot 快速整合Swagger

    一.前言 Spring Boot作为当前最为流行的Java web开发脚手架,越来越多的开发者选择用其来构建企业级的RESTFul API接口.这些接口不但会服务于传统的web端(b/s),也会服务于 ...

  9. Fiddler抓包工具简介:(三)手机端代理配置

    1.接入网络:需要在移动终端(手机或pad)上指定代理服务器为Fiddler所在主机的IP,端口默认为8888,要保证手机和安装有fiddler的电脑处在同一局域网内,手机能ping通电脑. [方法] ...

  10. 数组 & 对象 & 函数

    数组 数组也是一个对象,不同的是对象用字符串作为属性名,而数组用数字作为索引,数组的索引从0开始 创建数组: //方式一:构造器,可以在创建数组时指定 Var arr = new Array(1,2, ...