Unity Shader 实现贴花效果(三)

Deferred Decal(延迟贴花效果)

本文参考博客:Unity Shader-Decal贴花(SelfDecal,Alpha Blend,Mesh Decal,Projector,Deferred Decal)
一说到延迟,我首先想到的就是延迟渲染,要理解这个延迟贴花效果就得先理解延迟渲染的过程。延迟渲染大体先上就是先将场景的顶点位置、颜色、法线(都转换到世界空间)渲染到到GBuffer中(可以理解为和后处理一样的将场景渲染到一张贴图上,但是GBuffer不止一张贴图——就是很多张…分别存储了颜色,法线,位置…)。这样我们就可以只计算一张贴图的像素的光照,从而达到节省性能的目的。而延迟贴花效果就是将贴花后的场景渲染到GBuffer中参与光照计算,从而改变贴花的光照效果,思想就是这么个思想。
没想到这一篇写了这么久,哈哈,感觉所有的坑我都踩完了,太难受了,不过最后的结果相当惊艳,也学到了不少东西,开心。起初我是按照参考博客的代码搞,结果发现,完全搞不出来,就是治好了了我前一篇博客无法在Scene下正常显示的毛病,DeferredDecal的光照质感完全没粗来。
Scene下图片效果:
在这里插入图片描述
看着是不是相当一般…因为那篇博客省去了好多细节,但是非常棒的一件事情就是他给了官方示例的链接,那就相当Nice了,想来看看我好像从来没找过Unity的官方示例…
先上我弄出来的效果图
在这里插入图片描述
我的天,这质感,回头看看之前的效果那简直没法比,延迟渲染NB!!
这里有两个坑,唔…把我搞惨了!!!
First:记得把摄像机的RenderingPath改成Deferred,不然你毛都看不到。
在这里插入图片描述
Second:记得检查摄像机的CommandBuffer是否生成成功,不然你依旧毛都看不到。(因为之前我用的是StreamVR的遗留环境,导致CommandBuffer生成失败,调了好久,可太惨了)
在这里插入图片描述
我这里生成了一个叫做“MyDecal”的CommandBuffer。好了上代码

我们一开始就建一个空的GameObject,然后就给他贴上这个脚本就OK了,别忘了在Inspector给cubeMesh
以及_material赋值,cubeMesh用自带的Cube网格,_material就是你贴花的材质球。

using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
public class DeferredDecal : MonoBehaviour {

    public Mesh cubeMesh = null;
    public Material _material = null;
    private CommandBuffer commandBuffer = null;
    
	private void OnEnable()
    {
    	//创建命令缓冲区,并添加到摄像机
        commandBuffer = new CommandBuffer();
        commandBuffer.name = "MyDecal";
        
        //添加要在指定位置执行的命令缓冲区。
        //在摄影机的渲染中定义要附加CommandBuffer对象的位置。
        //CameraEvent.BeforeLighting:在延迟渲染的光照计算之前。
        Camera.main.AddCommandBuffer(CameraEvent.BeforeLighting,
          commandBuffer);
    }
	void OnRenderObject()//当摄像机渲染完场景时调用
    {
    	//给缓冲区清除一下
        commandBuffer.Clear();
        
        //标识CommandBuffer的RenderTexture。
        //调用public void SetRenderTarget(Rendering.RenderTargetIdentifier color, Rendering.RenderTargetIdentifier depth);
        //GBuffer0:延迟着色GBuffer0(通常为漫反射颜色)。
        //CameraTarget:当前渲染相机的目标纹理。
        commandBuffer.SetRenderTarget(BuiltinRenderTextureType.GBuffer0,
            BuiltinRenderTextureType.CameraTarget);
            
        //public void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material material, int submeshIndex, int shaderPass, MaterialPropertyBlock properties);
        //后面三个用了默认参数
        //添加“绘制网格”命令。
        commandBuffer.DrawMesh(meshFilter.sharedMesh, transform.localToWorldMatrix,
            meshRenderer.sharedMaterial);
    }

