ZigZagK的博客
Unity Shader 入门
2024年4月18日 01:19
Unity
查看标签

前言

前置知识:基础的图形学

参考链接:

基础 Shader

一个基础的 shader ,纯色。

Shader "Custom/TestShader"{ // Shader 命名
    // 定义属性
    Properties{
        m_color("Color",Color)=(1,1,1,1) // 定义一个 Color 属性,可以在 Inspector 中调整
    }
    // 定义着色器,系统会选第一个可以用的 SubShader 进行着色
    SubShader{
        Pass{
            CGPROGRAM
            float4 m_color; // 从 Unity 的属性拿出值
            // 顶点函数(对模型顶点处理)
            #pragma vertex vert
            float4 vert(float4 v:POSITION):SV_POSITION{
                // POSITION: 表明 v 是顶点坐标,系统会传过来
                // SV_POSITION: 表明该函数返回值是剪裁空间下的坐标

                // UnityObjectToClipPos(v): 把 v 从模型空间->裁剪空间
                // 等价于 mul(UNITY_MATRIX_MVP, v); 乘上 MVP 矩阵
                return UnityObjectToClipPos(v);
            }
            // 片元函数(对屏幕像素处理)
            #pragma fragment frag
            float4 frag():SV_TARGET{
                // SV_TARGET: 着色的结果
                return m_color;
            }
            ENDCG
        }
    }
    // SubShader 全炸了的备用方案
    Fallback "VertexLit"
}

基础Shader

Unity 语义

从系统传给顶点函数的语义(模型空间下):

  • POSITION:顶点位置
  • NORMAL:顶点法线
  • TANGENT:顶点切线
  • TEXCOORD0~TEXCOORDn:纹理坐标(可以有好几套)
  • COLOR:顶点颜色

顶点函数传给片元函数的语义:

  • SV_POSITION:裁剪空间中的顶点坐标
  • COLOR0/COLOR1:用户自定义,可以传递一个float4
  • TEXCOORD0~TEXCOORD7:传递纹理坐标(可以是处理后的)

片元函数传给系统的语义:

  • SV_TARGET:片元函数返回的颜色
Shader "Custom/NormalShader"{
    Properties{
    }
    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 用结构体 struct ,可以从 Unity 接收更多信息
            // 成员需要有语义

            // 传给 vert 的信息
            struct a2v{
                float4 p:POSITION; // 顶点的位置
                float3 n:NORMAL; // 顶点的法线
                float2 tex:TEXCOORD0; // 顶点的第一套纹理坐标
            };
            // 从 vert 传给 frag 的信息
            struct v2f{
                float4 p:SV_POSITION; // 裁剪空间的坐标
                float3 color:COLOR0; // 可以用户定义的颜色
            };

            v2f vert(a2v v){
                static v2f f;
                f.p=UnityObjectToClipPos(v.p);
                f.color=v.n;
                return f;
            }
            fixed4 frag(v2f f):SV_TARGET{
                return fixed4(f.color,1); // 显示法线
            }
            ENDCG
        }
    }
    Fallback "VertexLit"
}

法线Shader

LightMode

定义LightMode后,shader 才能获得 Unity 的内置光照变量。

有了光照(平行光)之后就可以写一个Blinn-Phong模型的纯色 shader 。

例:ForwardBase,在前向渲染中使用,应用环境光、主方向光、顶点/SH 光源和光照贴图。

Shader "Custom/BlinnPhongShader"{
    Properties{
        m_kd("Diffuse",Color)=(1,1,1,1) // 物体表面漫反射颜色 (kd)
        m_ks("Specular",Color)=(1,1,1,1) // 镜面反射系数 (ks) 默认白色
        m_gloss("Gloss",Range(1,1000))=10 // 镜面反射指数
    }
    SubShader{
        Pass{
            // 定义 LightMode
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            // Unity 内置变量头文件
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            fixed4 m_kd;
            fixed4 m_ks;
            half m_gloss;

            struct a2v{
                float4 p:POSITION;
                float3 n:NORMAL;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 p:SV_POSITION;
                float3 worldP:COLOR0; // 传递世界空间下的坐标
                float3 worldN:COLOR1; // 传递世界空间下的法线
            };

            // Unity 给的光源在世界空间,因此需要把模型空间转换到世界空间
            v2f vert(a2v v){
                static v2f f;
                f.p=UnityObjectToClipPos(v.p);
                // 把位置从模型空间转换到世界空间
                f.worldP=mul(unity_ObjectToWorld,v.p);
                // 把法线从模型空间转换到世界空间
                f.worldN=normalize(UnityObjectToWorldNormal(v.n));
                return f;
            }
            // 用 Blinn-Phong 模型对像素着色
            fixed4 frag(v2f f):SV_TARGET{
                // 环境光
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;

                // 漫反射
                float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP)); // 光照方向(着色点到光源)
                fixed3 diffuse=_LightColor0.rgb*m_kd.rgb*max(0,dot(lightDir,f.worldN));

                // 镜面反射
                float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP)); // 观察方向(着色点到相机)
                float3 halfDir=normalize(lightDir+viewDir); // 半程向量
                fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);

                return fixed4(ambient+diffuse+specular,1); // 最终着色
            }
            ENDCG
        }
    }
    Fallback "VertexLit"
}

平行光Blinn-Phong模型着色

纹理贴图

纹理缩放和偏移之后,纹理坐标 $(u,v)$ 会超出 $[0,1]$ 范围,根据设置不同有不同处理方式,例如:

  • Repeat:循环,坐标对 $1$ “取模”
  • Clamp:剪切,小于 $0$ 变为 $0$ ,大于 $1$ 变为 $1$

