UnityXR-Hololens-自定义shader总结

第1篇. Multi-Pass VS Single-Pass-Instanced

1.1 Multi-Pass

    Multi-Pass,又称传统双通道模式。该模式是先完成左眼的渲染,然后再做右眼的渲染。这种模式虽然能很快速适配VR/MR,但是两眼之间会有一定的时滞与延迟,体验不佳。因为在这种模式下, Unity会为左右眼各分配一个Render Texture做渲染, 目的是和非VR模式下的渲染方式尽可能的兼容。
该模式可以完美支持自定义shader和后处理,即自定义shader和后处理按照传统的方法实现,不需要额外添加代码。但渲染效率并不高。

1.2 Single-Pass-Instanced

    Single-Pass-Instanced,又称单通道实例化模式。在该模式下,利用渲染目标数组来执行单个绘制调用,该调用将实例实例化为每只眼睛的适当渲染目标。此外,此模式允许所有渲染都在渲染管道的一次执行中完成。
    因此,选择Single Pass Instanced渲染作为混合现实应用程序的渲染路径可以节省CPU和GPU上的大量时间,并且是推荐的渲染配置。
    但是,为了对每只眼睛的每个网格发出单个绘制调用,所有着色器都必须支持GPU Instancing。实例化使GPU可以在两只眼睛之间复用绘图调用。默认情况下,Unity内置着色器以及MRTK标准着色器在着色器代码中包含必要的实例化指令。如果为Unity编写自定义着色器,则可能需要更新这些着色器以支持Single Pass Instanced渲染。

1.3 Multi-Pass 或 Single-Pass-Instanced的设置方法

    设置方法如下:
    File ——> Build Settings ——> Player Settings ——> XR Settings ——> Stereo Rendering Mode
在这里插入图片描述

第2篇. Single-Pass-Instanced自定义shader

    上一篇已经说到,默认的着色器以及MRTK标准着色器在着色器代码中包含必要的实例化指令。官方建议使用MRTK标准着色器,相关的技术文档可见https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/README_MRTKStandardShader.html
如果需要自定义shader,可以通过下面的方法为自定义的shader添加实例化指令。

2.1 为自定义的shader添加实例化指令

    1、在预处理指令那里添加GPU Instance预处理指令,shader会根据是否开启GPU Instance生成不同的shader变体。

//添加GPU Instance预处理指令
#pragma multi_compile_instancing

    2、在顶点输入输出结构体添加以下宏

//为顶点实例化一个ID
UNITY_VERTEX_INPUT_INSTANCE_ID

    3、针对XR的Single-Pass-Instanced,还需要再顶点输出结构体添加以下宏

//在顶点着色器声明立体目标眼睛字段输出结构
//与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏
UNITY_VERTEX_OUTPUT_STEREO

    4、为顶点着色器添加以下宏

//初始化顶点信息
//这个宏必须在Vertex Shader的最开始调用
//如果你需要在Fragment Shader里访问Instanced属性,则需要在Fragment Shader的开始也用一下
//这个宏的目的在于让Instance ID在Shader函数里也能够被访问到。
UNITY_SETUP_INSTANCE_ID(v);
//在顶点程序中,将实例ID从输入结构复制到输出结构
//在Vertex Shader中把Instance ID从输入结构拷贝至输出结构中
//只有当你需要在Fragment Shader中访问每个Instance独有的属性时才需要写这个宏。
UNITY_TRANSFER_INSTANCE_ID(v,o);
//分配立体目标的眼睛
//与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    5、为片元着色器添加以下宏

//初始化顶点信息
UNITY_SETUP_INSTANCE_ID(i);

    完整参考shader代码见下:

Shader "Unlit/GPUInstancing"
{
    Properties
    {
        _Color ("Color", color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

			//添加GPU Instance预处理指令
			#pragma multi_compile_instancing

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

				//为顶点实例化一个ID
				UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

				//为顶点实例化一个ID
				UNITY_VERTEX_INPUT_INSTANCE_ID

				//在顶点着色器声明立体目标眼睛字段输出结构(与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏)
				UNITY_VERTEX_OUTPUT_STEREO
            };

            float4 _Color;

            v2f vert (appdata v)
            {
				//初始化顶点信息
				//这个宏必须在Vertex Shader的最开始调用,如果你需要在Fragment Shader里访问Instanced属性,则需要在Fragment Shader的开始也用一下。这个宏的目的在于让Instance ID在Shader函数里也能够被访问到。
				UNITY_SETUP_INSTANCE_ID(v);

				v2f o;

				//在顶点程序中,将实例ID从输入结构复制到输出结构
				//在Vertex Shader中把Instance ID从输入结构拷贝至输出结构中。只有当你需要在Fragment Shader中访问每个Instance独有的属性时才需要写这个宏。
				UNITY_TRANSFER_INSTANCE_ID(v,o);

				//分配立体目标的眼睛(与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏)
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				//初始化顶点信息
				UNITY_SETUP_INSTANCE_ID(i);
                fixed4 col = _Color;
                return col;
            }
            ENDCG
        }
    }
}

