0%

Unity Shader君の初上手

Shader

Unity——Shader入门

pinxSde.png

新建Shader并使用VS+ShaderLabVS进行编译,默认的Shader代码是这样的:

Shader "Unlit/MyShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fog

        #include "UnityCG.cginc"

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

        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
        };

        sampler2D _MainTex;
        float4 _MainTex_ST;

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

        fixed4 frag (v2f i) : SV_Target
        {
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);
            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }
        ENDCG
    }
}

}


Shader "Unlit/MyShader"

可以设置新建Shader的路径

 

属性

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
}

用来向监视器里面展示可控数据的代码段

属性名("在材质球(*Material*)上展示的名称",属性类型) = 默认值

pinxPJA.png

 

子着色器(SubShader)

SubShader用于将 Shader 对象分成多个部分,分别兼容不同的硬件、渲染管线和运行时(Runtime)设置

有点像条件编译,就是Unity会自动遍历所有的SubShader直到找到第一个能用到的着色器,如果找不到,

一个子着色器包含以下内容:

  • 与其他设备设置兼容信息
  • Tags——提供有关SubShader相关信息的键值对
  • Pass——通道

 

通道

Shader的基本元素,一个Shader至少包含一个通道

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
        #include "UnityCG.cginc"
**********************************************************
        struct appdata
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };
**********************************************************
**********************************************************
        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
        };
**********************************************************
        sampler2D _MainTex;
        float4 _MainTex_ST;
**********************************************************
        v2f vert (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            UNITY_TRANSFER_FOG(o,o.vertex);
            return o;
        }
**********************************************************
**********************************************************
        fixed4 frag (v2f i) : SV_Target
        {
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);
            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }
**********************************************************
        ENDCG
    }
  • appdata指CPU向Shader提供的模型数据
  • v2f是自定义的数据结构体,是顶点着色器输出的数据也是片元着色器输入的数据
  • vert是顶点着色器处理函数
  • frag是片元着色器处理函数

Shader的处理流程和渲染管线是相像的

语义解释

piui4UK.png

CPU->顶点着色器

 

piuioCD.md.png

顶点着色器->片元着色器

 

piui7gH.md.png

片元着色器->外界

 

实验:手动建立标准光照Shader

 

建立默认Shader与默认材质,挂载到物体上就是一个普通的白球(默认无光照)

piuiObt.png

尝试利用Blinn-Phong光照模型为其增加检测光照的效果(图中已经添加环境光与直线光)

也就是将这个unity默认的无光照着色器手动编译成标准着色器

从之前学到的知识,Blinn-Phong模型的最终结果就是环境光+漫反射+高光

环境光在unity中设置

漫反射与高光是由片元着色器处理,于是我们聚焦到frag上

可以看到frag的实现非常简单,从_MainTex中获取颜色,利用i.uv提供的坐标,再将颜色坐标信息返回给col。(那个什么FOG是利用untiy的api启用雾效)

	fixed4 frag (v2f i) : SV_Target
        {
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);
            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }

要加上环境光+漫反射+高光,也就是最终返回的结果最好是“final=col×(_Diffuse×diffuse+pow(specular,__Intensity)+ambient)"

其中diffuse为漫反射,specular为高光,ambient为环境光——为了区分把公式特征也写进去了,实际编写过程取决于编写者。

 

漫反射

fixed3 worldNormal = normalize(i.normal);
fixed3 worldPos = i.worldPos;
fixed3 lightDir = normalize(_WorldSpaceLightPos0 - worldPos);
float diffuse = max(0, dot(worldNormal, lightDir));

 

高光

fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 lightDir = normalize(_WorldSpaceLightPos0 - worldPos);
fixed3 halfDir = normalize(lightDir + viewDir);
float specular = max(0, dot(worldNormal, halfDir));
specular = pow(specular, _Intensity);

 

将这两个值的最终结果结合一下返回给finalColor:

piujGxx.png

有了能接受光照的雏形,漫反射也能通过光照方向改变而改变光亮和高光方向,但是模拟光照的技术瓶颈还是取决于硬件支持,也就是说漫反射只能反反射单一光线,而不能反射其他物体反射出的光线,所以看起来并不真实。

