本书版本为“占红来 译”版,笔记会持续更新,有错误的地方欢迎指正,谢谢!
引言
着色器不仅能决定物体的外观,还能重新定义物体的形状。
三维模型和一堆三角形的集合,三角形的每个顶点可以包含一些基础数据,通过这些数据就能渲染出该模型了。本文要讲述的是:如何获取这部分信息并使用之。
在表面着色器中访问顶点的颜色
一个顶点包含的信息:
- float4类型的颜色值;
- float3类型的顶点位置;
- float3类型的顶点法线方向。
实现访问顶点颜色的代码:
Shader "BookShaders/5-2 VertColor" {
Properties {
_MainTint("Global Color Tint",Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
//告诉Unity我们将在着色器中添加一个顶点函数。
#pragma surface surf Lambert vertex:vert
#pragma target 3.0
float4 _MainTint;
//Input结构里的数据是其他地方输出到它的数据,比如vertColor是vert()中输出到它的,
//这实现了提取顶点颜色的目的,还要使surf()能访问之。所以这需要加一个vertColor。
struct Input {
float2 uv_MainTex;
float4 vertColor;
};
//appdata_full是内置结构体,用来存储顶点信息。
void vert(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);//相当于把全部都参数初始化了。
//appdata_full内置结构体的一个参数color。
o.vertColor = v.color;
}
//SurfaceOutput也是内置的结构体,用于输出信息到光照模型。surf()获取数据的两种方式:
//1.通过“IN.XXXX”来使用Input结构里的数据;2.直接调用Properties中的属性值。
void surf (Input IN, inout SurfaceOutput o)
{
//Albedo是SurfaceOutput内置结构体的一个参数。
o.Albedo = IN.vertColor.rgb*_MainTint.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
导入Unity的模型的默认材质是不会让顶点颜色显示出来的,我们必须自己编写这个着色器获取到顶点颜色,并将它们展示在模型表面。
使用默认材质时:
使用含有该着色器的自定义的材质:
表面着色器中的顶点动画
访问顶点的每个位置并使用正弦波来动态修改每个顶点的位置,可用于制作飘扬的旗帜和海浪等效果。
实现访问顶点位置并修改的代码:
Shader "BookShaders/5-3 VertexAnimation"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
_tintAmount("Tint Amount", Range(0,1)) = 0.5
_ColorA("Color A", Color) = (1,1,1,1)
_ColorB("Color B", Color) = (1,1,1,1)
_Speed("Wave Speed", Range(0.1, 80)) = 5
_Frequency("Wave Frequency", Range(0, 5)) = 2
//振幅
_Amplitude("Wave Amplitude", Range(-1, 1)) = 1
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert
sampler2D _MainTex;
float4 _ColorA;
float4 _ColorB;
float _tintAmount;
float _Speed;
float _Frequency;
float _Amplitude;
float _OffsetVal;
struct Input
{
float2 uv_MainTex;
float3 vertColor;
};
void vert(inout appdata_full v, out Input o)
{
//相当于把参数初始化了。
UNITY_INITIALIZE_OUTPUT(Input, o);
float time = _Time * _Speed;
//使用正弦波对顶点位置进行修改。
float waveValueA = sin(time + v.vertex.x * _Frequency)*_Amplitude;
//在y方向起伏
v.vertex.xyz = float3(v.vertex.x, v.vertex.y + waveValueA, v.vertex.z);
//对网格顶点的法线进行微调,为了更逼真。让每个顶点的法线更偏向X轴的走向。
v.normal = normalize(float3(v.normal.x + waveValueA, v.normal.y, v.normal.z));
//可将下面三个数修改为(1,1,1)或(0,0,0)或(-1,-1,-1),就知道其作用了。
o.vertColor = float3(waveValueA, waveValueA, waveValueA);
}
void surf(Input IN, inout SurfaceOutput o)
{
half4 c = tex2D(_MainTex, IN.uv_MainTex);
//lerp()函数混合两种颜色。凸起的地方(值为0到1)是_ColorB,凹陷的地方(值为0到-1)是_ColorA。
//使用vertColor、vertColor.r、vertColor.g、vertColor.b都是同样的效果,因为值都为waveValueA。
float3 tintColor = lerp(_ColorA, _ColorB, IN.vertColor).rgb;
o.Albedo = c.rgb * (tintColor * _tintAmount);
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
未使用该着色器:
使用了该着色器:
挤压模型
通过法线挤压技术来控制模型的胖瘦(即调整其几何结构),比如:同一个模型(模型必须得有法线)可做成一个瘦瘦的僵尸或一个胖子。
实现法线挤压的代码:(特别简单,就是加一句:沿着法线增或减~)
Shader "BookShaders/5-4 Extrusion" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
//用于表示挤压程度,0是不变,0到-0.00015是瘦,0到0.00015是胖。
//0.00015已经算是这个模型的阈值了,不然这模型就不是人样了。。。
_Amount("Extrusion Amount",Range(-0.00015,0.00015)) = 0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
float _Amount;
fixed4 _Color;
void vert(inout appdata_full v)
{
//沿着法线方向进行挤压
v.vertex.xyz += v.normal*_Amount;
}
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
正常图:
瘦的:
胖的:
再放出下面这张图,大家应该能全部理解了:
实现雪花着色器
步骤:先调整几何结构,再给表面着白色的雪。(顶点控制函数vert()会在表面着色函数surf()之前被执行,vert()的位置并不影响它的执行顺序。)
代码:
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Bump ("Bump", 2D) = "bump" {}
_Snow ("Snow Level", Range(0,1) ) = 0//_Snow决定多大的角度才算是朝着下雪的方向,即覆雪的范围。
_SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0)//雪的颜色
_SnowDirection ("Snow Direction", Vector) = (0,1,0)//下雪的方向(正上方)
_SnowDepth ("Snow Depth", Range(0,0.3)) = 0.1//雪的厚度
}
sampler2D _MainTex;
sampler2D _Bump;
float _Snow;
float4 _SnowColor;
float4 _SnowDirection;
float _SnowDepth;
struct Input {
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal; INTERNAL_DATA
};
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
//UnpackNormal(x)接受一个fixed4的输入x,并将x转换为所对应的法线值(fixed3)。
o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump));
//将三角形的法线从相对坐标转换为世界坐标后,再与下雪方向点积。
if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1,-1,_Snow)) {
o.Albedo = _SnowColor.rgb;
} else {
o.Albedo = c.rgb;
}
o.Alpha = c.a;
}
//和之前积雪的运算其实比较类似,判断点积大小来决定是否需要扩大模型以及确定模型扩大的方向。
//surf是依靠三角形的法线和颜色rgba,vert是依靠顶点的法线和方向xyz。
void vert (inout appdata_full v) {
float4 sn = mul(transpose(_Object2World) , _SnowDirection);
if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow * 2) / 3)) {
v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
}
}
积雪效果:
实现体积爆炸效果
爆炸无非就是一团很热的气体,也就是模拟流体呗~很多游戏通过粒子效果来模拟爆炸,用一些火焰、烟雾和碎片粒子来做,而这种实现方式并不真实,所以有了体积(是三维对象)爆炸这个概念。
补充:顶点函数只能修改顶点,而其他非顶点部分的信息通过插值得到。
要实现爆炸效果,需要一张坡度纹理(包含爆炸所需的所有颜色);需要一张噪声纹理(在着色器中获取随机数的最简单的方法:对噪声纹理进行采样)。
步骤一:通过顶点函数(这里的顶点函数是法线挤压的一个变种)修改几何结构;
步骤二:利用表面函数进行着色。
为了让爆炸更炫酷,还可以加一些粒子喔~