unity shader 实现延迟渲染代码加注释

一 基础简介

延迟光照与延迟渲染

1.1 光源类型

【平行光】场景中唯一的全局光,光源信息可以影响场景中所有物体。
【环境光】是Edit->Render Setting里面的Ambient Light的值。在Shader中获取它只需要访问全局变量UNITY_LIGHTMODEL_AMBIENT即可。它是全局变量,因此在在哪个Pass里访问都可以。
【点光源】以一个中心点向周围扩散的光源,有衰退。
【聚光灯】像聚光灯一样从一个点向某个方向和范围散发光亮。

1.2 灯光渲染模式(Render Mode)

决定一个灯光是哪种处理模式取决于它的Render Mode,设置方式为点击灯光后,在Inspector中设置Render Mode:
【Not Important】所有非平行光源,会逐顶点或者球谐函数处理
【Important】所有非平行光源,会逐像素处理
【设置任何模式】场景中的平行光源都是按逐像素处理

1.3 渲染模式(LightMode)

渲染模式是关于渲染方式的设置。
渲染模式需要在shader中的 Pass > Tags中设置
pass内的tags有别与subshader中的tags,主要用于渲染模式设置

pass内的tags说明

取值例子说明
Always“LightMode”=“Always”不管是用哪种渲染路径,该pass总是会被渲染。但不计算任何光照
Forwardbase“LightMode”=“ForwardBase”用于向前渲染,该pass会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps
ForwardAdd“LightMode”=“ForwardAdd”用于向前渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源
Deferred“LightMode”=“Deferred”用于向前渲染,该pass会渲染G缓冲,G-buffer
ShadowCaster“LightMode”=“ShadowCaster”把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中,用于渲染产生阴影的物体
ShadowCollector“LightMode”=“ShadowCollector”用于收集物体阴影到屏幕坐标Buff里
PrepassBase用于遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分
PrepassFinal用于遗留的延迟渲染,该pass通过合并纹理、光照和自发光来渲染得到最后的颜色
Vertex、VertexLMRGBM和VertexLM用于遗留的顶点照明渲染
1.4 阴影处理
  1. 处理光照阴影必须添加#pragma multi_compile_fwdbase
  2. 在顶点输入参数中使用 SHADOW_COORDS(2)来定义阴影通道
  3. 在顶点处理器里调用TRANSFER_SHADOW(v2f)处理片元像素
  4. 在片元着色器调用SHADOW_ATTENUATION(v2f) 返回的就是这个像素是否存在阴影中。

二 延迟渲染(Forward Rendering )

2.1 延迟渲染的基础说明

【简介】延迟渲染是正向渲染的优化方法。正向渲染支持的光源有限,也更消耗性能。延迟渲染可以支持跟多的灯光,性能消耗也更低。在正向渲染中假设有x个物体和y个灯光,那么总共要执行shader x * y次。如果使用延迟渲染则只需执行 x + y次
【原理】原理是先深度测试后着色计算,即先将三维世界进行深度测试,取得二维世界的屏幕像素信息,然后再进光照等着色计算。

【优点】不必对那些没有通过深度测试的像素进行光照计算,有效增强了性能。
可以在着色计算的时候获取到深度值,正向渲染是不能的。光照的开销与场景复杂度无关。

【缺点】限制比较多:

  1. 显卡必须支持MRT、Shader Mode 3.0及以上
  2. 深度渲染纹理以及双面的模板缓冲
  3. 在移动端,需要硬件支持 OpenGL ES 3.0 以上
  4. 如果摄像机的 Projection 设置为了 Orthographi(正交相机),那么摄像机会回退到前向渲染。因为延迟渲染只支持透视投影
  5. 不支持半透明物体处理,不支持真正的抗锯齿。
    【缺点解决方案】
    1 使用支持DX10及以上的驱动和硬件。
    2 使用边缘检测处理可以在一定限度的实现软体抗锯齿(我博客里有),虽然没有硬件抗锯齿性能、效果好,但在一定程度上可以接受。
    3 对不透明物体采用延迟渲染,透明物体采用正向渲染,可以解决Alpha Blend的问题(Unity3D采用这种解决方案)
    4 有个叫延迟光照的技术。
2.2 向前、延迟渲染切换
  • 向前渲染和延迟渲染可以混合使用。但要显示延迟显示的shader必须经过设置。而正向渲染的shader无需做任何设置。
  • 两种切换方式
    第一种:
    Main Camera 的 Inspector 面包中的 Rendering Path 选择Deferred
    第二种:
    Edit > ProjectSettings > Graphics > Tier Settings
    去掉相应质量设置下的Use Default勾选,然后设置Rendering Path 为Deferred
    这样就从forward(向前渲染)切换到了Deferred(延迟渲染)
    在这里插入图片描述
