本系列文章由CSDN@萌萌的一天 出品,未经博主允许不得转载。
接触shader编程已经有很长一段时间,最近有很多初学者问我许多关于Unity3D Shader方面的问题,我打算写一篇关于shader教学的文章,方便解决大家初入图像编程~大坑时遇到的麻烦。
简单来说,学习Shader Programming就是学习如何利用GPU的力量。刚开始我会详细解释shader的作用、创建方式、基本语法等等,之后我会详细教给大家如何写出一个简单有效的shader案例。
Shader并不神秘,它只是很小的一段程序,包含着数学计算和算法模型,运行在计算机的图像管道层面,告诉计算机图像上的每一个像素应该怎样显示在屏幕上,它将一个个输入的网格绘制到屏幕之上,就能得到一个Material(材质),之后通过渲染器进行一系列的渲染输出操作后,我们就可以观察到客观的物体。Shader一般被称作着色器,它可以通过改变自身的属性来改变材质渲染到屏幕上的效果。以下是3中不同的Shader渲染到同一个材质上的例子:
Unity3D中编写的Shader是通过.shader文件来进行实现的,它使用ShaderLab语言,极其类似Cg/HLSL。ShaderLab语言很类似于C语言,如果你有不错的C/C++编程功底的话,学习ShaderLab将会很容易。
Shader分为三种类型:
1、Fixed Function Shader( 固定渲染管线着色器)
2、Surface Shader(表面着色器)
3、Vertex Shader&Fragment Shader (顶点着色器&片段着色器)
接下来我们在Unity中创建Shader和Material,详细介绍每一种Shader的创建,用法等。
首先,我们新建一个场景,在场景下的Project面板中新建一个叫做“Shader”的文件夹,方便我们管理今后创建的Shader文件。然后右键点击空白处--->Creat--->Shader,这样操作后,我们就能得到一个名为“NewShader”的.shader类型的文件了。
之前说过,shader文件只有依附在Material,我们才能看到正确渲染出来的图像,所以我们还应创建一个新的Material,和之前类似,右键点击空白处--->Creat--->Material,创建一个新的Material,命名为“TestMaterial_1”。
那么怎么将新创建的Shader赋予到Material上呢,我们点击“TestMaterial_1”,观察它的Inspector面板,在Shader-->Custom下就能找到我们刚刚创建的"NewShader"文件了。
双击Shader文件,就能看到Shader的基本写法。总体来说,我们可以概括Shader的框架写法:
Shader "name" { [Properties] Subshaders [Fallback] [CustomEditor] }
用图形可以这样表示:
用编辑器打开刚才的“NewShader”文件后,代码大概是如下的样子(可能会因为Unity版本略有不同):
Shader "Custom/NewShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse"
_Name("Display Name", type) = defaultValue[{options}]我们可以定义如下的类型:
1、name ("display name", Range (min, max)) = number
自定义浮点型属性,在面板上通过介于最大/最小值间的滑条修改
2、name ("display name", Float) = number
自定义浮点型属性,在面板上可以手动输入浮点值
3、name ("display name", Int) = number
自定义整型数值,在面板上可以手动输入整型数值
4、name ("display name", Color) = (number,number,number,number)
自定义颜色属性,在面板上可以打开颜色选择器
5、name ("display name", Vector) = (number,number,number,number)
自定义四维向量数值,在面板上可以手动输入四维向量(Float类型)
6、name ("display name", 2D) = "defaulttexture" {}
自定义2D Texture
7、name ("display name", Cube) = "defaulttexture" {}
自定义Cubemap
8、name ("display name", 3D) = "defaulttexture" {}
自定义3D Texture
Tags { "RenderType"="Opaque" }
Tags{ "TagName1" = "Value1" "TagName2" = "Value2" }
Transparent
半透明着色器,包括绝大多数粒子Shader,地形Shader等;TransparentCutout半透明着色器补充类(Cutout Shader类型不允许绘制部分透明的区域),包括双通道(Pass)植被ShaderBackground背景着色器,比如典型的天空盒;Overlay覆盖类着色器,常用于GUITexture和Flare Shaders;TreeOpaque不透明树木着色器;
GrassTreeTransparentCutout透明树木类着色器,比如树叶的渲染;
草地类渲染着色器;
除了RenderTyper,还有一些其它常用的标签类型,例如:
"DisableBatching tag"="true",表示对此着色器禁用批处理绘制方式;
"ForceNoShadowCasting tag"="true" 表示该SubShader不会对着色物体产生阴影;
"IgnoreProjector tag"="true"表示该SubShader产生的阴影不受投影机(Projector)的影响;
"Queue"=" "表示制定的的渲染顺序队列;
这里需要详细解释Queue这个属性。可以想象,在一个大型游戏中,游戏物体的渲染大致顺序应该是由近到远逐步进行,从而保证游戏镜头逐步进行呈现,透明的水后边应该绘制不透明的物体等等。这样我们可以通过制定渲染顺序队列,来确保让透明的着色器类型能渲染在不透明物体前边。
Unity为我们提供了四种预定义的渲染队列,当然,我们也可以自行添加自定义的渲染方式。官方提供的预定义渲染队列如下:
LOD 200
CGPROGRAM #pragma surface surf Lambert #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG首先,CGPROGRAM表示开始位,与结束位ENDCG相对应,表明这之中是子着色器编译命令,所有的输入输出操作在其中进行。之后是一句#pragma surface指令,它的语法是:
#paragma target 3.0//<span style="font-family: Arial, Helvetica, sans-serif;">是Unity5.X版本新加入的指令,用来处理某些2.0版本的Surface shader指令集限制。</span>
struct Input { float2 uv_MainTex; };
half _Glossiness; half _Metallic; fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; }现在到了最后的关键函数surf,在surf里首先对_MainTex进行采样,将它和_Color叠加后进行输出。这里的tex2D函数是Cg中用于2D纹理采样,它的函数原型很多:
float4 tex2D(sampler2D samp, float2 s) float4 tex2D(sampler2D samp, float2 s, inttexelOff) float4 tex2D(sampler2D samp, float3 s) float4 tex2D(sampler2D samp, float3 s, inttexelOff) float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy) float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff) float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy) float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff) int4 tex2D(isampler2D samp, float2 s) int4 tex2D(isampler2D samp, float2 s, inttexelOff) int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy) int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff) unsigned int4 tex2D(usampler2D samp, float2s) unsigned int4 tex2D(usampler2D samp, float2s, int texelOff) unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy) unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)tex2D函数的返回值是采样后的纹理,这样就能得到最终的着色效果。