了解command buffer
CommandBuffer携带一系列的渲染命令,可以指定在相机渲染的某个点执行本身命令(包括特殊渲染,保存当前rendertexture等)
CommandBuffer可以使用的点看这里(官网文档)
具体渲染流程和触发点如下
特效扭曲的实现原理以及方案
原理:就是抓取特效后面渲染的纹理进行扰动
方案1:抓取特效后面渲染的纹理一般用grabpass,根据grabpass进行扰动
方案2:利用command buffer在渲染天空盒之后保存一张屏幕纹理(特效渲染之前的纹理),然后抓取对应位置的纹理进行扰动
方案1总结:很多手机不兼容,而且性能很一般,所以手游暂时不可能使用
方案2总结:性能可以,有个层次问题,如果扭曲的特效在物体后面,扰动会出现前面的物体,不过为了性能我觉得问题不大,也能达到一定效果
下面效果图就是使用方案2
代码实现
代码实现难度不高
保存AfterSkybox渲染之后的纹理,然后设置全局纹理
m_AfterSkyboxCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, m_AfterSkyboxTex);
//设置摄像机触发commandbuffer时机
m_Camera.AddCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
//设置shader全局图片,方便给扭曲效果用
Shader.SetGlobalTexture(m_AfterSkyboxTexId, m_AfterSkyboxTex);
全局纹理的声明
/// <summary>
/// 在天空盒渲染之后保存的图片id(就是特效渲染之前)
/// </summary>
private static int m_AfterSkyboxTexId = Shader.PropertyToID("_AfterSkyboxTex");
shader全局纹理使用,直接shader里面这么声明就可以使用
sampler2D _AfterSkyboxTex;
工程比较简单直接放代码,如果有需要后方再放工程
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// Pieken 2020
/// </summary>
public class RenderMgr : MonoBehaviour
{
/// <summary>
/// 当前摄像机
/// </summary>
private Camera m_Camera;
/// <summary>
/// 屏幕波纹扭曲 演示屏幕后处理怎么写,功能是有
/// </summary>
public Material ScreenWaveDistortion;
/// <summary>
/// 在天空盒渲染之后保存的图片id(就是特效渲染之前)
/// </summary>
private static int m_AfterSkyboxTexId = Shader.PropertyToID("_AfterSkyboxTex");
/// <summary>
/// 在天空盒渲染之后保存的图片(就是特效渲染之前)
/// </summary>
private RenderTexture m_AfterSkyboxTex;
/// <summary>
/// 当前摄像机渲染最终的图片
/// </summary>
private RenderTexture m_CameraRenderTex;
/// <summary>
/// 在渲染天空盒之后的commandbuff指令
/// </summary>
private CommandBuffer m_AfterSkyboxCommandBuffer;
private void Start()
{
m_Camera = GetComponent<Camera>();
Init();
}
private void Init()
{
//获取一张纹理
m_AfterSkyboxTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.Default);
m_AfterSkyboxTex.name = "AfterSkyboxTex";
//新建一个commandbuffer
m_AfterSkyboxCommandBuffer = new CommandBuffer();
m_AfterSkyboxCommandBuffer.name = "AfterSkyBox_CommandBuffer";
//buffer之类把当前渲染出来的图片保存到m_AfterSkyboxTex
m_AfterSkyboxCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, m_AfterSkyboxTex);
//设置摄像机触发commandbuffer时机
m_Camera.AddCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
//设置shader全局图片,方便给扭曲效果用
Shader.SetGlobalTexture(m_AfterSkyboxTexId, m_AfterSkyboxTex);
}
private void OnPreRender()
{
if (QualitySettings.antiAliasing == 0)
{
m_CameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default);
}
else
{
m_CameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, QualitySettings.antiAliasing);
}
m_Camera.targetTexture = m_CameraRenderTex;
}
private void OnPostRender()
{
m_Camera.targetTexture = null;
//测试后处理,屏幕波纹扭曲 正常这么写Graphics.Blit(m_CameraRenderTex, null as RenderTexture);
Graphics.Blit(m_CameraRenderTex, null as RenderTexture, ScreenWaveDistortion);
RenderTexture.ReleaseTemporary(m_CameraRenderTex);
}
/// <summary>
/// 删除CommandBuffer相关
/// </summary>
private void DestroyCommandBuffer()
{
Destroy(m_AfterSkyboxTex);
if (m_AfterSkyboxCommandBuffer != null)
{
m_Camera.RemoveCommandBuffer(CameraEvent.AfterSkybox, m_AfterSkyboxCommandBuffer);
m_AfterSkyboxCommandBuffer.Dispose();
m_AfterSkyboxCommandBuffer = null;
}
}
private void OnDestroy()
{
DestroyCommandBuffer();
}
}
扭曲使用的shader
Shader "Effect/Particle/Distortion/Add"
{
Properties
{
_MainTex("Particle Texture", 2D) = "white" {}
//噪音图
_Noise("Noise", 2D) = "white" {}
_distortFactorTime("FactorTime",Range(0,5)) = 0.5
_distortFactor("factor",Range(0.04,1)) = 0
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha One
LOD 100
Cull Off Lighting Off ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_particles
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 grabPos:TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Noise;
float4 _Noise_ST;
fixed _distortFactorTime;
fixed _distortFactor;
sampler2D _AfterSkyboxTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.grabPos = ComputeGrabScreenPos(o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float2 tempuv = i.uv + _Time.xy*_distortFactorTime;
fixed4 col = tex2D(_Noise, tempuv);
i.grabPos.xy += col.xy*_distortFactor;
half4 color = tex2D(_MainTex, i.uv) * 2;
half4 bgcolor = tex2Dproj(_AfterSkyboxTex, i.grabPos) * 0.5;
color.a = saturate(color.a);
bgcolor.a = color.a;
return bgcolor * color;
}
ENDCG
}
}
}
总结
这种使用commandbuffer获取一张纹理进行扭曲,虽然层次会出现问题,但是性能可以也能达到一定效果
下面是几年前崩坏3技术分享的原话
我们在崩坏3的场景中较多的使用了屏幕扭曲效果,比如刀剑的拖尾特效,时空断裂效果,水流瀑布及其他场景效果。在渲染扭曲效果的过程中,我们使用3个通道来存储扭曲的渲染结果,两个用于存储uv偏移,另一个用于存储扭曲强度mask,扭曲强度mask用于执行深度剪裁和基于距离的强度控制。使用单独的pass渲染扭曲结果到帧缓冲纹理对于移动平台来说开销较大,所以我们在最终的后处理中整合应用了扭曲效果,相比前者要快很多。 但这种方法也可能导致靠前面的物体由于没有分层处理而混入后面扭曲材质的问题,不过考虑到移动平台的性能限制,相对于整体效果而言这种妥协是值得的。
最近研究开放世界场景加载,应该一段时间不写博客