2.3 延迟渲染的实现

延迟渲染通过两个Shader实现。分别是GBuffer Shader(缓冲着色)和Light Shader(灯光着色)

2.3.1 GBuffer Shader

【作用】渲染物体的漫反射颜色、高光反射颜色、平滑度、深度等信息并存储到GBuffer中,实际上就是对每个物体进行光源以外的着色操作, 即获取经过深度测试后的到投影屏幕的2维像素信息。
【用法】放置到场景中需要渲染的物体上,每个物体执行一次该Shader。
【补充说明】
GBuffer Shader 与普通shader最大的区别除了pass tag 中的light mode要选择延迟渲染外,shander的输出信息不再是S_Target 或 Color。而是包含多个渲染纹理的输出。
默认Gbuffer的渲染纹理信息包括:

调用参数名数据格式作用
SV_TARGET0ARGB32RGB存储漫反射颜色,A通道存储遮罩
SV_TARGET1ARGB32RGB存储高光(镜面)反射颜色,A通道存储高光反射的指数部分,也就是平滑度
SV_TARGET2ARGB2101010RGB通道存储世界空间法线,A通道没用
SV_TARGET3ARGB2101010(非HDR)或ARGBHalf(HDR)Emission + lighting + lightmaps + reflection probes (高动态光照渲染/低动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲
深度+模板缓冲区
Depth深度信息

【GBuffer shader 代码】

Shader "Custom/deferred"
{
    //unity参数入口 
	Properties
	{
		_MainTex("贴图",2D)="white"{}
		_Diffuse("漫反射",Color) = (1,1,1,1)
		_Specular("高光色",Color) = (1,1,1,1)
		_Gloss("平滑度",Range(1,100)) = 50
	}
	SubShader
	{
        //非透明队列
		Tags { "RenderType" = "Opaque" }
		LOD 100
        //延迟渲染
		Pass
		{
            //设置 光照模式为延迟渲染
			Tags{"LightMode" = "Deferred"}
			CGPROGRAM
				// 声明顶点着色器、片元着色器和输出目标
				#pragma target 3.0
				#pragma vertex vert
				#pragma fragment frag
				//排除不支持MRT的硬件
				//#pragma exclude_renderers norm
				// unity 函数库
				#include"UnityCG.cginc"
				//定义UNITY_HDR_ON关键字
				//在c# 中 Shader.EnableKeyword("UNITY_HDR_ON"); Shader.DisableKeyword("UNITY_HDR_ON");
				// 设定hdr是否开启
				#pragma multi_compile __ UNITY_HDR_ON
				// 贴图
				sampler2D _MainTex;
				// 题图uv处理
				float4 _MainTex_ST;
				// 漫反射光
				float4 _Diffuse;
				// 高光
				float4 _Specular;
				// 平滑度
				float _Gloss;
				// 顶点渲染器所传入的参数结构,分别是顶点位置、法线信息、uv坐标
				struct a2v
				{
					float4 pos:POSITION;
					float3 normal:NORMAL;
					float2 uv:TEXCOORD0;
				};
				// 片元渲染器所需的传入参数结构,分别是像素位置、uv坐标、像素世界位置、像素世界法线
				struct v2f
				{
					float4 pos:SV_POSITION;
					float2 uv : TEXCOORD0;
					float3 worldPos:TEXCOORD1;
					float3 worldNormal:TEXCOORD2;
				};
				// 延迟渲染所需的输出结构。正向渲染只需要输出1个Target,而延迟渲染的片元需要输出4个Target  
				struct DeferredOutput
				{
					// RGB存储漫反射颜色,A通道存储遮罩
					float4 gBuffer0:SV_TARGET0;
					// RGB存储高光(镜面)反射颜色,A通道存储高光反射的指数部分,也就是平滑度
					float4 gBuffer1:SV_TARGET1;
					// RGB通道存储世界空间法线,A通道没用
					float4 gBuffer2:SV_TARGET2;
					// Emission + lighting + lightmaps + reflection probes (高动态光照渲染/低动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲
					float4 gBuffer3:SV_TARGET3;
				};
				// 顶点渲染器
				v2f vert(a2v v)
				{
					v2f o;
					// 获取裁剪空间下的顶点坐标
					o.pos = UnityObjectToClipPos(v.pos);
					// 应用uv设置,获取正确的uv
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);
					// 获取顶点的世界坐标
					o.worldPos = mul(unity_ObjectToWorld, v.pos).xyz;
					// 获取世界坐标下的法线
					o.worldNormal = UnityObjectToWorldNormal(v.normal);
					return o;
				}
				// 片元着色器
				DeferredOutput frag(v2f i)
				{
					DeferredOutput o;
					// 像素颜色 = 贴图颜色 * 漫反射颜色
					fixed3 color = tex2D(_MainTex, i.uv).rgb * _Diffuse.rgb;
					// 默认使用高光反射输出!!
					o.gBuffer0.rgb = color; // RGB存储漫反射颜色,A通道存储遮罩
					o.gBuffer0.a = 1; // 漫反射的透明度
					o.gBuffer1.rgb = _Specular.rgb; // RGB存储高光(镜面)反射颜色,
					o.gBuffer1.a = _Gloss / 100; // 高光(镜面)反射颜色 的
					o.gBuffer2 = float4(i.worldNormal * 0.5 + 0.5, 1); // RGB通道存储世界空间法线,A通道没用
					// 如果没开启HDR,要给颜色编码转换一下数据exp2,后面在lightpass2里则是进行解码log2
					#if !defined(UNITY_HDR_ON)
						color.rgb = exp2(-color.rgb);
					#endif
					// Emission + lighting + lightmaps + reflection probes (高动态光照渲染/低动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲
					o.gBuffer3 = fixed4(color, 1);
					return o;
				}
			ENDCG
		}
	}
}


