基本原理
热扰动,或者说热扭曲,是游戏中一个挺常见的效果。在现实生活中,热扰动是因为空气受热不均,密度不同,导致了折射率也不一样,所以让光线发生了偏折,看上去就像扭曲了。但在游戏中我们要模拟这个效果的手段并非基于物理,不是真的要修改光的传播
通常情况下我们会生成一张当前背景的纹理,然后在采样时偏移uv,达到输出的结果与原本的背景相比呈现出错位效果的目的。这便是模拟的热扰动
实现思路
有两种思路用于实现该效果
截取当前背景画面作为纹理,然后在想要发生扰动的地方单独放置一个面片。该面片在渲染时根据屏幕坐标和偏移值对纹理进行采样,随后输出采样的结果 优点:可以在不同的地方使用不同的扰动方法,定制化方便。例如火焰是单纯地扭曲,水波的话有一个从中心扩散出去的效果。其次鉴于热扰动的效果不会用到很高清的分辨率,所以可以利用降采样的方式获取背景画面,以节省传输带宽的性能损耗 缺点:如果屏幕中同时出现大量互相重叠的扰动效果,会造成一定程度上的overdraw。并且在表现效果上可能会有穿帮问题 使用单独的材质或Shader Pass将那些要产生扰动的效果的物件输出到一张RT上作为遮罩。然后在后处理中采样该遮罩以进行扰动的计算 优点:适合大量需要扰动但又重叠的物件,一次后处理便能将效果实现。也不需要特意摆放单独的面片。RT也可以使用降采样的方式来提高性能 缺点:只能实现统一的扰动效果(如果要不同效果那就要更多的RT和后处理,代价太大)。其次如果游戏本身没有后处理的话,增加后处理又会带来额外的性能开销 具体实现
由于一般情况下并不会出现大量重叠扰动效果的情况,所以出于实现效果多元化的考虑,个人还是倾向于第一种思路的实现方式。以下便围绕第一种思路的相关代码
要做的事情有两件。第一是拷贝屏幕画面,第二是绘制扰动
以下是执行屏幕拷贝的ScriptableRenderPass,这里强制让拷贝出来的RT为屏幕1/4尺寸的大小
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class CopyTransparentPass : ScriptableRenderPass
{
private RenderTargetIdentifier source { get; set; }
private RenderTargetHandle destination { get; set; }
public CopyTransparentPass(RenderPassEvent evt)
{
base.profilingSampler = new ProfilingSampler(nameof(CopyTransparentPass));
renderPassEvent = evt;
}
public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
{
this.source = source;
this.destination = destination;
ConfigureTarget(destination.Identifier());
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.msaaSamples = 1;
descriptor.depthBufferBits = 0;
descriptor.width /= 4;
descriptor.height /= 4;
cmd.GetTemporaryRT(destination.id, descriptor, FilterMode.Bilinear);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, base.profilingSampler))
{
cmd.Blit(source, BuiltinRenderTextureType.CurrentActive);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
if (destination != RenderTargetHandle.CameraTarget)
{
cmd.ReleaseTemporaryRT(destination.id);
destination = RenderTargetHandle.CameraTarget;
}
}
}
随后是执行绘制扰动的ScriptableRenderPass,这里使用了单独的ShaderTag,避免正常渲染物件时会被画到
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class DrawDistortionPass : ScriptableRenderPass
{
private FilteringSettings m_FilteringSettings;
private ShaderTagId m_ShaderTagId;
public DrawDistortionPass(RenderPassEvent evt)
{
base.profilingSampler = new ProfilingSampler("DrawDistortion");
m_FilteringSettings = new FilteringSettings(RenderQueueRange.transparent);
m_ShaderTagId = new ShaderTagId("Distortion");
renderPassEvent = evt;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, base.profilingSampler))
{
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, SortingCriteria.CommonTransparent);
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
拷贝屏幕画面和绘制扰动的顺序需要安排在绘制透明物体顺序之后,因为很多情况下要扰动的是些特效,则需要等待所有特效绘制完毕才行。以下便是ScriptableRenderFeature的代码
using UnityEngine.Rendering.Universal;
public class CustomRendererFeature : ScriptableRendererFeature
{
private CopyTransparentPass m_CopyTransparentPass;
private DrawDistortionPass m_DrawDistortionPass;
private RenderTargetHandle m_CameraColorAttachment;
private RenderTargetHandle m_CameraTransparentAttachment;
public override void Create()
{
m_CopyTransparentPass = new CopyTransparentPass(RenderPassEvent.AfterRenderingTransparents);
m_DrawDistortionPass = new DrawDistortionPass(RenderPassEvent.AfterRenderingTransparents);
m_CameraColorAttachment.Init("_CameraColorTexture");
m_CameraTransparentAttachment.Init("_CameraTransparentTexture");
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
m_CopyTransparentPass.Setup(m_CameraColorAttachment.Identifier(), m_CameraTransparentAttachment);
renderer.EnqueuePass(m_CopyTransparentPass);
renderer.EnqueuePass(m_DrawDistortionPass);
}
}
SRP部分结束后接下来便是Shader,以下代码是实现了一个波纹外扩的效果,需要由外部来驱动_Offset的增加
Shader "Custom/Distortion/Wave"
{
Properties
{
_RingWidth("Ring Width", range(0, 1)) = 0.05
_Offset("Offset", range(0, 1)) = 0.05
_Radius("Radius", range(0, 1)) = 0
}
SubShader
{
Tags{"RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True" "PreviewType" = "Plane" "RenderPipeline" = "UniversalPipeline"}
Pass
{
Tags{ "LightMode" = "Distortion" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
src="https://pic1.zhimg.com/v2-26ab6c59563bd6e886ce07a1e02af894_b.jpg">
使用后处理的实现方式这里没有列出详细代码,具体可以看下这篇【URP】基于后处理的热空气扭曲效果
对比刚才的方法,使用后处理来做扰动的时候CopyTransparentPass这一步便可省去,而DrawDistortionPass的绘制目标可以设为自定义的一张遮罩RT,Shader也可以只是单纯地返回单色即可,用于区分扰动的区域。最后一步便是后处理时采样遮罩RT和当前屏幕画面输出即可,可以根据需求适当加入噪声纹理。总结下步骤
创建遮罩RT并将默认颜色填充为黑色 将渲染目标设为遮罩RT,然后绘制要发生扰动效果的物件,要发生扰动的区域返回白色 后处理时采样遮罩RT和屏幕画面,当遮罩RT采样到的值为白色时便使用计算扰动后的结果,否则返回当前屏幕画面
3 绿一色 由23468条及发字中的任何牌组成的顺子、刻五、将的和牌。不计混一色。如无“发”字组成的各牌,可计清一色
追答这是最普通的博彩问答。用得最多的胡法本回答由提问者推荐已赞过已踩过收起yxiaojun2015-06-19