Unity Shader 表面着色器(Surface Shader)

Unity Shader分为表面着色器(Surface Shader),顶点着色器(Vertex Shader),片段着色器(Fragment Shader)

我们先来看个简单的Surface Shader,看看它都能做什么。

首先我们通过"Create->3D Object ->Plane"创建一个面板


然后通过"Create->Shader->Standard Surface Shader"创建一个表面着色器,并修改名字为SurfaceShader

然后通过"Create->Material"创建一个材质,并修改名字为MaterialSurfaceShader,材质使用的shader指定为我们刚创建的SurfaceShader


然后我们把材质MaterialSurfaceShader拖到面板Plane上

我们用MonoDevelop打开刚创建的表面着色器SurfaceShader,看看里面都有什么

Shader "Custom/SurfaceShader" {
	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
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#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
	} 
	FallBack "Diffuse"
}

我们一行一行的对这段代码进行解析,解析完后,我们就可以读懂一些简单的unity shader了

第一行

Shader "Custom/SurfaceShader" { }

Shader是关键字,标明这个文件是个Shader,跟在后面的"Custom/SurfaceShader" 是个路径,我们在给材质选择Shader的时候会找到这个路径



第二行到第七行是属性区域

第二行

Properties{ }

Properties是属性的意思,和C#中的属性是一个意思,我们在这里定义一些属性,以便我们在后面用。

我们定义的这些属性在unity的Inspector面板中都能看到

我们来看看默认都有那些属性:

第三行

_Color ("Color", Color) = (1,1,1,1)

针对这句话的讲解我找到了一张图片


这里的内部名称 就是属性的名称,也是我们在后面用到该属性的时候用的名字

编辑器中显示的名称 就是在unity的Inspector面板中显示的名称

属性类型 标明该属性的类型,就像我们在c#中声明一个整形的变量a,我们会这样写 private int a,这里的int就是用来标明a是一个整形。同样Color标明我们的属性_Color是一个颜色

默认值 就是属性的默认值,针对颜色的这个,分别是r,g,b,a

我们来看看第三行的属性我们会在unity的Inspector面板中看到什么


我们把这个颜色变成红色,然后我们看看我们的面板发生了什么




我们的面板变成红色了!!

我们可以通过这种方式改变我们材质的颜色

第四行

_MainTex ("Albedo (RGB)", 2D) = "white" {}

这个属性的名字叫做_MainTex,在unity的Inspector面板中显示的名字是"Albedo (RGB)",属性类型是2D,默认材质的颜色是白色white,最后的大括号{}我们先不用管

2D的意思是2D纹理,关于表示纹理的属性,unity文档中有下面三种



我们来看看第四行会在unity的Inspector面板中显示什么


我们通过"Albedo (RGB)"区域的右边Texture中的select选择一张我们的纹理图片看看是什么效果。我们需要把颜色还原为白色



我们的面板Plane有图片了!!!



可以看到第四行的作用是用来设置纹理图片的,我们可以通过属性的名字_MainTex来操作这幅纹理图片

第五行和第六行分别设置平滑程度(Smoothness)和金属性(Metallic)的,这两个属性的作用在前一章中已经讲过,这里不再重复讲,我们来看看在unity的Inspector面板中会出现什么



第三行到第39行驶着色器执行的区域SubShader,也就是在这个区域中会用到我们在属性区域中定义的属性

如果我们定义的属性在SubShader没有使用,那么这个属性将不会有任何效果


第八行

SubShader{ }

SubShader是个关键字,表示着色器执行的区域

第九行

Tags { "RenderType"="Opaque" }

Tags是个关键字,表示标签的意思,作用是告诉引起这个物体什么时候已经怎样被渲染出来

下面是unity文档中对tags的说明及语法格式



unity shader的标签有很多,我们列举出常用的几个,其他的我怕们可以在unity文档中找到



