表面着色器和纹理制图
属性在称为表面函数的函数中初始化,并存储在名为SurfaceOutput的结构中。
其次,SurfaceOutput传递给照明模型。
这是一个特殊功能,它还将获取有关场景中附近灯光的信息。然后使用这两个参数计算模型中每个像素的最终颜色。照明功能是着色器的真实计算输出的部分,因为它是确定光接触材料时应该如何表现的模块。
下图概括地概括了Surface Shader的工作原理:
着色器允许您将材质的渲染属性传递给其光照模型的方式是通过SurfaceOutput。
它基本上是当前照明模型所需的所有参数的包装器。不同的照明模型具有不同的SurfaceOutput结构。
下表显示Unity中使用的三个主要输出结构以及如何使用它们:
SurfaceOutput结构具有以下属性:
fixed3 Albedo;:这是材质的漫反射颜色
fixed3 Normal;:这是法线纹理
fixed3 Emission;:这是材质发出的光的颜色(此属性在标准着色器中声明为half3)
fixed Gloss;:这是材料半透明度的镜面反射强度
SurfaceOutputStandard结构具有以下属性:
fixed3 Albedo;:这是材质的基色(无论是漫反射还是镜面反射)
fixed3 Normal;: 这是法线纹理
half3 Emission;:此属性声明为half3,而它被定义为材质发出的光的颜色
fixed Alpha; 透明度
half Occlusion;: 这是遮挡(默认1)
half Smoothness;: 这是平滑度(0 =粗糙,1 =平滑)
half Metallic;: 0 =非金属,1 =金属
SurfaceOutputStandardSpecular结构具有以下属性:
fixed3 Albedo;
fixed3 Normal;
half3 Emission;
fixed Alpha;
half Occlusion;
half Smoothness;
fixed3 Specular;: 这是镜面反射颜色。这与SurfaceOutput中的Specular属性非常不同,因为它允许您指定颜色而不是单个值。
使用和修改打包数组
着色器内的代码必须至少对屏幕中的每个像素执行。
这就是为什么GPU针对并行计算进行了高度优化的原因;他们可以同时执行多个流程。
这种理念在Cg中可用的标准类型的变量和运算符中也很明显。理解它们是必不可少的,不仅可以正确使用着色器,还可以编写高度优化的着色器。
Cg中有两种类型的变量:单值和压缩数组。数组的类型以诸如float3或int4之类的数字结尾。这些类型的变量类似于结构,这意味着它们每个都包含几个单独的值。Cg称它们为打包数组,尽管它们不是传统意义上的数组。
o.Alpha = _Color.a;
o是一个结构,_Color是一个打包数组。Cg禁止混合使用这两种语法的原因:你不能使用_Color.xgz。
Albedo是fixed3,这意味着它包含三个固定类型的值。_Color定义为fixed4类型。直接赋值会导致编译器错误,因为_Color比Albedo大。
将纹理添加到着色器
Properties属性中,添加纹理:
_MainTex ("Albedo (RGB)", 2D) = "white" {}
在CGPROGRAM部分中,此纹理定义为sampler2D:
sampler2D _MainTex;
每次调用surf()函数时,Input结构都将包含UV_MainTex表示需要渲染的3D模型的特定点。标准着色器识别出名称uv_MainTex引用_MainTex并自动初始化它。
struct Input {
float2 uv_MainTex;
};
最后,UV数据用于在曲面函数的第一行中对纹理进行采样:
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
这是使用Cg的tex2D()函数完成的;它需要纹理和UV并返回该位置的像素颜色。
通过修改UV值滚动纹理
当今游戏行业中最常用的纹理技术之一是允许您在对象表面上滚动纹理的过程。这允许您创建瀑布,河流和熔岩流等效果。它也是一种创建动画精灵效果的基础技术。
Shader "Custom/ScrollingUVs"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_ScrollXSpeed ("X Scroll Speed", Range(0,10)) = 2
_ScrollYSpeed ("Y Scroll Speed", Range(0,10)) = 2
}
SubShader
{
CGPROGRAM
fixed _ScrollXSpeed;
fixed _ScrollYSpeed;
sampler2D _MainTex;
// 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
struct Input
{
float2 uv_MainTex;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed2 scrolledUV = IN.uv_MainTex;
fixed xScrollValue = _ScrollXSpeed * _Time;
fixed yScrollValue = _ScrollYSpeed * _Time;
scrolledUV += fixed2(xScrollValue, yScrollValue);
half4 c = tex2D(_MainTex, scrolledUV);
o.Albedo = c.rgb * _Color;
o.Alpha = c.a;
}
ENDCG
}
}
我们首先将UV存储在名为scrolledUV的单独变量中。这个变量必须是float2/fixed2,因为UV值是从Input结构传递给我们的。
struct Input
{
float2 uv_MainTex;
}
一旦我们可以访问网格的UV,我们就可以使用滚动速度变量和内置_Time变量来改变它们。
这个_Time变量将根据Unity的游戏时钟给出一个递增的浮点值。因此,我们可以使用此值在UV方向上移动UV,并使用滚动速度变量缩放该时间。
通过按时间计算正确的偏移量,我们可以将新的偏移值添加回原始UV位置。这就是我们在一行使用+=运算符的原因。
将新UV传递给tex2D()函数。这会产生纹理在表面上移动的效果。我们真正在做的是操纵UV,所以我们伪造纹理移动的效果。
使用法线贴图创建着色器
3D模型的每个三角形都有一个面向方向,这是它指向的方向。它通常放置在三角形中心的箭头表示,并且与表面正交。面对方向在光线反射到表面上的方式中起着重要作用。如果两个相邻的三角形朝向不同的方向,它们将以不同的角度反射光线,因此它们将以不同的方式着色。对于弯曲物体,这是一个问题:显而易见的是,几何形状由平面三角形构成。
为了避免这个问题,光线在三角形上反射的方式不考虑其面向方向,而是考虑其法线方向。
具有粗糙边缘的平滑对象清楚地显示了每顶点的法线。如果我们绘制存储在每个顶点中的法线方向,可以看到如下面的屏幕截图所示。应该注意的是,每个三角形只有三个法线,但由于多个三角形可以共享同一个顶点,因此可以出现多个线条:
使用UnpackNormal()函数处理法线贴图后,将其返回SurfaceOutput结构,以便可以在照明函数中使用它。这是通过使用o.Normal=normalize(normalMap.rgb);来完成的。
float3 normalMap = UnpackNormal (tex2D(_NormalTex, IN.uv_NormalTex));
o.Normal = normalize(normalMap.rgb);
我们还可以通过修改法线贴图的x,y来控制发现贴图的强度:
normalMap.x *= _NormalMapIntensity;
normalMap.y *= _NormalMapIntensity;
其中_NormalMapIntensity为float类型。
法线向量膜长应该为1。将它们乘以_NormalMapIntensity会改变它们的长度,从而需要进行规范化。normalize函数将采用向量并调整它,使其指向正确的方向,并且长度为1,这正是我们需要做的。