Deferred Decal(延迟贴花)

article/2025/8/29 1:57:11

Decal渲染是一个引擎中重要的一部分,记忆中印象最深刻的就是以前CS中的弹痕与爆炸痕迹了。目前来说,Decal的实现方法也比较多,而且感觉还跟游戏类型有关,比如子弹乱飞的射击类FPS游戏中对贴花系统的要求就比较高,因为本来Decal的变化就比较丰富。一般来说贴花渲染主要有两种实现方法:

  • Texture projection关于投影纹理的原理网上也有很多文章,基本就是需要对地形渲染两遍,一遍正常渲染,一遍用投影的纹理渲染,然后把这两个混合的结果融合。显然,如果场景中的贴花较多,且场景规模复杂的话,这种方法就会比较费。
  • Mesh projection 这种方法应该来说使用比较多,包括早期Source中的Decal等使用的应该就是这种方法,其主要原理就是将Decal处理为附着于其所能贴到的几何表面上的几何网格,然后当做polygon list来渲染。当然这些网格也是经过修饰的(比如贴到一个桌子的边角上时,就需要根据桌角几何网格的分布来裁剪Decal所对应的网格)。关于该方法这里有一篇文章介绍得非常好:http://blog.wolfire.com/2009/06/how-to-project-decals/。另外,基于此种方法的改进类型也不少,比如Frosbite引擎中就利用Geometry Shader来生成类似该类型的Decal(Shadows and Decals in the Frostbite Engine)。

结合最近普遍应用的后处理渲染流程,于是自然而然地也就出现了Deferred Decal,这里一个最基本的条件就是需要有一个基于延迟渲染的系统才能使用这种方法。关于Deferred decal的实现原理也比较简单,过程如下:

  1. 在后处理渲染中完成正常的G-Buffer渲染后,即可得到对应的Normal-depth buffer,以及Diffuse Buffer,但是此时仍没有进行光照计算,这时就需要将Decal给渲染到对应的Diffuse buffer中。为了实现Decal,这里需要在G-Buffer后再增加一个Pass,并使用Diffse buffer作为Render target,并使用Decal shader进行渲染,计算并更新Decal所能够影响到的那些Pixel的Diffuse。
  2. 为了确定每个Decal所能够影响到的范围以便更新相应的Diffuse buffer,一般需要绘制出Decal所对应的一个包围几何体,这里可以是Sphere或Box(例如Volume decal中使用的就是Sphere,CryEnigne中使用的就是Box),当然,如果不计代价还可以使用更复杂的形体。
  3. 最后,关键的一步就是计算需要Decal的那些Pixel所对应的Decal uv,这里根据包围体的不同方法也不同。Sphere的映射方法比较简单,可以直接将Pixel的世界坐标转到局部球体坐标即可,再根据对应的Volume texture或2D texture再做一次映射,然后访问纹理即可。如果使用Box的话可能就稍麻烦些,这时需要知道Decal box所对应的一个局部坐标系转化。这个Decal坐标系由以下几个量来确定:Decal所贴的位置,Decal贴到处的Normal,Decal的水平方向,Decal的大小变换;然后由这三个基本量来构靠世界到Decal局部的变换矩阵:
    float3 gDecalScale;
    float3 gDecalPosition;
    float3 gDecalNormal;
    float3 gDecalLRVector;
    float3 zAxis = normalize(gDecalNormal);
    float3 xAxis = normalize(gDecalLRVector);
    float3 yAxis = normalize(cross(xAxis , zAxis));
    float4x4 decalMatrix = 
    {
    {xAxis.x , yAxis.x , zAxis.x , 0.0f} ,
    {xAxis.y , yAxis.y , zAxis.y , 0.0f} ,
    {xAxis.z , yAxis.z , zAxis.z , 0.0f} ,
    {-dot(xAxis , gDecalPosition) , -dot(yAxis , gDecalPosition) , -dot(zAxis , gDecalPosition) , 1.0f}
    };
    
    这里的变换矩阵其实也相当于在Decal的位置上放置一个Camera,即从Decal的角度来观察整个场景~_~
  4. 得到变换矩阵之后即可对Pixel变换,然后在局部坐标中计算出对应的Decal UV就可以做相应的Decal纹理读取了。

比如将下面这个很有追求的Decal贴到Sponza场景中的效果如下

 

Enhanced effect with Bump:

此外,为了增强Decal的渲染效果可以使用带有Bump的Decal,不过这里要加入到整个渲染流程中的话可能会稍稍有些麻烦。为了使用Decal的Normal并在此上进行光照着色,就需要把Decal的Bump给附加到G-Buffer中的Normal上,这一步虽然与Decal texture的附加类似,但通常情况下Normal与Depth会在一个Buffer里边存储,而在进行Diffuse的decal pass时由于需要反求像素的世界位置,因而就要使用ND buffer,这样就不能在该pass里完成decal bump normal的合并。不过可以再建立一个跟Diffuse类似的中间Buffer,其只用来存储渲染Decal时生成的Normal值;然后在当前的Decal pass结束以后,再使用另外一个Pass,使用Normal Depth作为目标RT来合并原始Normal与Decal的Normal。

