本书版本为“占红来 译”版,笔记会持续更新,有错误的地方欢迎指正,谢谢!
引言
表面着色器是Unity中主要使用的一种着色器,使用表面着色器有两个步骤:
- 给材质的物理属性赋值(比如漫反射颜色、光滑度等)。这些物理属性会在表面函数(surf())中赋值,之后,存储在表面输出(surface output)的结构中;
- 表面输出(负责属性和光照模型之间的通信)会传递给光照模型。光照模型也是一个函数,该函数还依赖于场景中的光照信息。所以,光照函数决定了光线在材质上的最终表现。
漫反射着色
非反光材料最好的渲染方式是使用漫反射着色器,这是一种大量用到的廉价的着色方式。
创建漫反射着色器最快的一种方式:修改标准着色器。
Shader "BookShaders/2-2 Diffuse" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
//SurfaceOutputStandard是表面输出结构
void surf (Input IN, inout SurfaceOutputStandard o)
{
o.Albedo = _Color.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
漫反射着色:
标准着色:
使用包装数组
常识:GPU需要并行计算的原因:着色器中的代码需要在屏幕的每一个像素点上执行。
Cg中有两种类型的变量:单一变量和包装数组。float3就属于包装数组,尽管它不是数组,而类似于结构体。
包装数组元素的访问:用x、y、z、w或r、g、b、a(这两种是完全等价的)。其实着色器是处理一些位置和颜色的计算,选择合适的名字访问元素会让代码更加表义。
包装数组可以处理类型转换:比如:Albedo是fixed3类型,而_Color是fixed4类型。 o.Albedo = _Color.rgb;
包装数组可以对元素重新排序,比如:o.Albedo = _Color.gbr;
补充:
float:32位高精度浮点数
half:16位中精度浮点数
fixed:11位低精度浮点数
给着色器添加纹理
模型是由三角形组成,三角形的每个顶点可存储着色器可访问的数据,UV数据是其中一个。UV数据只是给顶点用的,三角形其他位置的值则由差值运算按照一定的间隔比例去纹理上取值。模型要有UV组件,才能支持纹理映射。
操作就是在Unity中赋一个纹理图。着色器知道如何通过UV数据将二维图像映射到三维模型上。
部分代码说明:
//Input结构包含三维模型需要渲染的每个点的_MainTex的UV值,着色器会识别出uv_MainTex引用的是_MainTex的值。
struct Input
{
float2 uv_MainTex;
};
//UV数据被用来对纹理进行采样:
fixed4 c = tex2D(_MainTex,IN.uv_MainTex)*_Color;
通过修改UV值来滑动纹理
实际上是通过修改UV值来移动纹理,能实现瀑布、河流等流动效果:
Shader "BookShaders/2-5 ScrollingUVs"
{
Properties {
_MainTint("Diffuse Tint", Color) = (1,1,1,1)
_MainTex("Base (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
#pragma surface surf Lambert
fixed4 _MainTint;
fixed _ScrollXSpeed;
fixed _ScrollYSpeed;
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
fixed2 scrolledUV = IN.uv_MainTex;
fixed xScrollValue = _ScrollXSpeed * _Time;//_Time是内建的
fixed yScrollValue = _ScrollYSpeed * _Time;
scrolledUV += fixed2(xScrollValue, yScrollValue);
//将添加了偏移量的UV值传递给tex2D函数作为纹理的新UV值。
half4 c = tex2D(_MainTex, scrolledUV);
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
有图有真相:
法线映射
模型中每个三角形都有一个面朝的方向,其对于光线在物体表面反射的时候起到了至关重要的作用。但弧形物体表面没法用平的三角形拼出来,这时就要用到法线方向。而法线方向是仅次于UV值的一个非常重要的参数,法线方向是单位长度的向量。与面朝方向不同的是,每个三角形的每个顶点都有一个法线方向,这就意味着一个连接着多个三角形的顶点会有多个法线。而这些顶点的法线方向通过线性插值得到。
但上述计算模型法线的方法已被法线映射打败。。。
法线映射:法线方向可以通过一个额外的纹理来提供。
补充:bump映射(即凹凸映射)包含法线映射和高度映射,但常把bump映射和法线映射当成相同技术。
创建透明材质
能实现玻璃等效果:
Shader "BookShaders/2-7 Transparent"
{
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader{
Tags
{
//表明着色器是一个透明着色器,即在color中调alpha值是有效的。
"Queue" = "Transparent"
//确保物体不受Unity的投影的影响(不明觉厉)。
"IgnoreProjector" = "True"
//置换着色器(不明觉厉)。
"RenderType"="Transparent"
}
Cull Back//确保目标模型背后的几何体是没有画过的(不明觉厉)。
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;//此时修改有效
}
ENDCG
}
FallBack "Diffuse"
}
Tags是用来添加物体渲染信息的。接下来讲讲其中的Queue:Unity提供了默认渲染序列,见下方表格,每个渲染序列都有一个渲染序列值来告诉Unity其出现的顺序。
渲染序列 | 渲染序列值 |
---|---|
几何结构(Geometry) | 2000 |
alpha检查(AlphaTest) | 2450 |
透明(Transparent) | 3000 |
看看我搞的玻璃:
创建全息着色器
可以通过噪声、动画扫描线以及振动来创建非常炫酷的全息特效,它能实现显示物体轮廓、幽灵、冲击波、泡泡护盾等效果:
全息特效只显示物体的轮廓。
Shader "BookShaders/2-8 Silhouette"
{
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
//模型边缘由那些法线垂直与视线方向的三角形构成,
//用_DotProduct来判断点积多么接近于0时才将三角形视为轮廓。
_DotProduct("RimEffect",range(-1,1)) = 0.25
}
SubShader{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType"="Transparent"
}
LOD 200
CGPROGRAM
//不是模拟真实材质,就没必要用PBR光照模型(Standard),
//alpha:fade表明这是一个透明着色器,nolighting禁用所有光照。
#pragma surface surf Lambert alpha:fade nolighting
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
};
fixed4 _Color;
float _DotProduct;
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
//点积
float border = 1-abs(dot(IN.viewDir, IN.worldNormal));
//完全可见部分(即模型的边缘)和完全不可见部分之间的渐变褪色,通
//过下面的线性插值来完成,但有了_DotProduct后,就不存在完全不可
//见部分了,即使border为0(原本的完全不可见处),也有一定可见度了。全息效果更真实。
float alpha = border*(1 - _DotProduct) + _DotProduct;
o.Alpha = c.a*alpha;
}
ENDCG
}
FallBack "Diffuse"
}
图在这:
混合纹理
将纹理混合后涂在表面上,这种需求通常出现在地形类的着色器上。
需要这里提供三张待混合的图:
还需要一张彩色混合(Blend)纹理图:
待混合的图分别分配到R通道、G通道、B通道,再根据彩色混合纹理的RGB分布,将对应的RGB通道的图混合起来(用的几个lerp(a,b,f)函数进行混合,a和b这两种纹理按照f指定的比例进行混合,即为(1-f)*a+f*b)。
核心代码:finalColor = lerp(lerp(rTexData, gTexData, blendData.g), bTexData, blendData.b);
最终效果:
在地形周围创建圆环
能实现显示复杂地形上的攻击范围、视野范围等效果:
不过就是直接更换纹理图表示圈的那一部分~
见图: