第二章 表面着色器和纹理映射
2.1-2.3 部分:
材质的物理属性在表面函数中进行初始化,存储在“表面输出”的结构内,表面输出(surface output)被传递给光照模型,同时根据光照信息计算出模型上每一个像素最终的颜色。
unity内三种主要的表面输出结构:
- SurfaceOutPut
- fixed3 Albedo 表示材质的漫反射颜色
- fixed3 Normal 表示切面法线
- fixed3 Emission 表示材质发射的颜色
- fixed Alpha 表示材质的透明度
- half Specular 表示光亮度(0,1)
- fixed Gloss 表示光强度
- SurfaceOutputStandard
- fixed Albedo 表示材质的基础颜色
- fixed3 Normal
- half3 Emission
- 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
- Cg中两种类型的变量:单一值变量 和 包装数组。包装数组类似于结构体,一个数组包含数个单一值。
Cg语言内的几种特性
- 调和(swizzling)
o.Albedo=_Color.rgb;
在本例中,作为fixed3类型的Albedo同时被rgb三个元素所赋值。同时Cg支持对元素重新排序,如_Color.bgr会将红色成分和蓝色成分进行对调。 - 涂抹(smearing)
o.Albedo=0;//Black=(0,0,0);
将单一值赋给包装数组,单一值将会填充数组的每一元素。 - 遮罩(masking)
o.Albedo.rg=_Color.rg;
调和用在表达式的左边,以重写包装数组的部分元素。
- 调和(swizzling)
- 包装矩阵: float4*4表示float类型的4行4列的矩阵。通过_mRC标注访问矩阵R行C列的元素。
2-4:给着色器添加纹理
- 三维模型由三角形组成,三角形的每一个顶点存储着着色器可以访问的数据。
- uv数据:由uv两个坐标组成,取值范围(0,1),表示了++二维图像中像素映射到顶点时的xy坐标++,只是给顶点使用。
uv数据保存在三维模型中,用建模软件才可编辑。 工作原理:
_MainTex("Albedo(RGB)",2D)="White"{} //实际引用纹理的代码 sampler2D _MainTex //将纹理定义为二维纹理的标准类型 struct Input{ float2 uv_MainTex; }; //输入参数,包含三维模型需要渲染的某个特殊点的MainTex的uv值。 fixed4 c=tex2D(_MainTex,In.uv_MainTex)*_Color; //用来对纹理进行采样,tex2D返回颜色值(包装数组)。```
- Filter(过滤)模式:
- 双线性过滤(Bilinear):廉价有效的平滑纹理方式
- 点过滤(Point):对于二维游戏,双线性插值可能会产生模糊,用点过滤来移除纹理采样过程中的插值。
- Aniso Level:用于减少纹理采样瑕疵。
2-5 通过修改uv值来滑动纹理
代码
Shader "Custom/flowShader" { Properties { _MainTint("Diffuse Tint",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 { 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; fixed4 _MainTint; fixed _ScrollXSpeed; fixed _ScrollYSpeed; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; 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*_MainTint; o.Alpha=c.a; } ENDCG } FallBack "Diffuse" }
工作原理
- 开始时先将UV值保存在scrolledUV的变量中,该变量为float2或fixed2类型。
- 用滑动速度变量和內建的递归时间变量_Time来对纹理进行偏移。_Time变量随着时间的推移返回一个递增的浮点数值。 关于递归时间的完整描述:https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
- 计算了不同时刻的偏移之后,将新计算出来的偏移量添加到原始的UV位置(+=运算符的目的),然后将新的UV值作为参数传给tex2D()作为纹理的新UV值。
- 实际上是通过操作UV值造成一种纹理在移动的假象。
2-6法线映射
- 每一个三维物体都是由三角形的面组成的,每一个三角形都有着其朝向,由三角形中心的法线方向决定,该++中心点++法线决定整个面的朝向。当相隔很近但朝向不同的三角形反射光时,由于朝向不同,着色方向会大不一样,对于弧形表面效果很差。
- 因此引入法线方向的概念,在处理弧形表面的光照效果时,使用其法线方向。 在顶点存储的数据中,法线方向是仅次于UV值的一个非常重要的参数。 法线方向是一个单位向量,表示顶点面朝的方向,然而,与面朝方向不同,三角形中的每一个点都有着其独有的法线方向(一个刺猬(基于顶点法线方向线性插值)。
- 作用:用一些低精度模型制作高精度的几何体。
- 法线映射(bump):通常用一些RGB图像来存贮法线方向(R,G,B)->(X,Y,Z)。unity内使用UnpackNormals()向表面着色器添加法线映射。
- 代码
/*null*/
2-7创建透明材质
- 在渲染实心物体之前,Unity按照各个物体距离镜头的距离(z排序)对它们进行排列,然后跳过所有没有朝着镜头的三角形(剔除)。
代码:
Shader "Custom/Transparent" { 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 { "Queue"="Transparent"//通道混合的对象(不写入深度缓存的shader)使用该队列,例如玻璃和粒子。 "IgnoreProjector"="True"//确保该物体不受Unity的投影影响 "RenderType"="Transparent"//着色器置换 } LOD 200 Cull Back// CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard alpha:fade // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; fixed4 _Color; // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_CBUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_CBUFFER_END void surf (Input IN, inout SurfaceOutputStandard o) { float4 c=tex2D(_MainTex,IN.uv_MainTex)*_Color; o.Albedo=c.rgb; o.Alpha=c.a; } ENDCG } FallBack "Diffuse" }
一些参数
- Tags参数详解:https://docs.unity3d.com/Manual/SL-SubShaderTags.html
Unity提供了默认的 渲染序列(Queue):
渲染队列 渲染队列描述 渲染队列值 Background 这个队列被最先渲染。它被用于skyboxes等。 1000 Geometry 这是默认的渲染队列。它被用于绝大多数对象。不透明几何体使用该队列。 2000 AlphaTest 通道检查的几何体使用该队列。它和Geometry队列不同,对于在所有立体物体绘制后渲染的通道检查的对象,它更有效。 2450 Transparent 该渲染队列在Geometry和AlphaTest队列后被渲染。任何通道混合的(也就是说,那些不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果。 3000 Overlay 该渲染队列是为覆盖物效果服务的。任何最后被渲染的对象使用该队列,例如镜头光晕。 4000 - 事实上透明序列会在几何序列后被渲染,详情搜索:ZBuffing
- alpha:fade 这种材质上的每一个像素需要与屏幕上之前的颜色根据其alpha值进行混色。
2-8创建全息着色器
- 简介:一种适合于创造未来科技感的着色技术。可以通过噪声,动画扫描线,以及震动来创造特效。
代码:
Shader "Custom/Silhouette" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} //用_DotProduct来判断点积多么接近于0时才将三角形视为轮廓。 _DotProduct("Rim effect",Range(-1,1))=0.25 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProtector"="True" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Lambert alpha:fade nolighting //朗伯(Lambertian reflectance)反射,廉价光照模型,nolighting用来禁止光照 sampler2D _MainTex; struct Input { float2 uv_MainTex; float3 worldNormal; float3 viewDir; }; fixed4 _Color; float _DotProduct; void surf (Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color float4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; float border=1-(abs(dot(IN.viewDir,IN.worldNormal))); //点积,判断两个向量是否正交(点积为0),根据几何将法线与视线点积为0的三角形视为轮廓 float alpha=(border*(1-_DotProduct)+_DotProduct); //线性插值,模型的边与_DotProduct之间的渐变褪色,由线性插值完成 o.Alpha = c.a*alpha; } ENDCG } FallBack "Diffuse" }
2-9 打包和混合纹理
- 纹理的作用
- 存储像素颜色数据
- 存储x,y方向的像素集合,RGBA通道
- 将图像打包成一个RGBA纹理,再提取单独的组件作为单独的纹理(节省空间)
- 将数种纹理混合涂在一个表面上(地形等)
代码:
Shader "Custom/battleGround" { Properties { _MainTint("Diffuse Tint",Color)=(1,1,1,1) _ColorA("Terrain Color A",Color)=(1,1,1,1) _ColorB("Terrain Color B",Color)=(1,1,1,1) _RTexture("Red Channel Texture",2D)=""{} _GTexture("Green Channel Texture",2D)=""{} _BTexture("Blue Channel Texture",2D)=""{} _ATexture("Alpha Channel Texture",2D)=""{} _BlendTex("Blend Texture",2D)=""{} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Lambert #pragma target 4.0 float4 _MainTint; float4 _ColorA; float4 _ColorB; sampler2D _RTexture; sampler2D _GTexture; sampler2D _BTexture; sampler2D _ATexture; sampler2D _BlendTex; struct Input { float2 uv_RTexture; float2 uv_GTexture; float2 uv_BTexture; float2 uv_Atexture; float2 uv_BlendTex; }; void surf (Input IN, inout SurfaceOutput o) { float4 blendData=tex2D(_BlendTex,IN.uv_BlendTex); float4 rTexData=tex2D(_RTexture,IN.uv_RTexture); float4 gTexData=tex2D(_GTexture,IN.uv_GTexture); float4 bTexData=tex2D(_BTexture,IN.uv_BTexture); float4 aTexData=tex2D(_ATexture,IN.uv_Atexture); float4 finalColor; finalColor=lerp(rTexData,gTexData,blendData.g);//r=g finalColor=lerp(finalColor,bTexData,blendData.b);//g=b finalColor=lerp(finalColor,aTexData,blendData.a);//b=a finalColor.a=1.0; float4 terrainLayers=lerp(_ColorA,_ColorB,blendData.r); finalColor *= terrainLayers; finalColor =saturate(finalColor); o.Albedo=finalColor.rgb * _MainTint.rgb; o.Alpha=finalColor.a; } ENDCG } FallBack "Diffuse" }
- 工作原理:
混合纹理通过內建的lerp()函数,将第一个参数和第二个参数按照第三个参数指定的比例进行混合。
该着色器取纹理的不同纹理进行混合。
2-10 在地形周围创建圆环
- 这个案例展示了用着色器画一个圆环,++这个着色器附在的材质将赋给所要画的地形,而不是跟随的物体++。
- 代码:
Shader "Custom/RadiusShader" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Center("Center",Vector)=(0,0,0,0)//圆心
_Radius("Radius",Float)=0.5//半径
_RadiusColor("Radius color",Color)=(1,0,0,1)
_RadiusWidth("Radius Width",Float)=2
}
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
float3 _Center;
float _Radius;
fixed4 _RadiusColor;
float _RadiusWidth;
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float3 worldPos;//请求当前绘制的像素在世界坐标中的位置
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float d=distance(_Center,IN.worldPos);
//绘制部分,如果在圆环内采用选定颜色,不在的话使用纹理采样的颜色
if(d>_Radius&&d<_Radius+ _RadiusWidth)
o.Albedo=_RadiusColor;
else
o.Albedo=tex2D(_MainTex,IN.uv_MainTex).rgb;
}
ENDCG
}
FallBack "Diffuse"
}
//跟随角色的代码,在Update里根据自身position设置在地图绘画的圆心位置。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Radius : MonoBehaviour {
public Material radiusMaterial;
public float radius = 1;
public Color color = Color.white;
// Update is called once per frame
void Update () {
radiusMaterial.SetVector("_Center", transform.position);
radiusMaterial.SetFloat("_Radius", radius);
radiusMaterial.SetColor("_RadiusColor", color);
}
}