一般这种”二手光“直接用统一的环境光来代替

fixed4 ambient = (UNITY_LIGHTMODEL_AMBIENT.xyz,1);

 

但是加上环境光却出现了问题:

piujReS.png

物体光线又回到了最初的起点,就好像环境光将光线都扰乱了一样。

 

最后尝试了很多种方式,发现竟然只是代码的语法错误

piuxZHU.png

float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed4 finalColor = col * (_Diffuse * diffuse + specular ) + fixed4(ambient, 1);

如上,直接通过在fixed3后面加上标量1的方式是错误的,必须新建以fixed4(ambient,1)的格式新建fixed类型的向量——最终还是因为我没有正式安装CG代码的编译环境,所以并没有报错。。。

 

但是新的问题又出现了,那就是这个环境光只相应纯色类型的环境光,而不能相应天空盒类型环境光。

在自定义着色器中获取环境颜色 - Unity Forum

在Unity社区中寻找到有人在讨论这个问题,最终把代码改成

half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
fixed4 finalColor = col * (_Diffuse * diffuse + specular ) + fixed4(ambient, 1);

最终形成天空盒也能充当环境光的效果。

 

优化

最终很遗憾地发现最终效果仍然很奇怪,因为最后才发现所使用的unity的光照是太阳光而不是点光源(太阳光默认直线平行,而点光源有距离缩减且角度都不同)

所以更改代码逻辑如下:

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Diffuse("Diffuse",Range(0,1)) = 1
        _Intensity("Intensity",float) = 1

    _IntensityHigh("IntensityHigh",float) = 1
    _Specluar("Specular",Color) = (1,1,1,1) 
}
SubShader
{
    Tags { "RenderType"="Opaque" }
    Tags {"LightMode" = "ForwardBase"}
    LOD 100

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        // make fog work
        #pragma multi_compile_fog
        #include "Lighting.cginc"
        #include "UnityCG.cginc"

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

        struct v2f
        {
            float2 uv : TEXCOORD0;
            UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
            float3 worldPos : TEXCOORD1; 
            float3 normal : TEXCOORD2;
        };


        sampler2D _MainTex;
        float4 _MainTex_ST;
        v2f vert(appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            UNITY_TRANSFER_FOG(o, o.vertex);

            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            o.normal = UnityObjectToWorldNormal(v.normal);

            return o;
        }
        float _Intensity;
        float _IntensityHigh;
        float _Diffuse;
        fixed4 frag(v2f i) : SV_Target
        {
            // Sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);

            // Calculate the lighting
            fixed3 worldNormal = normalize(i.normal);
            fixed3 worldPos = i.worldPos;
            fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

            fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
            fixed3 halfDir = normalize(lightDir + viewDir);
            fixed4 lightColor = _LightColor0;

            float diffuse = _Intensity*max(0, dot(worldNormal, lightDir));
            float specular = max(0, dot(worldNormal, halfDir));
            specular = pow(specular, _IntensityHigh);

            half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
            fixed4 finalColor = col * _Diffuse *( diffuse +specular ) + fixed4(ambient, 1);

            // Apply fog
            UNITY_APPLY_FOG(i.fogCoord, finalColor);

            return finalColor;
        }
        ENDCG
    }
}

}

高光反射的p值比我想象中的大很多,突然想起闫令祺老师说过一般要达到128才能接近现实的效果。。

piKIydI.png

piKIgFP.png

与官方的标准光照着色器对比——你可以猜一下哪个是我写的

 

后记

从别人的博客上看见使用半兰伯特(Half Lambert)模型来优化漫反射。

就是单纯变更一下diffuse:

float diffuse = 0.5*_Intensity*max(0, dot(worldNormal, lightDir))+0.5;

这样可以去掉环境光也能形成很好的效果:

piKIbF0.png

但是我其实觉得这是一种投机取巧的做法——虽然看起来确实像摸像样了一点,但不能跟踪天空盒,而且和其他反射严重割裂。