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

10、混合

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

学习目标

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

10.6、alpha通道

源alpha分量可以用于在RGB混合的过程中控制像素的透明度,而混合方程中的源颜色实际上来自于像素着色器。在第九章中,我们将漫反射材质中的alpha分量作为纹理着色器的alpha分量输出,因此,我们可以利用漫反射图(diffuse map)中的alpha通道来控制混合过程中的透明度了。

float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap,pin.TexC)*gDiffuseAlbedo;
……
//从漫发射反照率获取alpha值的常用方法
litColor.a = diffuseAlbedo.a;
return litColor;
}

我们可以在常见的图像编辑软件中添加alpha通道,然后到处为支持alpha通道的格式,如DDS

10.7、裁剪像素

有些时候,我们希望彻底禁止某一个像素参与后续的处理,这时候我们可以通过HLSL中的内置函数clip(x)实现,该函数仅供像素着色器使用,当x < 0时,该像素将会不在参与后面的处理阶段,因此用这个函数来绘制透明和非透明相见的像素是最合适的了。(比如铁丝网)

在像素着色器中,我们将会采集像素的alpha分量,如果该值极小接近于0,则表明该像素是完全透明的,那么我们将会通过clip(x)函数将该像素彻底从后续的处理阶段中移除。

float4 PS(VertexOut pin) : SV_Traget
{
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap,pin.TexC)*gDiffuseAlbedo;
#ifdef ALPHA_TEXT
//如果alpha < 0.1f,则抛弃该像素,我们要在着色器中尽早执行该操作,以尽快检测处满足条件的像素并
//退出着色器,从而跳过后续的处理过程
clip(diffuseAlbedo.a - 0.1f);
#endif
……
//从漫发射反照率中获取alpha值的常用手段
litColor.a = diffuseAlbedo.a;
return litColor;
}

通过上述的代码可以看出,只有在定义了ALPHA_TEXT宏的时候我们才会执行透明像素的筛选。因为我们有时候可以不希望对某些渲染项执行clip(x)方法,所以我们要有能力针对特殊的着色器开启或关闭clip函数的调用,而且alpha测试的开销也不小,因此我们不能一直开着。

注意:通过混合操作也可以实现相同的效果,但是我们要优先使用clip函数:

  1. 无需执行混合运算(可以禁用混合运算)
  2. 处理期间不需要考虑绘制顺序的问题
  3. 通过提前从像素着色器筛选出要抛弃的像素,可以避免该像素参与后续的处理过程

10.8、雾

接下来我们将介绍如何实现雾化效果,实现雾化效果的流程如下:

  1. 指明雾气的颜色、由摄像机到雾气的最近距离以及雾气的分散范围
  2. 将网格三角形上点的颜色设置为原色于雾气颜色的加权平均值
\[foggedColor = litColor + s(fogColor - litColor) = (1-s)litColor + s·fogColor
\]

参数s的范围为[0 , 1]之间,由一个函数确定(该函数的参数为摄像机位置、被雾气覆盖物体表面点)。参数s的定义如下:

\[s = saturate((dist(p,E) - fogStart)/fogRange)
\]

其中saturate会将其参数限制在范围[0, 1]之间,dist(p, E)表示表面点p到摄像机位置E之间的距离。

当dist(p, E)小于fogStart的时候,雾色将不会改变物体顶点的本色,即s = 0;

\[foggedColor = litColor;
\]

当dist(p, E)大于fogEnd(fogStart + fogRange)时,雾色将会完全遮住物体,即s = 1;

\[foggedColor = fogColor;
\]

下面的代码将展示如何实现雾化效果,我们先计算距离,然后再像素层级进行插值,最后求出光照颜色。

// Default.hlsli文件

// 光照数量的默认值
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 3
#endif #ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif #ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif // 包含光照所用的结构体和函数
#include "LightingUtil.hlsli" Texture2D gDiffuseMap : register(t0); SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5); // 每一帧都在变化的常量数据
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
}; // 绘制过程中所使用的杂项常量数据
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight; // 允许应用程序在每一帧都能改变雾化效果的参数
// 比如我们可能只在一天中的特定时间才使用雾化效果
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerObjectPad2; Light gLights[MaxLights];
}; //每种材质中不同的常量数据
cbuffer cbMaterial : register(b2)
{
//漫反射反照率
float4 gDiffuseAlbedo;
//介质的一种属性
float3 gFresnelR0;
//粗造程度
float gRoughness;
//材质变换矩阵
float4x4 gMatTransform;
}; struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
}; struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
};
// VS.hlsl文件

#include "Default.hlsli"

VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f; //将顶点变换到世界空间
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosW = posW.xyz; //假设进行的是等比变换,否则要使用世界矩阵的逆转置矩阵
vout.NormalW = mul(vin.NormalL, (float3x3) gWorld); //将顶点变换到齐次裁剪空间
vout.PosH = mul(posW, gViewProj); //为三角形插值输出顶点属性
float4 texC = mul(float4(vin.TexC, 1.0f, 1.0f), gTexTransform);
vout.TexC = mul(texC, gMatTransform).xy; return vout;
}
// PS.hlsl文件