但是,这其中又涉及到另外一个变换:Decal normal由局部坐标到世界坐标的变换,这其实是相当于对应于decal space的一次tangent变换,需要对于上述的decalMatrix求逆转矩阵来做变换,而这一步的代价还是比较费的,因而此功能就需要谨慎使用。

Some Tips:
  • 处理不同类型物体时的操作。在使用Defferd Decal的方法时,这个问题其实最为常见,也最需要解决。其实这里的不同类型较为广泛,比如说下述几何上不属于同一类物体(见下图),本来是想只将Decal贴在旗子上,但是其却也出现在了后面的墙上;另外,还在就是比如地面上的decal,但如是主角走过该处时,decal可能就同样会出现在角色身上。这个问题一般有两种解决方法:Stencil testing,Object index。前一种方法使用模板测试来标识不同的物体,相对来说其实现较为方便些,而第于二种方法可能需要改变G-Buffer的内容,在使用上可能还不如第一种方法,虽然其通用性更强。                              
  •  
  • 关于Corner Wrap问题,这种情况的表现就像下图所示。该情况的产生是由于通常情况下只考虑Decal水平方向上的坐标投影变换来计算UV,而并没有考虑Decal normal方向上的影响。知道原因后这个问题比较容易解决,可以利用法向量的变化来计算Decal垂直方向上的UV分布就可以了。                    
  • Decal texture采样寻址方式需要设置成CLAMP,否则就会出边重复的情况,视觉上的不正确。另外,也要注意Alpha的混合状态设置。
  • 关于Camera进行Decal Box或Sphere内部的问题。由Defferred decal的实现原理可知,画出包围Decal的Box或Sphere其实是用来标记其所影响的Pixel以便更改diffuse,但这样就会有一个问题,那就是当Camera进入Box或Sphere内部后可能就会导致在视点处直接看到原始mesh,而不是box的mesh,这样肯定就不会进行Decal所对应的shader了,于是也就没法画上decal。这个问题也好解决,既然想增加对原始Mesh的遮挡,那么就可以将原始的Box或Sphere细分,即在其内部增加绘制的triangle以增加遮挡的机会,这样即使Camera进入其内部,仍然可以有内部的子部件来遮挡原始Mesh。不过简单起见可以只在Box内部再多画几个对角线上的triangle就可以应付大多数情况了。
  • 对于Decal数量较多的情况,加入Decal Box或Sphere的实例化渲染以及对应Camera的可见性检测也是很有必要的。

http://chatgpt.dhexx.cn/article/AVZSKpau.shtml

相关文章

【游戏开发小技】Unity中实现Dota里的角色技能地面贴花效果(URP | ShaderGraph | Decal)

本文最终效果 文章目录 一、前言二、环境准备1、URP环境准备2、技能范围图案 二、方案一:写Shader实现1、Shader脚本:UrpDecal.shader2、材质球3、创建Cube4、地面场景5、添加Renderer Feature: Decal6、移动DecalCube,与地面交叉7、运行效果…

UE5实现贴地面效果(RT+Decal)

文章目录 1.实现目标2.实现过程2.1 实现原理2.1.1 Render Target2.1.2 Polygon2.2 具体过程3.参考资料1.实现目标 在之前的文章中基于CesiumForUnreal实现了对地形3DTileset的贴地面绘制效果,在这里基于UE自带的RT和Decal实现更加通用的贴地面效果。依旧是加载在线的Cesium W…

Unity Shader-Decal贴花(SelfDecal,Alpha Blend,Mesh Decal,Projector,Deferred Decal)

前言 最近通关了《What Remains of Edith Finch》(艾迪芬奇的记忆),总体来说应该算是一个剧情解密向的游戏,故事表现手法十分出色。 游戏主要是叙述一个神秘的家族遭遇了一系列类似《死神来了》的故事,家族的人离奇死…

Unity Decal 贴花效果测试

贴花效果,就和名字的直接意思类似,把一张图贴到另一个物体上显示,经常被用于表现一些重复出现的图案,比如弹孔,涂鸦,污渍等。效果图: 常规贴花实现 Unity官方提供了一个工程,这个…

UE4 Decal 贴花不在静态光照下绘制

Decal顶点没有烘焙的光照数据,因此无法在含前向管线的阴影下绘制。特效贴花为unlit自发光材质,阴影下表现影响不大。而场景Decal需要计算光照。 1. Decal实现原理 MobileDecalRendering.cpp 通过矩阵变换 得到FrustumComponentToClip,传入…

游戏中的Decal(贴花)

在游戏中,decal是一种非常常见的效果,常用来实现弹孔,血迹,涂鸦等效果。最近研究了下Decal在游戏引擎中的实现方式,大致总结了一下:1.基于面片实现:直接用一个Quat的mesh,加上一张贴图&#xff…

UE4 Decal实现简介