那么第九行的意思就是渲染不透明的物体

第十行

LOD 200

Unity中的LOD表示着色器细节等级,LOD 200表示只有在细节等级小于等于200时才起作用,细节等级如下图




第十二行到第十八行驶CG程序,CG程序以CGPROGRAM开始,以ENDCG结束

第十二行

CGPROGRAM 表示CG程序的开始

第十四行

#pragma surface surf Standard fullforwardshadows

针对这句话的解释看下图


所以 #pragma是一个关键字,用来声明一些东西的

surface 表示声明一个表面着色器

surf是我们表面着色器函数的名字,这个函数在第29行

Standard fullforwardshadows 是一个光照模型,unity中的光照模型分为Lambert,Phone等,使用不同的光照模型,会产生不同的效果

第十七行

#pragma target 3.0

表示把shader编译为3.0版本,不同的版本支持的性能会有不同

第十九行

sampler2D _MainTex;

声明我们将要用到的属性,我们在属性区域中定义了一些属性,想要用着这些属性,必须在SubShader区域再次声明一个名字一样的字段,才可以使用。

sampler2D是个啥?其实在CG中,sampler2D就是和texture所绑定的一个数据容器接口。等等..这个说法还是太复杂了,简单理解的话,所谓加载以后的texture(贴图)说白了不过是一块内存存储的,使用了RGB(也许还有A)通道,且每个通道8bits的数据。而具体地想知道像素与坐标的对应关系,以及获取这些数据,我们总不能一次一次去自己计算内存地址或者偏移,因此可以通过sampler2D来对贴图进行操作。更简单地理解,sampler2D就是GLSL中的2D贴图的类型,相应的,还有sampler1D,sampler3D,samplerCube等等格式。


解释通了sampler2D是什么之后,还需要解释下为什么在这里需要一句对_MainTex的声明,之前我们不是已经在Properties里声明过它是贴图了么。答案是我们用来实例的这个shader其实是由两个相对独立的块组成的,外层的属性声明,回滚等等是Unity可以直接使用和编译的ShaderLab;而现在我们是在CGPROGRAM...ENDCG这样一个代码块中,这是一段CG程序。对于这段CG程序,要想访问在Properties中所定义的变量的话,必须使用和之前变量相同的名字进行声明。于是其实sampler2D _MainTex;做的事情就是再次声明并链接了_MainTex,使得接下来的CG程序能够使用这个变量。


第二十一到二十三行

struct Input {
float2 uv_MainTex;
};

我们定义的一个结构体,作为表面着色器函数surf的输入参数

第二十五到第二十七行

half  _Glossiness;
half  _Metallic;
fixed4  _Color;

作用和第十九行类似


第二十九到第三十七行是我们的表面着色器函数surf

上面的#pragma段已经指出了我们的着色器代码的方法的名字叫做surf,着色器就是给定了输入,然后给出输出进行着色的代码。CG规定了声明为表面着色器的方法(就是我们这里的surf)的参数类型和名字,因此我们没有权利决定surf的输入输出参数的类型,只能按照规定写。这个规定就是第一个参数是一个Input结构,第二个参数是一个inout的SurfaceOutput结构。


它们分别是什么呢?Input其实是需要我们去定义的结构,这给我们提供了一个机会,可以把所需要参与计算的数据都放到这个Input结构中,传入surf函数使用;SurfaceOutput是已经定义好了里面类型输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出,这样就可以完成着色了。先仔细看看INPUT吧,现在可以跳回来看上面定义的INPUT结构体了:

struct Input {
			float2 uv_MainTex;
		};

作为输入的结构体必须命名为Input,这个结构体中定义了一个float2的变量…你没看错我也没打错,就是float2,表示浮点数的float后面紧跟一个数字2,这又是什么意思呢?其实没什么魔法,float和vec都可以在之后加入一个2到4的数字,来表示被打包在一起的2到4个同类型数。

