• Hololens低版本不支持多通道或者单眼左眼显示问题


    原文链接

    自己学习记录一下
    第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
    
    • 1
    • 2

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

    //为顶点实例化一个ID
    UNITY_VERTEX_INPUT_INSTANCE_ID
    
    • 1
    • 2

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

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

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

    完整参考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
            }
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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
            }
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    打印星堆(for循环嵌套实例)
    ES6之 变量的解构赋值 ➕ 扩展运算符(…)
    Day54、55 进程的定义、组成、组织方式、特征
    docker制作镜像
    《痞子衡嵌入式半月刊》 第 40 期
    【Jmeter】提取和引用Token
    node.js云学堂微信小程序学习系统的设计与实现毕业设计源码011735
    【NAS备份】摆脱丢数据的噩梦,群晖备份硬核实战教程分享
    ElasticSearch安装部署,单节点部署,集群部署
    clickhouse使用clickhouse-keeper代替zookeeper
  • 原文地址:https://blog.csdn.net/qq_40143976/article/details/128131624