#include "Default.hlsli"

float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo; //alpha通道测试
#ifndef ALPHA_TEXT
clip(diffuseAlbedo.a - 0.1f);
#endif //对法线插值可能导致其非规范化,所以需要重新对他进行规范化处理
pin.NormalW = normalize(pin.NormalW); //光线经过物体表面一点反射到观察点这一方向上的向量
float3 toEyeW = gEyePosW - pin.PosW;
float distToEye = length(toEyeW);
//规范化处理
toEyeW /= distToEye; float4 ambient = gAmbientLight * diffuseAlbedo; const float shininess = 1.0f - gRoughness;
Material mat = { diffuseAlbedo, gFresnelR0, shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat, pin.PosW, pin.NormalW, toEyeW, shadowFactor); float4 litColor = ambient + directLight; #ifndef FOG
float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
litColor = lerp(litColor, gFogColor, fogAmount);
#endif //从漫反射反照率中获取alpha常用的手段
litColor.a = diffuseAlbedo.a; return litColor; }

在演示程序中,我们通过向CompileShader函数提供下列D3D_SHADER_MACRO结构体来开启雾化效果

const D3D_SHADER_MACRO defines[] =
{
"FOG","1",
NULL,NULL
};
mShadersp["opaquePS"] = d3dUtil::CompileShader(L"HLSL\\PS.hlsl",defines,"PS","ps_5_1");

演示程序运行效果:

10.9、小结

  1. 混合是一种将档期那光栅化的像素(源像素)与之前的已经完成关光栅化的像素并存在后台缓冲区中的像素(目标像素)相互融合的技术,混合使我们可以渲染出半透明效果的物体,比如水和玻璃
  2. RGB分量和alpha分量的混合运算是各自单独展开的,目的是希望可以扩展出更多效果
  3. 混合因子是混合方程可以自定义的法宝,这些混合因子是枚举类型D3D12_BLEND中的成员之一。注意,对于alpha分量来说,不可以使用以COLOR为后缀的混合因子
  4. 源alpha的信息来自于漫发射材质,在外面自己编写的应用程序框架中,漫反射材质由纹理图来定义,而源alpha就是纹理图中的alpha通道
  5. 通过HLSL的内部函数clip(x)可以将源像素从后续的处理过程中完全屏蔽掉
  6. 可以使用雾化效果来模拟各种气象环境效果和大气透视效果,以此来掩饰远处场景渲染的失真以及物体忽然出现在视锥体之中从而闯进用户视野的情况。

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. BUAA2020软工作业(五)——软件案例分析

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 软件案例分析作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方面 ...

  2. makedown笔记

    makedown语法 表格 这个表格的主题 |姓名|性别|年龄|职业| | ----- | ----- | ----- | ----- | |张三|男|34|码农| |李四|男|27|代驾| 这个表格 ...

  3. redis5集群搭建步骤

    通常情况下为了redis的高可用,我们一般不会使用redis的单实例去运行,一般都会搭建一个 redis 的集群去运行.此处记录一下 redis5 以后 cluster 集群的搭建. 一.需求 red ...

  4. 关于QGIS的插件开发(C++)

    关于C++插件的开发材料较少,根据网上的指导,我采用了早期版本的插件模板生成的方法来创建QGIS的插件,其方法是从以前版本(2.18.25)里面拷贝插件模板的方法进行,具体的执行步骤为 1.拷贝文件 ...

  5. 必备的60个常用的Linux命令

    Linux必学的60个命令Linux提供了大量的命令,利用它可以有效地完成大量的工 作,如磁盘操作.文件存取.目录操作.进程管理.文件权限设定等.所以,在Linux系统上工作离不开使用系统提供的命令. ...

  6. C++STL(set……)

    set 底层实现是用红黑树. set 建立 set<int> s; // 不可重,默认升序 set<int,less> s; // 不可重,升序 set<int,grea ...

  7. Shadertoy 教程 Part 3 - 矩形和旋转

    Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...

  8. 便宜的回文串(区间DP)

    题目链接:便宜的回文串 这道题刚开始其实还是没有思路的.没办法,只能看题解了... 其实我们在思考问题时,考虑到一段串增或减时会改变它的长度,所以转移时会麻烦... 但其实不用考虑那么多的问题,我们只 ...

  9. 开发笔记-----Ajax 基础使用

    一.GET 方式的用法: 1 <!--html --> 2 <div class="layui-form"> 3 <div class="l ...

  10. sudo user1账号获得管理员root的权限

    user1虽然有sudo权限,但不是真正的root权限,修改内核参数之类的就做不了 但是有sudo权限就可以添加账号,以下添加了admin账号与root账号一样的权限 useradd -u 0   - ...