Decal 是游戏中常见的一个东西,经常被用在 显示弹痕,地面叠加花纹等。 Decal 绘制流程 Decal可以认为是,将一个面的画面沿Decal的Box的X轴方向投影到物体表面。 Decal的绘制实际只有一个Box绘制。 RHICmdList.DrawIndexedPrimitive(GetUn…

跳出for循环

跳出for循环有三种方式&#xff1a; 1&#xff1a;continue&#xff1b;跳出当次循环&#xff0c;可继续进行下一个循环&#xff1b; function ceshi(){for(var i 0 ; i < 6 ; i){if(i 3){continue;}console.log(,i);} }ceshi(); 效果图&#xff1a; 2&#xff1a;brea…

跳出forEach循环

我们平时用到的循环有很多种。for, map, while, forEach, for...of, for...in等等&#xff0c;每种循环都有在某一次循环语句中跳出本次循环的方法&#xff0c;但是除了forEach。 有小伙伴说不用不就好了。其实这些循环里边&#xff0c;当属for的效率最高&#xff0c;for...in最…

Java 8 跳出foreach循环,跳出本次循环,继续执行,之前的for each循环如何跳出本次循环,跳出循环,跳出多层for循环。

在Java8之前&#xff0c;最开始使用for i 循环&#xff0c;很老旧&#xff0c; 后来有了高级的for each 循环&#xff0c;然后这个跳出本次循环和跳出所有的for循环&#xff0c;都简单&#xff0c;稍微没见过的就是跳出多层for循环。 然后就是Java8出的foreach循环&#xff0…

js中的for循环如何跳出,js中for循环的两种语法

js几种for循环的几种用法 谷歌人工智能写作项目&#xff1a;小发猫 js&#xff0c;for循环是怎么运行的&#xff1f; typescript有哪些变化。 最普遍的介绍&#xff1a;for循环是JavaScript中最常用的循环&#xff0c;标准for循环代码格式为&#xff1a;for(定义变量初始值;…

if/while/do-while/for循环以及跳出循环break/return/continue

流程控制对任何一门编程语言都是至关重要的&#xff0c;它为我们提供了控制程序步骤的基本手段。常见对主要分为&#xff0c;条件语句、循环语句、跳转语句。 1、if语句 if 语句是一种判断语句。 语法&#xff1a; if(条件){条件成立时执行的代码 } if...else 语句当条件成…

Python中跳出循环的两种方法

我们经常遇到循环在进行到某一个特定的值时&#xff0c;需要跳出循环&#xff0c;或跳过这个值&#xff0c;python中早已为我们准备了这样的参数:break,continue 比如下面的for循环&#xff1a; for i in range(1,10):print(循环了,i,次) 结果显而易见如图&#xff1a; 当我…

js foreach与for循环之return跳出循环

因为自己比较大只&#xff0c;容易忘记&#xff0c;仅此用来记录一下~ 各种循环中使用return或者退出循环的机制。 1、forEach 使用 return 可以退出循环吗&#xff1f;下面代码打印啥&#xff1f; const list [1, 2, 3, 4, 5]list.forEach(e > {if (e 3) {return}consol…

JS中如何跳出.forEach循环

写在前面 提到在一段程序中如果碰到需要终止&#xff0c;结束一个循环&#xff0c;函数或者一段代码&#xff0c;一般会想到以下这几个关键字return、continue、break 简述一下三者的区别&#xff1a; break: 终止整个循环(有内层循环时终止的是内层循环)&#xff0c;退出swi…

JavaScript foreach 方法跳出循环

通常&#xff0c;在 for循环中跳出循环可以用 break或者 continue 来跳出循环。 break&#xff1a;跳出循环&#xff1b; continue&#xff1a;跳过当次循环。 而有时候需要在 foreach 中跳出循环&#xff0c;该怎么做呢&#xff1f; forEach() 方法用于调用数组的每个元素&am…

C++跳出for循环的方式

注&#xff1a;continue只能跳出当前层的循环&#xff0c;无法直接跳出for循环 方法1&#xff1a; for循环中满足条件后使用break语句&#xff1b; #include <iostream> using namespace std;int main(int argc,char *argv[]) {int i;for(i0;i<5;i){if(i2)break;}cou…

Js之跳出for循环,跳出多次for循环详解

一、for循环退出方式 首先我们都知道循环中最常用的就是continue;break; continue:表示跳出本次循环&#xff0c;也就是不执行本次循环continue之后的操作 break:表示跳出当前的循环&#xff0c;针对整个循环体终止后续的遍历&#xff1b; 最简单的应用效果如下&#xff1a; …

layui遮罩

layui遮罩 在执行某个按钮功能时,可能出现网络延迟或是其他原因时 避免重复表单提交,用遮罩.此次请求不响应,执行完毕,就一直遮罩住其他功能 var loading layer.load(1, {shade: [0.8, #393D49], time: 0}); layer.close(loading);写的位置 在请求开启之前遮住 在请求响应之…

遮罩Mask

一、遮罩&#xff1a;遮罩层范围内可看见被遮罩层图层&#xff0c;遮挡层范围可看见笼罩层图形 遮罩层必须至少有两个图层&#xff0c;上面的一个图层为“遮罩层”&#xff0c;下面的称“被遮罩层”&#xff1b;这两个图层中只有相重叠的地方才会被显示。也就是说在遮罩层中有…