    private void OnDisable()
    {
        //移除命令缓冲区。
        if (Camera.main == null) return;
        Camera.main.RemoveCommandBuffer(CameraEvent.BeforeLighting,
            commandBuffer);
    }
	
	private void DrawGizmo(bool selected)
    {
        var col = new Color(0.0f, 0.7f, 1f, 1.0f);
        col.a = selected ? 0.3f : 0.1f;
        Gizmos.color = col;
        Gizmos.matrix = transform.localToWorldMatrix;
        Gizmos.DrawCube(Vector3.zero, Vector3.one);
        col.a = selected ? 0.5f : 0.2f;
        Gizmos.color = col;
        Gizmos.DrawWireCube(Vector3.zero, Vector3.one);
    }

    public void OnDrawGizmos()
    {
        DrawGizmo(false);
    }
    public void OnDrawGizmosSelected()
    {
        DrawGizmo(true);
    }
    //后面三个函数是官方示例给的代码,因为我们用的是一个GameObject所以我们可以画一个正方体让他的位置更加
    //具体
}

这里有个缺点就是我的代码Scene看不到贴花效果,但官方的贴花效果在Scene场景下也可以看到,大概是因为官方用的是OnWillRenderObject()来更新GBuffer而我用的是OnRenderObject(),但是OnWillRenderObject()函数是物体必须有MeshRender组件才会调用,但我们的GameObject没有MeshRender组件。官方的做法是将要贴花的GameObject统一放到一个有MeshRender组件的物体去管理,但是我为了方便考虑就用了OnRenderObject(),这样子即使没有MeshRender组件的GameObject也能更新GBuffer。

在这里插入图片描述

Shader代码

Shader "NewStart/DeferredDecal01"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	
	SubShader
	{
		Fog { Mode Off }
		Tags {"Queue"="Transparent+100"}
		Pass
		{
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
 
			struct appdata
			{
				float4 vertex : POSITION;
			};
 
			struct v2f
			{
				float4 vertex : SV_POSITION;
				float4 screenPos : TEXCOORD1;
				float3 ray : TEXCOORD2;
				float3 yDir:TEXCOORD3;
			};
 
			sampler2D _MainTex;
			sampler2D_float _CameraDepthTexture;
			sampler2D _CameraGBufferTexture2;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.ray =UnityObjectToViewPos(v.vertex)*float3(-1,-1,1);
				o.yDir=mul((float3x3)unity_ObjectToWorld,float3(0,1,0));
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//深度重建视空间坐标
				float2 screenuv = i.screenPos.xy / i.screenPos.w;//透视除法
				float4 depth=tex2D(_CameraDepthTexture,screenuv);
				float linear01Depth=Linear01Depth(depth);
				float viewDepth = linear01Depth * _ProjectionParams.z;//乘以远裁剪平面,恢复原来的值
				float3 viewPos = i.ray* (viewDepth / i.ray.z);
				float4 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1.0));
				float3 objectPos = mul(unity_WorldToObject, worldPos);

				clip(float3(0.5, 0.5, 0.5) - abs(objectPos)); //abs计算输入值的绝对值。

				float3 worldNormal=tex2D(_CameraGBufferTexture2,screenuv).rgb*2.0-1;
				float3 yDir=normalize(i.yDir);
				float nodt=dot(yDir,worldNormal);
				float offset=1-nodt;
				float2 uv = objectPos.xz+0.5+offset*float2(0,objectPos.y);

				fixed4 col = tex2D(_MainTex, uv);
				return col;
			}
			ENDCG
		}
	}
}

这个Shader代码算是祖传代码了,需要注意的是我们,将 _CameraDepthNormalsTexture换回了_CameraDepthTexture,并且定义了一个sampler2D _CameraGBufferTexture2来获取GBuffer中的法线(因为GBuffer2:延迟着色GBuffer2(通常为法线)。),并且因为GBuffer中的法线是世界方向的所以,yDir(Y轴)不再转换到视角空间而是世界空间,剩下部分的我的上一篇博客有详细的解释,此处不再赘述。