还有其他属性,比如插值模式,可以指定不插值,双线性插值,三线性插值。

Shader "Custom/TextureShader"{
    Properties{
        m_MainTex("Main Tex",2D)="white"{} // 物体表面贴图 (kd)
        m_ks("Specular",Color)=(1,1,1,1) // 镜面反射系数 (ks) 默认白色
        m_gloss("Gloss",Range(1,1000))=10 // 镜面反射指数
    }
    SubShader{
        Pass{
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D m_MainTex;
            // _ST 后缀是固定的,表示贴图的四个参数
            // 前两个值为缩放Tilling,后两个值为偏移Offset
            float4 m_MainTex_ST;
            fixed4 m_ks;
            half m_gloss;

            struct a2v{
                float4 p:POSITION;
                float3 n:NORMAL;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 p:SV_POSITION;
                float3 worldP:COLOR0;
                float3 worldN:COLOR1;
                float2 uv:TEXCOORD0; // 传递纹理坐标
            };

            v2f vert(a2v v){
                static v2f f;
                f.p=UnityObjectToClipPos(v.p);
                f.worldP=mul(unity_ObjectToWorld,v.p);
                f.worldN=normalize(UnityObjectToWorldNormal(v.n));
                // 乘上贴图缩放倍数(xy),然后加上偏移(zw)
                f.uv=v.uv*m_MainTex_ST.xy+m_MainTex_ST.zw;
                return f;
            }

            fixed4 frag(v2f f):SV_TARGET{
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;

                float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP));
                // 使用纹理贴图颜色代替漫反射系数
                float3 tex_color=tex2D(m_MainTex,f.uv.xy);
                fixed3 diffuse=_LightColor0.rgb*tex_color*max(0,dot(lightDir,f.worldN));

                float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP));
                float3 halfDir=normalize(lightDir+viewDir);
                fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);

                return fixed4(ambient+diffuse+specular,1);
            }
            ENDCG
        }
    }
    Fallback "VertexLit"
}

ZigZagK贴图(?)Shader

渲染队列 ZTest ZWrite

渲染队列Queue:Unity 会按照渲染队列的顺序渲染,Queue越小越先渲染。

用法:Tags { "Queue" = "[QueueName]+[Offset]" }QueueName是预设的一些队列名,Offset是整数,表示预设的值加上偏移。

QueueName功能
Background1000背景渲染队列
Geometry2000不透明渲染队列(默认)
AlphaTest2450透明度测试渲染队列
Transparent3000半透明渲染队列
Overlay4000覆盖效果渲染队列

ZTest:深度测试,即像素上和当前的深度缓存进行比较的方式,满足条件的才进行绘制。

  • Less:深度 $<$ 缓存
  • LEqual(默认):深度 $\le$ 缓存
  • Equal:深度 $=$ 缓存
  • GEqual:深度 $\ge$ 缓存
  • Greater:深度 $>$ 缓存
  • NotEqual:深度 $\not=$ 缓存
  • Always:任何情况都绘制

ZWrite:深度写入,即通过深度测试之后,是否把深度写入缓存。通常不透明物体要写入,而半透明物体不写入(因为半透明即使更近也不会遮挡更远的物体,但能被更远的遮挡)。

  • On:启用深度写入
  • Off:不启用深度写入

Blend:混合模式,Blend SrcAlpha OneMinusSrcAlpha是最普通的透明度混合模式。

Shader "Custom/TransparentShader"{
    Properties{
        m_kd("Diffuse",Color)=(1,1,1,1)
        m_ks("Specular",Color)=(1,1,1,1)
        m_gloss("Gloss",Range(1,1000))=10
        m_alpha("Alpha",Range(0,1))=0.5
    }
    SubShader{
        Tags{
            "Queue"="Transparent" // 将渲染队列改为 Transparent
            "IgnoreProjector"="True" // 忽略投影器(投影器不兼容半透明的)
            "RenderType"="Transparent" // 给着色器分类为 Transparent(可以在 C# 脚本中选取)
        }
        Pass{
            Tags {"LightMode"="ForwardBase"}

            // 对半透明对象禁用深度写入
            // 这样半透明对象只会进行深度测试(能被正确遮挡),不进行深度写入(不会挡住其他的)
            ZWrite Off
            ZTest LEqual
            // 一般的 Alpha 混合模式
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag

            fixed4 m_kd;
            fixed4 m_ks;
            half m_gloss;
            fixed m_alpha;

            struct a2v{
                float4 p:POSITION;
                float3 n:NORMAL;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 p:SV_POSITION;
                float3 worldP:COLOR0;
                float3 worldN:COLOR1;
            };

            v2f vert(a2v v){
                static v2f f;
                f.p=UnityObjectToClipPos(v.p);
                f.worldP=mul(unity_ObjectToWorld,v.p);
                f.worldN=normalize(UnityObjectToWorldNormal(v.n));
                return f;
            }

            fixed4 frag(v2f f):SV_TARGET{
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;

                float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP));
                fixed3 diffuse=_LightColor0.rgb*m_kd.rgb*max(0,dot(lightDir,f.worldN));

                float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP));
                float3 halfDir=normalize(lightDir+viewDir);
                fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);

                return fixed4(ambient+diffuse+specular,m_alpha); // 透明度为 m_alpha
            }
            ENDCG
        }
    }
    Fallback "VertexLit"
}

半透明Shader

版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!