每次都能让人头大的 Shader -- 从整合说起

  之前也说过引擎能不能提供一个一般化的开发环境给使用者, 这样使用者只需要指定他要的开发环境, 就能用它最熟悉的方式去写Shader了.

  从提供者的角度来看, 因为有太多的应用场景无法确定, 所以提供无数多套的设定才能满足需求, 比如你要用来做一般Shader还是用来做后处理用, 后处理中的顶点位置在片元阶段的插值得到的就绝对不是所显示的对象的世界坐标.

  从使用者的角度来说, 在经过多平台, 多版本的迭代之后, 要维护的代码量只会越来越多, 到最后简直不知道怎样去维护了, 举个例子 : 

0. 变量声明

    sampler2D _MainTex;    
    half4 _MainTex_ST;
    half4 _MainTex_TexelSize;

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

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

1. 初始代码

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        return o;
    }

2. UV 有拉伸位移的代码 -- 多用于物体渲染

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        return o;
    }

3. 我要用在VR上! -- 官方后处理效果可见

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = UnityStereoScreenSpaceUVAdjust(v.uv, _MainTex_ST);
        return o;
    }

4. 我要用在后处理上! -- 后处理对于屏幕起始点敏感

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
#if UNITY_UV_STARTS_AT_TOP  
        if (_MainTex_TexelSize.y < 0)
        {
            o.uv.y = 1 - o.uv.y;
        }
#endif
        return o;
    }

5. 后处理UV有拉伸位移! -- 我都不知道这段代码对不对了! (后处理的缩放系数始终为1, 偏移始终为0, 被引擎强制处理了)

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
#if UNITY_UV_STARTS_AT_TOP  
        if (_MainTex_TexelSize.y < 0)
        {
            o.uv.y = 1 - o.uv.y;
        }
#endif
        return o;
    }

 

 6. 我要继续用到VR的后处理上! -- 我已经飘了!

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = UnityStereoScreenSpaceUVAdjust(v.uv, _MainTex_ST);
            
#if UNITY_UV_STARTS_AT_TOP
    if (_MainTex_TexelSize.y < 0.0)     o.uv.y = 1.0 - o.uv.y; #endif return o; }

 

  这些不是随便臆想出来的, 它的官方代码也是一样的, 随着版本的更迭提供了更多的目标平台, 所以官方的代码也在一直变化, 这只是单一个UV的变量问题, 其它问题比这多的多......有一份代码就要改一份, 各种各样Shader用的多的项目组估计要升天的.

  像上面的UV变量, 正常来说你有 _MainTex_ST 的设定的话, 一定会用 TRANSFORM_TEX(v.uv, _MainTex); 的吧, 或者我使用VR 并且使用Single Pass模式的话, 肯定要去调用 UnityStereoScreenSpaceUVAdjust(v.uv, _MainTex_ST); 的吧, 就不能在顶点阶段传入的时候给我自动计算好吗, 每次还要开发者自己去加宏判断平台吗, 这些在编辑器下加个平台选项不就好了吗.

  像 UNITY_UV_STARTS_AT_TOP 这种判断, 应该是每个屏幕后处理都要加的吧, 看看它出现的次数, 如果给使用者设定这个Shader是使用D3D或者OpenGL标准的话, 我们就能明确知道屏幕(0,0)点的位置在哪了, 习惯opengl的就从左上角开始计算, 习惯D3D的从左下角开始计算, 这些不就又能省掉了么, 太能折腾了.

 

  (2020.03.10)

  今天发现似乎平台Graphics APIs是可以选择的, 就在PlayerBuild Settings里面, 通过手动添加目标API就可以设定了:


  运行时它会从上往下选择当前平台可用的API, 我们来测试一下吧, 为了减少变量, 只留目标API, 先测试一下OpenGL的:

  用一个简单的后处理Shader来跑一下

using UnityEngine;

public class EditorCamera : MonoBehaviour
{
    public Shader shader;
    private Material m_mat;

    void Start()
    {
        if(shader)
        {
            m_mat = new Material(shader);
        }
    }
    
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(m_mat)
        {
            Graphics.Blit(source, destination, m_mat);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}
Shader "Unlit/StandardTest"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float t : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);                
#if UNITY_UV_STARTS_AT_TOP 
                o.t = 1.0;
#else
                o.t = 0.2;
#endif
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return i.t > 0.5 ? 1 : 0;
            }
            ENDCG
        }
    }
}

  很简单, OpenGL屏幕是从左下角开始的, D3D从屏幕左上角开始的.

  运行起来看看, 现在是OpenGL标准, 可见屏幕是黑的, 没有进到UNITY_UV_STARTS_AT_TOP宏里面 : 

 

  下来切换API标准, 使用D3D11 : 

  代码不用改, 直接重新运行看看, 屏幕是白色的了 :

 

  所以这里的设置是可以进行不同API的测试的, 跟我前面说的差不多, 只是不能在运行时进行强行设置, 并且只选择一个API的话, 在不使用这个API的平台上应该是不能转换的了, 前面说的是希望它能做成IL代码一样, 把所有的API当成不同平台的底层代码, 把Shader编译成中间代码, 这样就没有"硬"转换了.

 

 

  又碰到个问题, 使用Surface Shader的模板写多个pass的情况, 不能把surface框在Pass里面, 会报错:

[Parse error: syntax error, unexpected TOK_PASS, expecting TOK_SETTEXTURE or '}'......]

  错误写法:

SubShader
{
    Pass
    {
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        ......
        ENDCG
    }    
    Pass
    {
        CGPROGRAM
        ......
        ENDCG
    }
}

  直接在原代码上添加新Pass就行了:

SubShader
{
    CGPROGRAM
    #pragma surface surf Standard fullforwardshadows
    ......
    ENDCG
    
    Pass
    {
        CGPROGRAM
        ......
        ENDCG
    }
}

 

posted @ 2019-11-28 15:07  tiancaiKG  阅读(752)  评论(0编辑  收藏  举报