2.3.2 Lighting Shader

【作用】直接对Gbuffer中的像素信息进行光照计算,并存储到帧缓冲中。每个光源执行一次该shader。
【用法】unity有内建的shader。所以如果没有定制需求,不编写这个shader也是可以的。如果需要自己写light shader
需要在编写好shader后将edit -> project settings -> Graphics -> Deferred 设置为costom shader,并指定自定义文件。
Light Shader
【light shader 代码】

Shader "Unlit/deferredLight"
{
    SubShader
    {
        // 第一个pass用于合成灯光
        Pass
        {
            // 由于像素信息已经经过深度测试,所以可以关闭深度写入
			ZWrite Off
            // 如果开启了LDR混合方案就是DstColor zero(当前像素 + 0),
            // 如果开启了HDR混合方案就是One One(当前像素 + 缓冲像素),由于延迟渲染就是等于把灯光渲染到已存在的gbuffer上,所以使用one one
			Blend [_SrcBlend] [_DstBlend]
            CGPROGRAM
            // 定义运行平台
			#pragma target 3.0
            // 我们需要所有的关于灯光的变体,使用multi_compile_lightpass
			#pragma multi_compile_lightpass 
            // 不使用nomrt着色器
			#pragma exclude_renderers nomrt
            //定义UNITY_HDR_ON关键字
			//在c# 中 Shader.EnableKeyword("UNITY_HDR_ON"); Shader.DisableKeyword("UNITY_HDR_ON");
			// 设定hdr是否开启 
			#pragma multi_compile __ UNITY_HDR_ON
            // 定义顶点渲染器和片元渲染器的输入参数
            #pragma vertex vert
            #pragma fragment frag
            // 引入shader 相关宏宏
			#include "UnityCG.cginc"
			#include "UnityDeferredLibrary.cginc"
			#include "UnityGBuffer.cginc"
            //定义从 Deferred模型对象输入的屏幕像素数据 
			sampler2D _CameraGBufferTexture0;// 漫反射颜色
			sampler2D _CameraGBufferTexture1;// 高光、平滑度
			sampler2D _CameraGBufferTexture2;// 世界法线
            //顶点渲染器输出参数结构,包含顶点坐标、法线
			struct a2v
			{
				float4 pos:POSITION;
				float3 normal:NORMAL;
			};
            //片元渲染器输出结构,包含像素坐标、uv坐标
			struct Deffred_v2f
			{
				float4 pos: SV_POSITION;
				float4 uv:TEXCOORD;
				float3 ray : TEXCOORD1;
			};
            // 顶点渲染器
			Deffred_v2f vert(a2v v)
			{
				Deffred_v2f o;
                //将顶点坐标从模型坐标转化为裁剪坐标
				o.pos = UnityObjectToClipPos(v.pos);
                // 获取屏幕上的顶点坐标
				o.uv = ComputeScreenPos(o.pos);
                // 模型空间转 视角空间做i奥
				o.ray =	UnityObjectToViewPos(v.pos) * float3(-1,-1,1);
                // 插值
				o.ray = lerp(o.ray, v.normal, _LightAsQuad);
				return o;
			}

            //片段渲染器
            //设置片段渲染器输出结果的数据格式。如果开始hdr就使用half4,否则使用fixed4
			#ifdef UNITY_HDR_ON
			    half4
			#else
			    fixed4
			#endif
			frag(Deffred_v2f i) : SV_Target
			{
                // 定义光照属性
				float3 worldPos;//像素的世界位置
				float2 uv;//uv
				half3 lightDir;//灯光方向
				float atten;// 衰减
				float fadeDist;// 衰减距离
                //计算灯光数据,并填充光照属性数据,返回灯光的坐标,uv、方向衰减等等
				UnityDeferredCalculateLightParams(i, worldPos, uv, lightDir, atten, fadeDist);

				// 灯光颜色
				half3 lightColor = _LightColor.rgb * atten;
                //gbuffer与灯光合成后的像素数据
				half4 diffuseColor = tex2D(_CameraGBufferTexture0, uv);// 漫反射颜色
				half4 specularColor = tex2D(_CameraGBufferTexture1, uv);// 高光颜色
				float gloss = specularColor.a * 100;//平滑度
				half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);// 法线
				float3 worldNormal = normalize(gbuffer2.xyz * 2 - 1);// 世界法线

                // 视角方向 = 世界空间的摄像机位置 - 像素的位置
				fixed3 viewDir = normalize(_WorldSpaceCameraPos - worldPos);
                // 计算高光的方向 = 灯光方向与视角方向中间的点
				fixed3 halfDir = normalize(lightDir + viewDir);

                // 漫反射 = 灯光颜色 * 漫反射颜色 * max(dot(像素世界法线, 灯光方向))
				half3 diffuse = lightColor * diffuseColor.rgb * max(0,dot(worldNormal, lightDir));
                // 高光 =  灯光颜色 * 高光色  * pow(max(0,dot(像素世界法线,计算高光的方向)), 平滑度);
				half3 specular = lightColor * specularColor.rgb * pow(max(0,dot(worldNormal, halfDir)),gloss);
                // 像素颜色 = 漫反射+高光,透明度为1
				half4 color = float4(diffuse + specular,1);

                //如果开启了hdr则使用exp2处理颜色
				#ifdef UNITY_HDR_ON
				    return color;
				#else 
				    return exp2(-color);
				#endif
			}

            ENDCG
        }

		//转码pass,主要用于LDR转码
		Pass
		{
            //使用深度测试,关闭剔除
			ZTest Always
			Cull Off
			ZWrite Off
            //模板测试
			Stencil
			{
				ref[_StencilNonBackground]
				readMask[_StencilNonBackground]

				compback equal
				compfront equal
			}
			CGPROGRAM
            //输出平台
			#pragma target 3.0
			#pragma vertex vert
            #pragma fragment frag
            // 剔除渲染器
			#pragma exclude_renderers nomrt
            //
			#include "UnityCG.cginc"
            //缓冲区颜色
			sampler2D _LightBuffer;
            struct a2v
			{
				float4 pos:POSITION;
				float2 uv:TEXCOORD0;
			};
			struct v2f
			{
				float4 pos:SV_POSITION;
				float2 uv:TEXCOORD0;

			};
            //顶点渲染器
			v2f vert(a2v v)
			{
				v2f o;
                // 坐标转为裁剪空间
				o.pos = UnityObjectToClipPos(v.pos);
				o.uv = v.uv;
                // 通常用于判断D3D平台,在开启抗锯齿的时候图片采样会用到
				#ifdef  UNITY_SINGLE_PASS_STEREO
				    o.uv = TransformStereoScreenSpaceTex(o.uv,1.0);
				#endif
				return o;
			}
            //片段渲染器
			fixed4 frag(v2f i): SV_Target
			{
				return -log2(tex2D(_LightBuffer,i.uv));
			}

			ENDCG
		}
    }
}



2.5 延迟渲染的实现流程
2.5.1 实例说明

场景中包含一个唯一的平行光,4个点光源。两个相邻的方块。

2.5.2 使用步骤

0,首先按上面 2.2所说的将unity切换为延迟渲染
1,点击场景中需要启用的光源,然后在Inspactor窗口中设置RenderMode 为Important或Auto,如果设置为Not Important 就不会被shader处理了

2,然后在asset窗口中分别创建GBuffer Shader并创建相对应的Materal
3,将附有GBuffer Shader的材质 赋给需要延迟渲染的物体
4,如果有自定义的 Light Shader,在asset窗口中创建好,并在工具栏edit -> project settings -> Graphics -> Deferred 设置为costom shader,并关联创建的Light Shader。

然后就能看到效果了
在这里插入图片描述

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千年奇葩

从来没受过打赏,这玩意好吃吗?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值