• Unity3D中的Shader


    简单的说,Shader是为渲染管线中的特定处理阶段提供算法的一段代码。Shader是伴随着可编程渲染管线出现的,从而可以对渲染过程加以控制。

    1. Unity提供了很多内建的Shader,这些可以从官网下载,打开looking for older version的链接就能看到Build-in shaders。选择合适的Shader很重要,以下是开销从低到高的排序:

    (1)Unlit:仅使用纹理颜色,不受光照影响

    (2)VertexLit:顶点光照

    (3)Diffuse:漫反射

    (4)Specular:在漫反射基础上增加高光计算

    (5)Normal mapped:法线贴图,增加了一张法线贴图和几个着色器指令

    (6)Normal Mapped Specular:带高光法线贴图

    (7)Parallax Normal Mapped:视差法线贴图,增加了视差贴图的计算开销

    (8)Parallax Normal Mapped Specular:带高光视差法线贴图

      对于现在流行的移动平台游戏,Unity提供了几种专门的着色器放在Shader->Mobile下,它们是专门优化过的。

    2. 在Unity中,可以编写3种类型的Shader:

    表面着色器(Surface Shaders):最常用的Shader,可以与灯光、阴影、投影器交互,以Cg/HLSL语言进行编写,不涉及光照时尽量不要使用。

    顶点和片段着色器(Vertex and Fragment Shaders):全屏图像效果,代码比较多,以Cg/HLSL编写,难以和光照交互。

    固定功能管线着色器(Fixed Function Shaders):游戏要运行在不支持可编程管线的老旧机器上时,需要用ShaderLab语言来编写。

    无论编写哪种Shader,实际的Shader代码都需要嵌入ShaderLab代码中,Unity通过ShaderLab代码来组织Shader结构。

    下面是我新建的一个Shader的默认内容:

    Shader "Custom/TestShader" {
        Properties {
            _MainTex ("Base (RGB)", 2D) = "white" {}
        }
        SubShader {
            Tags { "RenderType"="Opaque" }
            LOD 200
            
            CGPROGRAM
            #pragma surface surf Lambert
    
            sampler2D _MainTex;
    
            struct Input {
                float2 uv_MainTex;
            };
    
            void surf (Input IN, inout SurfaceOutput o) {
                half4 c = tex2D (_MainTex, IN.uv_MainTex);
                o.Albedo = c.rgb;
                o.Alpha = c.a;
            }
            ENDCG
        } 
        FallBack "Diffuse"
    }

    Properties:用来定义着色器中使用的贴图资源或者数值参数等,这里定义了一个Base (RGB)的2D纹理。

    SubShader:一个着色器包含的一个或多个子着色器。Unity从上到下遍历子着色器,找到第一个能被设备支持的着色器。

    FallBack:备用着色器,一个对硬件要求最低的Shader名字。

    (1)Properties定义的属性

    名称("显示名称", Vector) = 默认向量值,一个四维向量
    名称("显示名称", Color) = 默认颜色值,一个颜色(取值0~1的四维向量)属性
    名称("显示名称", Float) = 默认浮点数值,一个浮点数
    名称("显示名称", Range(min,max)) = 默认浮点数值,一个浮点数,取值min~max
    名称("显示名称", 2D) = 默认贴图名称{选项},一个2D纹理属性
    名称("显示名称", Rect) = 默认贴图名称{选项},一个矩形纹理属性(非2的n次幂)
    名称("显示名称", Cube) = 默认贴图名称{选项},一个立方体纹理属性

    选项指的是一些纹理的可选参数,包括:

    TexGen:纹理生成模式,可以是ObjectLinear、EyeLinear、SphereMap、CubeReflect、CubeNormal中的一种。如果使用了自定义的顶点程序,这些参数会被忽略。

    LightmapMode:纹理将受渲染器的光照贴图参数影响。纹理将不会从材质中获取,而是取自渲染器的设置。

    示例如下:

    Properties {
       _RefDis ("Reflect Distance", Range(0, 1)) = 0.3 //范围数值
       _Color ("Reflect Color", Color) = (.34, .85, .92, 1) //颜色
       _MainTex ("Reflect Color", 2D) = "white"{} //纹理
    }

    常用的变量类型如下:

    颜色和向量:float4, half4, fixed4
    范围和浮点数:float, half, fixed
    2D纹理贴图:sampler2D
    Cubemap:samplerCUBE
    3D纹理贴图:sampler3D

    (2)SubShader,子着色器由标签(可选)、通用状态(可选)、Pass列表组成。使用子着色器渲染时,每个pass都会渲染一次对象,所以应尽量减少Pass数量。

    (3)Category,分类用于提供让子着色器继承的命令。

    3. 表面着色器,使用Cg/HLSL编写,然后嵌在ShaderLab的结构代码中使用。仅需编写最关键的表面函数,其余代码由Unity生成,包括适配各种光源类型、渲染实时阴影以及集成到前向/延迟渲染管线中。如果你需要的效果与光照无关,最好不要使用表面着色器,否则会进行很多不必要的光照计算。使用#pragma surface...来指明是一个表面着色器。输入结构体Input一般包含必须的纹理坐标,还可以在输入结构中加入一些附加数据。

    CGPROGRAM
    #pragma surface surf Lambert
    
    sampler2D _MainTex;
    fixed4 _Color;
    
    struct Input {
        float2 uv_MainTex;
    };
    
    void surf (Input IN, inout SurfaceOutput o) {
        half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
        o.Albedo = c.rgb;
        o.Alpha = c.a;
    }
    ENDCG

    SurfaceOutpu描述了表面的各种参数,它的标准结构如下:

    struct SurfaceOutput{
        half3 Albedo;    //反射光
        half3 Normal;    //法线
        half3 Emission;  //自发光
        half Specular;   //高光
        half Gloss;      //光泽度
        half Alpha;      //透明度
    }

    4. 顶点和片段着色器,运行于具有可编程渲染管线的硬件上,它包括顶点程序和片段程序。使用该着色器渲染时,固定功能管线将会关闭,即编写好的顶点程序替代原有的3D变换、光照、纹理坐标生成等功能,片段程序会替换掉SetTexture命令中的纹理混合模式。代码使用Cg/HLSL编写,放在Pass命令中,格式如下:

    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
    
            //cg code
    
            ENDCG
        }
    }

     编译命令说明如下:

    #pragma vertex name-----------------------------将函数name的代码编译成顶点程序
    #pragma fragment name---------------------------将函数name的代码编译成片段程序
    #pragma geometry name---------------------------将函数name的代码编译成DX10的几何着色器
    #pragma hull name-------------------------------将函数name的代码编译成DX11的hull着色器
    #pragma domain name-----------------------------将函数name的代码编译成DX11的domain着色器
    #pragma fragmentoption option-------------------添加选项到编译的OpenGL片段程序,对于顶点程序或编译目标不是OpenGL的无效
    #pragma target name-----------------------------设置着色器的编译目标
    #pragma only_renderers space separated names----仅编译到指定的渲染平台
    #pragma exclude_renderers space separated names-不编译到指定的渲染平台
    #pragma glsl------------------------------------为桌面系统的OpenGL进行编译时,将Cg/HLSL代码转换成GLSL代码
    #pragma glsl_no_auto_normalization--------------编译到移动平台GLSL时,关闭顶点着色器中对法线和切线进行自动规范化

    示例代码,使用命令:

    Shader "Custom/Shader1" {
        Properties {
            _Color("Main Color", Color) = (1,1,1, 0.5)
            _SpecColor("Spec Color", Color) = (1,1,1,1)
            _Emission("Emmisive Color", Color) = (0,0,0,0)
            _Shininess("Shininess", Range(0.01, 1)) = 0.7
            _MainTex("Base (RGB)", 2D) = "white" {}
        }
    
        SubShader {
            Pass{
                Material{
                    Diffuse[_Color]
                    Ambient[_Color]
                    Shininess[_Shininess]
                    Specular[_SpecColor]
                    Emission[_Emission]
                }
                Lighting On
                SeparateSpecular  On
                SetTexture[_MainTex]{
                    constantColor[_Color]
                    Combine texture * primary DOUBLE, texture * constant
                }
            }
        } 
    
        FallBack "Diffuse"
    }

     示例代码,使用Cg。其中的包含文件可以在/Data/CGIncludes/目录下找到。

    Shader "Custom/Shader2" {
        
        //定义属性(变量)
        Properties {
            _MainTex ("Texture", 2D) = "white" {} //纹理
            _Color ("Main Color", Color) = (1,1,1,0.5) //颜色
        }
    
        //子着色器
        SubShader {
    
            //每个Pass中,对象几何体都被渲染一次
            Pass{
    
                CGPROGRAM //Cg代码开始
                #pragma vertex vert //将函数vert编译为顶点程序
                #pragma fragment frag //将函数frag编译为片段程序
    
                //包含一个内置的cg文件,提供了常用的声明和函数,比如appdata_base结构
                #include "UnityCG.cginc"
    
                float4 _Color; //变量,颜色的向量表示
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                //定义一个结构体v2f
                struct v2f{
                    float4 pos:SV_POSITION;
                    float2 uv:TEXCOORD0;
                };
    
                //顶点处理程序
                v2f vert(appdata_base v)
                {
                    v2f o;
                    //3D坐标被投影到2D窗口中,与矩阵Model-View-Projection相乘
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                    return o;
                }
    
                //片段处理程序
                half4 frag(v2f i):COLOR
                {
                    half4 texcol = tex2D(_MainTex, i.uv);
                    //自定义颜色_Color与纹理的融合
                    return texcol * _Color;
                }
                
                ENDCG //Cg代码结束
            }
            
        }
    
        //备用着色器
        FallBack "VertexLit"
    }

    上面的例子用到了一些内置的变量,有下面这些:

    UNITY_MATRIX_MVP--------------------------当前的model*view*projection矩阵
    UNITY_MATRIX_MV---------------------------当前的model*view矩阵
    UNITY_MATRIX_V----------------------------当前的view矩阵
    UNITY_MATRIX_P----------------------------当前的projection矩阵
    UNITY_MATRIX_VP---------------------------当前的view*projection矩阵
    UNITY_MATRIX_T_MV-------------------------model*view矩阵的转置矩阵
    UNITY_MATRIX_IT_MV------------------------model*view矩阵的转置逆矩阵
    UNITY_MATRIX_TEXTURE0
    UNITY_MATRIX_TEXTURE1
    UNITY_MATRIX_TEXTURE2
    UNITY_MATRIX_TEXTURE3---------------------纹理变换矩阵
    UNITY_LIGHTMODEL_AMBIENT------------------当前的环境光颜色

    下面是官方文档中的一个例子,可以产生不同颜色交错的效果:

    Shader "Custom/Bars" {
        SubShader {
            Pass {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct vertOut {
                    float4 pos:SV_POSITION;
                    float4 scrPos;
                };
    
                vertOut vert(appdata_base v) {
                    vertOut o;
                    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                    //ComputeScreenPos将返回片段着色器的屏幕位置scrPos
                    o.scrPos = ComputeScreenPos(o.pos);
                    return o;
                }
    
                fixed4 frag(vertOut i) : COLOR0 {
                    float2 wcoord = (i.scrPos.xy/i.scrPos.w);
                    fixed4 color;
    
                    //改变50可以调整间距
                    if (fmod(50.0*wcoord.x,2.0)<1.0) {
                        color = fixed4(wcoord.xy,0.6,1.0);//这里可以改变颜色
                    } else {
                        color = fixed4(0.1,0.3,0.7,1.0);//这里可以改变颜色
                    }
                    return color;
                }
    
                ENDCG
            }
        }
    }

    下面的例子来自官方手册,棋盘格效果:

    Shader "Custom/Chess" {
    
        SubShader {
    
            Pass {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                //输入顶点结构体,包含位置和颜色
                struct vertexInput {
                    float4 vertex : POSITION;
                    float4 texcoord0 : TEXCOORD0;
                };
    
                //片段结构体,包含位置和颜色
                struct fragmentInput{
                    float4 position : SV_POSITION;
                    float4 texcoord0 : TEXCOORD0;
                };
    
                //顶点处理
                fragmentInput vert(vertexInput i){
                    fragmentInput o;
                    o.position = mul (UNITY_MATRIX_MVP, i.vertex);
                    o.texcoord0 = i.texcoord0;
                    return o;
                }
    
                //片段处理
                float4 frag(fragmentInput i) : COLOR {
                    float4 color;
                    //fmod用来取余数,物体表面X方向被分成了8/2=4个区间
                    //X坐标对2求余,所以这里用1来作为比较,黑、白各占一半
                    if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0  ){
                        if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 )
                        {
                            color = float4(1.0,1.0,1.0,1.0);//白色
                        } else {
                            color = float4(0.0,0.0,0.0,1.0);//黑色
                        }
                    } else {
                        if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 )
                        {
                            color = float4(1.0,1.0,1.0,1.0);//白色
                        } else {
                            color = float4(0.0,0.0,0.0,1.0);//黑色
                        }
                    }
                    return color;
                }
    
                ENDCG
            }
        }
    
        FallBack "Diffuse"
    }

    相同效果的简化代码:

    Shader "Custom/ChessOpt" {
        SubShader {
            Pass {
                CGPROGRAM
                #pragma vertex vert_img
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                float4 frag(v2f_img i) : COLOR {
                    bool p = fmod(i.uv.x*8.0,2.0) < 1.0;
                    bool q = fmod(i.uv.y*8.0,2.0) > 1.0;
    
                    return float4(float3((p && q) || !(p || q)),1.0);
                }
                ENDCG
            }
        }
    }

    上一个例子中有个texcoord0变量,它的x和y的值都是从0到1的,刚好映射到一张特殊的纹理上。

  • 相关阅读:
    [原创] 为Visio添加公式编辑器工具栏按钮
    Matlab 图论最短路问题模型代码
    「SCOI2011」「LOJ #2441」 棘手的操作
    「APIO2012」「Luogu P1552」派遣
    「JLOI2015」「LOJ #2107」城池攻占
    「Wallace 笔记」LOJ 「数列分块入门」 9 题题解
    「Codeforces 235C」Cyclical Quest
    「Codeforces 1037H」Security
    「UVA 11468」Substring
    「LOJ #2102」「TJOI2015」弦论
  • 原文地址:https://www.cnblogs.com/shihui142857/p/3848580.html
Copyright © 2020-2023  润新知