接下来要搞得是在贴花里加入法线影响,这个不难,就是要改的地方有点多,而且效果比较微妙
在这里插入图片描述
混合开不开,怎么个混合法,就让这个效果变得很微妙,平坦的不带法线信息的地方还好,但是自身带法线的地方呢?
开?
在这里插入图片描述
不开?
在这里插入图片描述
个人感觉没法线的效果还比较好,见仁见智吧,反正也看实际需求。
代码方面我们需要注意的是GBuffer一般不要同时读和写,所以我们要拷贝一份用来读取,参考博客里写得很详细可以去看看,我也是看了才懂。而其因为我们要修改颜色和法线要写入两个GBuffer,所以要设置一个RenderTargetIdentifier(渲染过程中生成临时的渲染纹理)数组。

using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
public class DeferredDecal : MonoBehaviour
{

    public Mesh cubeMesh = null;
    public Material _material = null;
    private CommandBuffer commandBuffer = null;

    private void OnEnable()
    {
        commandBuffer = new CommandBuffer();
        commandBuffer.name = "MyDecal";
        Camera.main.AddCommandBuffer(CameraEvent.BeforeLighting,
          commandBuffer);
    }
    void OnRenderObject()
    {
        //创建命令缓冲区
        commandBuffer.Clear();
		
		//Shader的静态方法PropertyToID通过属性名获取属性ID
        int normalCopyRTId = Shader.PropertyToID("_NormalCopy");
        
        //添加“获取临时渲染纹理”命令。简单来说就将渲染纹理是拷贝到normalCopyRTId里
        //public void GetTemporaryRT(int nameID, int width, int height),后面一大串都用了默认参数就不写了
        //width,height是以像素为单位的宽度,当为-1表示“相机像素宽度”。
        commandBuffer.GetTemporaryRT(normalCopyRTId, -1, -1);
        //Blit类似于后处理里常用的Graphics.blit-它主要用于从一个(渲染)纹理复制到另一个纹理。
        commandBuffer.Blit(BuiltinRenderTextureType.GBuffer2, normalCopyRTId);
        
        //添加“设置活动渲染目标”命令。
        //标识CommandBuffer的RenderTexture。
        //调用public void SetRenderTarget(Rendering.RenderTargetIdentifier color, Rendering.RenderTargetIdentifier depth);
        //GBuffer0:延迟着色GBuffer0(通常为漫反射颜色)。
        //GBuffer2:延迟着色GBuffer2(通常为法线)。
        //CameraTarget:当前渲染相机的目标纹理。
        RenderTargetIdentifier[] mrt = new RenderTargetIdentifier[]
        { BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.GBuffer2 };
        commandBuffer.SetRenderTarget(mrt, BuiltinRenderTextureType.CameraTarget);
        //public void DrawMesh(Mesh mesh, Matrix4x4 matrix, Material material, int submeshIndex, int shaderPass, MaterialPropertyBlock properties);
        //添加“绘制网格”命令。
        commandBuffer.DrawMesh(cubeMesh, transform.localToWorldMatrix,
            _material);
        //释放掉拷贝的内存
        commandBuffer.ReleaseTemporaryRT(normalCopyRTId);
    }

    private void OnDisable()
    {
        //移除要在指定位置执行的命令缓冲区。
        if (Camera.main == null) return;
        Camera.main.RemoveCommandBuffer(CameraEvent.BeforeLighting,
            commandBuffer);
    }

    private void DrawGizmo(bool selected)
    {
        var col = new Color(0.0f, 0.7f, 1f, 1.0f);
        col.a = selected ? 0.3f : 0.1f;
        Gizmos.color = col;
        Gizmos.matrix = transform.localToWorldMatrix;
        Gizmos.DrawCube(Vector3.zero, Vector3.one);
        col.a = selected ? 0.5f : 0.2f;
        Gizmos.color = col;
        Gizmos.DrawWireCube(Vector3.zero, Vector3.one);
    }

    public void OnDrawGizmos()
    {
        DrawGizmo(false);
    }
    public void OnDrawGizmosSelected()
    {
        DrawGizmo(true);
    }
}