2.2 后处理Shader添加实例化指令的注意事项

    后处理的写法跟自定义shader的写法一样,可以参考“为自定义的shader添加实例化指令”
唯一不同的是后处理是通过Graphics.Blit(src, dest, material)的方法给material传入src到_MainTex的,所以_MainTex的声明及采样需要做出一些修改:
    1、_MainTex的声明:UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex);
    2、_MainTex的采样:UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv);

    完整参考shader代码见下:

Shader "Unlit/ScreenColor"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_H("H",Range(0,1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

		/*  后处理的shader必须要添加 ZTest Always 和 ZWrite Off  */
		ZTest Always
		ZWrite Off
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
			//添加GPU Instance预处理指令
			#pragma multi_compile_instancing
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

				//为顶点实例化一个ID
				UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

				//为顶点实例化一个ID
				UNITY_VERTEX_INPUT_INSTANCE_ID
				//在顶点着色器声明立体目标眼睛字段输出结构(与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏)
				UNITY_VERTEX_OUTPUT_STEREO
            };
            //后处理_MainTex的声明,以便2D纹理数组将被正确声明
			UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex);
			uniform float _H;

			float3 RGBToHSV( float3 Color ){
			    float4 p = lerp(float4(Color.bg, -1.0,2.0 / 3.0), float4(Color.gb, 0.0, -1.0 / 3.0), step(Color.b, Color.g));
			    float4 q = lerp(float4(p.xyw, Color.r), float4(Color.r, p.yzx), step(p.x, Color.r));
			    float d = q.x - min(q.w, q.y);
			    float e = 1.0e-10;
			    return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
			}
			float3 HSVToRGB( float3 Color ){
			    return lerp(float3(1,1,1),saturate(3.0*abs(1.0-2.0*frac(Color.r+float3(0.0,-1.0/3.0,1.0/3.0)))-1),Color.g)*Color.b;
			}

            v2f vert (appdata v)
            {
				//初始化顶点信息
				//这个宏必须在Vertex Shader的最开始调用,如果你需要在Fragment Shader里访问Instanced属性,则需要在Fragment Shader的开始也用一下。这个宏的目的在于让Instance ID在Shader函数里也能够被访问到。
				UNITY_SETUP_INSTANCE_ID(v);

                v2f o;

				//在顶点程序中,将实例ID从输入结构复制到输出结构
				//在Vertex Shader中把Instance ID从输入结构拷贝至输出结构中。只有当你需要在Fragment Shader中访问每个Instance独有的属性时才需要写这个宏。
				UNITY_TRANSFER_INSTANCE_ID(v,o);
				//分配立体目标的眼睛(与普通的GPU Instancing不一样的地方,XR的Single-Pass-Instanced需要添加这个宏)
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				//初始化顶点信息
				UNITY_SETUP_INSTANCE_ID(i);
				//后处理_MainTex的采样方式,以便于在Single Pass模式下给双眼取样
                fixed4 col = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv);

				float3 colToHSV = RGBToHSV( col.rgb );
				colToHSV.r += _Time.y * 0.5;
				colToHSV.r = frac(colToHSV.r);
                return fixed4( HSVToRGB(colToHSV),col.a );
            }
            ENDCG
        }
    }
}

2.3 XR Single-Pass-Instanced的踩坑记录

    1、在shader上使用UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) 声明纹理会出现不可预计的问题,如纹理加载不了,采样不了等问题(但后处理的_MainTex必须要这样声明)
    2、尽量在每个材质球上的Enable GPU Instancing 上打勾,如果不打勾,这个材质球使用到的这个shader的GPU Instancing变体shader并不会打包出去导致Single-Pass-Instanced实例化出问题。
    3、后处理的脚本请通过材质赋值,有些程序的习惯是获取shader,再通过
material = new Material(shader);
来动态创建材质,导致这个后处理shader的GPU Instancing变体shader并不会打包出去而渲染出错。
建议方法:

    public Material material;
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
            Graphics.Blit(src, dest, material);
        else
            Graphics.Blit(src, dest);
}
``

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值