在访问这些值时,我们即可以只使用名称来获得整组值,也可以使用下标的方式(比如.xyzw,.rgba或它们的部分比如.x等等)来获得某个值。在这个例子里,我们声明了一个叫做uv_MainTex的包含两个浮点数的变量。


如果你对3D开发稍有耳闻的话,一定不会对uv这两个字母感到陌生。UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点处理手段。在CG程序中,我们有这样的约定,在一个贴图变量(在我们例子中是_MainTex)之前加上uv两个字母,就代表提取它的uv值(其实就是两个代表贴图上点的二维坐标 )。我们之后就可以在surf程序中直接通过访问uv_MainTex来取得这张贴图当前需要计算的点的坐标值了。

我们来看看struct Input中我们可以定义的默认值有哪些,下面的内容来自unity文档

Surface Shader input structure

The input structure Input generally has any texture coordinates needed by the shader. Texture coordinates must be named “uv” followed by texture name (or start it with “uv2” to use second texture coordinate set).

Additional values that can be put into Input structure:

  • float3 viewDir - will contain view direction, for computing Parallax effects, rim lighting etc.
  • float4 with COLOR semantic - will contain interpolated per-vertex color.
  • float4 screenPos - will contain screen space position for reflection or screenspace effects.
  • float3 worldPos - will contain world space position.
  • float3 worldRefl - will contain world reflection vector if surface shader does not write to o.Normal. See Reflect-Diffuse shader for example.
  • float3 worldNormal - will contain world normal vector if surface shader does not write to o.Normal.
  • float3 worldRefl; INTERNAL_DATA - will contain world reflection vector if surface shader writes to o.Normal. To get the reflection vector based on per-pixel normal map, use WorldReflectionVector (IN, o.Normal). See Reflect-Bumped shader for example.
  • float3 worldNormal; INTERNAL_DATA - will contain world normal vector if surface shader writes to o.Normal. To get the normal vector based on per-pixel normal map, use WorldNormalVector (IN, o.Normal)




如果你坚持看到这里了,那要恭喜你,因为离最后成功读完一个Shader只有一步之遥。我们回到surf函数,它的两有参数,第一个是Input,我们已经明白了:在计算输出时Shader会多次调用surf函数,每次给入一个贴图上的点坐标,来计算输出。第二个参数是一个可写的SurfaceOutput,SurfaceOutput是预定义的输出结构,我们的surf函数的目标就是根据输入把这个输出结构填上。SurfaceOutput结构体的定义如下

struct SurfaceOutput {  
    half3 Albedo;     //像素的颜色
    half3 Normal;     //像素的法向值
    half3 Emission;   //像素的发散颜色
    half Specular;    //像素的镜面高光
    half Gloss;       //像素的发光强度
    half Alpha;       //像素的透明度
};
这里的half和我们常见float与double类似,都表示浮点数,只不过精度不一样。也许你很熟悉单精度浮点数(float或者single)和双精度浮点数(double),这里的half指的是半精度浮点数,精度最低,运算性能相对比高精度浮点数高一些,因此被大量使用.

第三十一行

fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
这里用到了一个tex2d函数,这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个float4。这里对_MainTex在输入点上进行了采样,然后再乘与颜色值

第三十二行

o.Albedo = c.rgb;

将其颜色的rbg值赋予了输出的像素颜色

第三十四行

o.Metallic = _Metallic

将其金属性值赋予了输出的像素的金属性值

第三十五行

o.Smoothness = _Glossiness;

将其平滑性值赋予了输出的像素的平滑性值

第三十六行

o.Alpha = c.a;

将其alpha值赋予了输出的像素的alpha

第三十八行

ENDCG

ENDCG 是个关键字,标明CG程序的结束


第四十行

FallBack "Diffuse"

如果说我们的SubShader由于某些原因没有被执行,那就使用系统默认的shader


OVER










  • 19
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值