Shader部分要改的也是蛮多的,因为将法线贴图中的法线转换到世界坐标下,我们需要添加上X,Z轴的转换,还要定义一个_NormalCopyRT来接收拷贝的法线数据,因为我们要同时输出法线和颜色信息所以用到了MRT(多重渲染),我们还要修改frag函数,让他输出两个值。

Shader "NewStart/DeferredDecal"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BumpMap("Normal",2D)="bump"{}
	}
	
	SubShader
	{
		Tags {"Queue"="Transparent+100"}
		Pass
		{
			Fog { Mode Off } // no fog in g-buffers pass
			ZWrite Off
			//Blend SrcAlpha OneMinusSrcAlpha
			Blend One One
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma exclude_renderers nomrt
			
			#include "UnityCG.cginc"
 
			struct appdata
			{
				float4 vertex : POSITION;
			};
 
			struct v2f
			{
				float4 vertex : SV_POSITION;
				float4 screenPos : TEXCOORD1;
				float3 ray : TEXCOORD2;
				float3 xDir:TEXCOORD3;
				float3 yDir:TEXCOORD4;
				float3 zDir:TEXCOORD5;
			};
 
			sampler2D _MainTex;
			sampler2D _BumpMap;
			sampler2D_float _CameraDepthTexture;
			sampler2D _NormalCopyRT;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.ray =UnityObjectToViewPos(v.vertex)*float3(-1,-1,1);

				o.xDir=mul((float3x3)unity_ObjectToWorld,float3(1,0,0));
				o.yDir=mul((float3x3)unity_ObjectToWorld,float3(0,1,0));
				o.zDir=mul((float3x3)unity_ObjectToWorld,float3(0,0,1));
				return o;
			}
			
			void frag (v2f i,out half4 outAlbedo:COLOR0,out half4 outNormal:COLOR1)
			{
				//深度重建视空间坐标
				float2 screenuv = i.screenPos.xy / i.screenPos.w;//透视除法
				float4 depth=tex2D(_CameraDepthTexture,screenuv);
				float linear01Depth=Linear01Depth(depth);
				float viewDepth = linear01Depth * _ProjectionParams.z;//乘以远裁剪平面,恢复原来的值
				float3 viewPos = i.ray* (viewDepth / i.ray.z);
				float4 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1.0));
				float3 objectPos = mul(unity_WorldToObject, worldPos);

				clip(float3(0.5, 0.5, 0.5) - abs(objectPos)); //abs计算输入值的绝对值。

				float3 worldNormal=tex2D(_NormalCopyRT,screenuv).rgb*2.0-1;
				float3 yDir=normalize(i.yDir);
				float nodt=dot(yDir,worldNormal);
				float offset=1-nodt;
				float2 uv = objectPos.xz+0.5+offset*float2(0,objectPos.y);

				outAlbedo = tex2D(_MainTex, uv);
				
				//读取法线并转换到世界空间最后再进行度法线的逆操作就OK了。
				fixed3 bump=UnpackNormal(tex2D(_BumpMap,uv));
				//注意这里的顺序是XZY不是XYZ!!!!!!!!!!!
				float3x3 normalMat=float3x3(i.xDir,i.zDir,i.yDir);
				bump=mul(bump,normalMat);
				outNormal=fixed4(bump*0.5+0.5,1.0);
			}
			ENDCG
		}
	}
}

那么这篇博客到这里就结束了…才怪,还有个坑…太惨了
在这里插入图片描述
此情此景我不喊了一句“卧槽!”,为什么会这样子?我也说不太清,当然是延迟渲染的锅了。但是我知道解决办法,就是在场景中再添加一个光源,把主光源照不到的地方照亮就一切OK了,当然你也可以直接把主光源设置为NoShadow就可以解决,不过这样子你的场景就没阴影了,反正看情况吧。
在这里插入图片描述
OK,问题都解决了,Decal篇顺利结束。感谢你的阅读,如有错误,欢迎指正。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值