当前位置:   article > 正文

unity-shader(入门)_unity3d shader

unity3d shader

1. 简介

1. 概述

渲染管线
在这里插入图片描述

在这里插入图片描述

  • Cpu 应用阶段:
    把数据加载到显存中。
    设置渲染状态。
    调用Draw Call。
    在这里插入图片描述
  • 顶点着色器:实现顶点的空间变换,逐顶点光照。
    模型空间->齐次裁剪空间->计算顶点颜色
  • 曲面细分着色器:可选着色器,用于细分图元。
  • 几何着色器:可选着色器,可以被用于执行逐图元的着色操作,或者被产生于更多的图元。
  • 裁剪,这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,剔除某些三角图元的面片。
  • 屏幕映射,这一阶段不可配置和编程,负责把每个图元的坐标转换到屏幕坐标系中。
    将裁剪后的齐次坐标(NDC)转换到屏幕坐标系。
  • 三角形设置:计算光栅化一个三角网格所需要的信息。
  • 三角形遍历根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。(片元信息,包括该顶点的各种信息)
2. 详解

在这里插入图片描述

  1. 片元着色器:可编程的,用于实现逐片元的着色操作。
    根据那些从顶点着色器中输出的数据插值得到的。而其输出是一个或者多个颜色值。
    这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到起覆盖的片元的纹理坐标。
  2. 逐片元操作:负责很多重要操作,如修改颜色,深度缓冲,进行混合等,不可编程,但是可配置。
    决定每个片元的可见性,涉及很多测试工作,例如深度测试,模板测试。
    如果一个片元通过了所有测试后,就需要把这个片元的颜色值和已经储存在颜色缓冲区的色彩进行合并,或者说混合。
  3. 模板测试:Gpu首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值进行比较,这个比较函数可以由开发者指定的,例如小于等于舍弃该片元,或者大于等于舍弃该片元。如果这个片元没有通过测试,该片元就会被舍弃。
  4. 深度测试:Gpu会把该片元的深度值和已经存在于深度缓冲中的深度值进行比较。这个比较函数也是可由开发者设置的,例如小于时舍弃该片元,或者大于时舍弃该片元。通常这个比较函数是小于等于,即如果这个片元的深度大于等于当前深度缓冲区中的值,那么就舍弃它。
  5. 合并混合:渲染过程是一个物体接着一个物体画到屏幕上,而每个像素的颜色信息被储存在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们使用这次渲染得到的颜色完全覆盖掉之前的结果还是进行其他处理,就是合并需要解决的。

对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会之间覆盖掉颜色缓冲区中的像素值。但对于半透明物体,就需要混合操作来让这个物体看起来是透明的。

3. 测试总结:
  • 各种测试的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试。因为当你在片元着色器进行了大量的计算及设置,最后测试没通过,可以说是计算成本全都浪费了。
  • 命令缓冲区包含了一个缓冲队列,由Cpu向其中添加命令,而由Gpu从中读取命令,添加和读取过程是相互独立的。

2. UnityShader

在这里插入图片描述
无光照->顶点片元
图像效果->特效
变体集->库文件

  • Shader的分类
  1. 表面着色器 Surface Shader (对顶点/片元着色器进行封装)
  2. 顶点/片元着色器 Vertex/Fragment Shader
  3. 固定函数着色器 Fixed Function Shader
1. 简介
  1. Mesh Filter : 存储一个Mesh(网格,模型的网格,就是模型的由哪些三角面组成,组成一个什么样子的模型,三角面的一些顶点信息)
  2. Mesh Renderer:用来渲染一个模型的外观,就是样子, 按照 mesh给它皮肤,给它颜色
  3. 通过Material(材质)控制模型渲染的样子
    Material:
    贴图(可以没有,可以是一个单纯的颜色)
    Shader
  4. shader可以认为是一种渲染命令 ,由opengl 或者dx进行解析,来控制渲染丰富多彩的图形
2. 各种坐标

R:x轴
G:y轴
B:z轴

1. 模型坐标
NORMAL
  • 1

在这里插入图片描述

2. 世界坐标
UnityObjectToWorldNormal(v.normal)
  • 1

在这里插入图片描述

3. 视图坐标
// 计算视图法线
// normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
// UNITY_MATRIX_IT_MV	模型逆转置 * 视图矩阵。
// 从模型空间 -> 逆转置到非模型空间 -> 视图空间
COMPUTE_VIEW_NORMAL
  • 1
  • 2
  • 3
  • 4
  • 5

该坐标系中,坐标系方向的(x,y)为摄像机的(x,y),而z轴朝向摄像机,摄像机的远处为z轴无穷小处。
在这里插入图片描述

4. 裁剪坐标
TransformViewToProjection(COMPUTE_VIEW_NORMAL)
  • 1

该坐标系中,坐标系方向的x向摄像机右,y向摄像机下,而z轴只是简单的深度,因此没有意义。
在这里插入图片描述

3. UnlitShader的基本结构
Shader "Liluo/01 myShader"{ // 这里是shader名字
	Properties{
		// 公有属性
		// 属性名,显示名,数据类型 float4
		_Color("tintColor", Color) = (1, 1, 1, 1)
	}
	// 子着色器,可以有很多。如果第一个支持就运行第一个,不支持就往下来
	SubShader{
        // 标签可选项
        Tags { "RenderType" = "Opaque" }
        
        // 可选的
        // 裁剪
        Cull off
		
		// 使用其他shader的pass通道
        UsePass "Unlit/011-卡通着色/Outline"
        
		// 至少有一个Pass
		// 在这里编写shader代码
		Pass{
			// 使用CG语言编写shader代码
			CGPROGRAM

			// 四个值的float类型
			float4 _color;
			
			ENDCG
		}
	}
	Fallback "VertexLit" // 后备方案,所有的都不支持时候,会选择该方案
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

Properties 块:属性块
SubShader 块:子着色器,可以有很多。如果第一个支持就运行第一个,不支持就往下来。
Fallback 块:所有的都不支持时,会选择该方案

4. SurfaceShader介绍
  1. 类似于UnlitShader,但没有 Pass 块。
  2. 是对UnlitShader的封装。
5. Unityshader中属性的类型
  1. 基本属性
    // 说是int,其实是float类型
    _Int("Int", Int) = 2
    // float
    _Float("float", Float) = 1.5
    // 范围数据 (float) float
    _Range("Range", Range(0.0, 2.0)) = 0.5
    // 颜色 float4
    _Color("Color", Color) = (1, 1, 1, 1)
    // float4
    _Vector("Vector", Vector) = (1, 2, 3, 4)
    // 图片类型 sampler2D类型,也可写作sampler2D_float类型提高精度,以防止在移动端贴图模糊
    // "white" 普通图像
    // "bump" 法线纹理
    _2D("Texture", 2D) = "white"{}
    // 立方体类型(立方体贴图文件,常见于天空盒) samplerCube类型
    _Cube("Cube", Cube) = "white"{}
    // 3D图片类型 sampler3D类型
    _3D("3D", 3D) = "black"{}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    公有的属性类型定义后,可在SubShader块中声明并使用。未定义的变量也可以在SubShader块中声明并使用。
  2. 私有变量 fixed 和 float
    fixed 的区间为[0, 1],因此适合作为颜色的rgba使用,精度较低开销小。
    fixed 精度较大开销高。
  3. 在访问floatX时,可访问其各个分变量值:
    // 访问四个值的
    float4.xyzw = float4.rgba
    // 访问任意三个值
    float4.xyz = float4.rgb
    float4.xz = float4.rb
    float4.x = float4.r
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  4. 自展开赋值
    fixed3 f3 = fixed3(1, 1, 1);
    fixed4 f4 = fixed4(1, f3);
    
    • 1
    • 2
6. SubShader块的基本组成
1. Tags:标签可选项
  1. 均为 key = value 样式
  2. Tags 可在 Pass 通道内编写,前提是去掉外部的 Tags,这时每个 Pass 将使用各自的 Tags。否则将使用外部的 Tags(只要外部的 Tags 存在)。
描述可选值
Queue渲染顺序(渲染器队列)Transparent
RenderType着色器替换功能Opaque
DisableBatching是否进行合批Bool
ForceNoShadowCasting是否投射阴影Bool
IgnoreProjector是否受投影影响Bool
CanUseSpriteAtlas是否用于图片的ShaderBool
PreviewType用于Shader面板预览的类型Plane
2. Render 设置

可在 Pass 通道内编写,前提是去掉外部的 Render,这时每个 Pass 将使用各自的 Render。否则将使用外部的 Render(只要外部的 Render 存在)。

  1. Cull:裁剪(默认:back)

    参数描述
    off两面都渲染
    back切掉背面,只渲染正面
    front切掉正面,只渲染背面
  2. ZTest:深度测试(默认:LEqual)

    参数描述
    Always总是
    Less小于
    Greater大于
    LEqual小于等于
    GEqual大于等于
    Equal等于
    NotEqual不等于
  3. ZWrite:深度写入(默认:On)

    On 开启
    Off 关闭

  4. Blend:混合模式(默认:Off)

    SrcFactor DstFactor :配置并启动混合。产生的颜色被乘以SrcFactor. 已存在于屏幕的颜色乘以DstFactor,并且两者将被叠加在一起。

  5. LOD:着色器细节级别(默认:无穷大

3. Pass 通道:
  1. Name :声明名称

    名称用于允许其他通道去use使用该通道
    例:Name “Default”

  2. 额外的Tags可选项

    描述可选值
    LightMode定义该Pass通道在Unity渲染流水中的角色ForwardBase
    RequireOptions声明它只在一些额外的条件被满足的情况下被渲染SoftVegetation
    ColorMask是否写入颜色通道0
  3. 使用其他shader的Pass通道

    • 确保被使用的Pass通道被设定了Name。
    • 通道内设定的参数,如果原本使用的是公有的属性,那么也需要在使用的通道内定义这些公有属性。
    • 编写代码以使用,如:UsePass "Unlit/011-卡通着色/Outline"
7. CGPROGRAM
CGPROGRAM
    // 声明顶点着色器函数名称 vert
    #pragma vertex vert
    // 声明片元着色器函数名称 frag
    #pragma fragment frag

    float4 vert (float4 v:POSITION):SV_POSITION
    {
        return UnityObjectToClipPos(v);
    }

    fixed4 frag () : SV_TARGET
    {
        return fixed4(1, 1, 1, 1);
    }
ENDCG
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
1. pragma

编译注释。用于声明某种着色器使用的函数。

着色器描述
vertex顶点着色器(模型空间->齐次裁剪空间)
fragment片元着色器(输出是一个或者多个颜色值)
target目标Shader Target

注:

  1. 关于target:
    在这里插入图片描述
2. 用语意来将值、输入与输出关联到某个值中
类型描述
POSITION顶点信息 [ f4 ]
NORMAL顶点法线 [ f3 ]
TANGENT顶点切线 [ f4 ]
TEXCOORD纹理坐标(包含颜色值的二维数组【位图】)[n套] [ f2 | f4 ]
COLOR顶点颜色 [ fx4 ] [n套]
SV_POSITION裁剪空间的顶点坐标 [ f4 ]
SV_TARGET目标返回值(输出将会储存到渲染目标(RenderTarget)缓存中) [ f4 ]

注:

  • unity会将第一套uv位图填充给TEXCOORD0。根据Shader Model版本,最多16套[4+],最少8套。
  • 语义也让变量作为寄存器使用,通常使用TEXCOORD0以后的纹理坐标。
  • SV_前缀代表system value,一旦被作为vertex shader的输出语义,那么这个最终的顶点位置就被固定了,用来标识经过着色器变换之后的值。
  • 结构体中必须包含一个用SV_POSITION语义修饰的变量。
3. 可用的函数方法
  1. 基本方法
函数功能描述
abs(x)返回输入参数的绝对值
acos(x)反余切函数,输入参数范围为[-1,1], 返回[0,π]区间的角度值
all(x)如果输入参数均不为0,则返回ture; 否则返回flase。&&运算
any(x)输入参数只要有其中一个不为0,则返回true。
asin(x)反正弦函数,输入参数取值区间为,返回角度值范围为,
atan(x)反正切函数,返回角度值范围为
atan2(y,x)计算y/x的反正切值。实际上和atan(x)函数功能完全一样,至少输入参数不同。atan(x) = atan2(x, float(1))。
ceil(x)对输入参数向上取整。例如: ceil(float(1.3)) ,其返回值为2.0
clamp(x,a,b)如果x值小于a,则返回a;如果x值大于b,返回b;否则,返回x。
cos(x)返回弧度x的余弦值。返回值范围为
cosh(x)双曲余弦(hyperbolic cosine)函数,计算x的双曲余弦值。
cross(A,B)返回两个三元向量的叉积(cross product)。注意,输入参数必须是三元向量!
degrees(x)输入参数为弧度值(radians),函数将其转换为角度值(degrees)
determinant(m)计算矩阵的行列式因子。
dot(A,B)返回A和B的点积(dot product)。参数A和B可以是标量,也可以是向量(输入参数方面,点积和叉积函数有很大不同)。
exp(x)计算的值,e=2.71828182845904523536
exp2(x)计算的值
floor(x)对输入参数向下取整。例如floor(float(1.3))返回的值为1.0;但是floor(float(-1.3))返回的值为-2.0。该函数与ceil(x)函数相对应。
fmod(x,y)返回x/y的余数。如果y为0,结果不可预料。
frac(x)返回标量或矢量的小数
frexp(x, out i)将浮点数x分解为尾数和指数,即, 返回m,并将指数存入i中;如果x为0,则尾数和指数都返回0
isfinite(x)判断标量或者向量中的每个数据是否是有限数,如果是返回true;否则返回false;
isinf(x)判断标量或者向量中的每个数据是否是无限,如果是返回true;否则返回false;
isnan(x)判断标量或者向量中的每个数据是否是非数据(not-a-number NaN),如果是返回true;否则返回false;
ldexp(x, n)计算的值
lerp(a, b, f)计算或者的值。即在下限a和上限b之间进行插值,f表示权值。注意,如果a和b是向量,则权值f必须是标量或者等长的向量。
lit(NdotL, NdotH, m)N表示法向量;L表示入射光向量;H表示半角向量;m表示高光系数。 函数计算环境光、散射光、镜面光的贡献,返回的4元向量。 X位表示环境光的贡献,总是1.0; Y位代表散射光的贡献,如果 ,则为0;否则为 Z位代表镜面光的贡献,如果 或者,则位0;否则为;W位始终位1.0
log(x)计算的值,x必须大于0
log2(x)计算的值,x必须大于0
log10(x)计算的值,x必须大于0
max(a, b)比较两个标量或等长向量元素,返回最大值。
min(a,b)比较两个标量或等长向量元素,返回最小值。
modf(x, out ip)把x分解成整数和分数两部分,每部分都和x有着相同的符号,整数部分被保存在ip中,分数部分由函数返回
mul(M, N)矩阵M和矩阵N的积
mul(M, v)矩阵M和列向量v的积
mul(v, M)行向量v和矩阵M的积
noise(x)根据它的参数类型,这个函数可以是一元、二元或三元噪音函数。返回的值在0和1之间,并且通常与给定的输入值一样
radians(x)函数将角度值转换为弧度值
round(x)返回四舍五入值。
rsqrt(x)x的平方根的倒数,x必须大于0
saturate(x)把x限制到[0,1]之间
sign(x)如果则返回1;否则返回0
sin(x)输入参数为弧度,计算正弦值,返回值范围 为[-1,1]
sincos(float x, out s, out c)该函数是同时计算x的sin值和cos值,其中s=sin(x),c=cos(x)。该函数用于“同时需要计算sin值和cos值的情况”,比分别运算要快很多!
sinh(x)计算x的双曲正弦
smoothstep(min, max, x)值x位于min、max区间中。如果x=min,返回0;如果x=max,返回1;如果x在两者之间,按照下列公式返回数据:
step(a, x)如果x<a返回0;如果x>=a返回1
sqrt(x)求x的平方根,,x必须大于0
tan(x)计算x正切值
tanh(x)计算x的双曲线切线
transpose(M)矩阵M的转置矩阵

几何函数(只对三元向量有效)

函数功能描述
distance(pt1, pt2)两点之间的欧几里德距离(Euclidean distance)
faceforward(N,I,Ng)如果,返回N;否则返回-N。
length(v)返回一个向量的模(长度),即sqrt(dot(v,v))
normalize(v)返回v向量的单位向量
reflect(I, N)根据入射光纤方向I和表面法向量N计算反射向量,仅对三元向量有效(入射方向为从目标朝向该对象)
refract(I,N,eta)根据入射光线方向I,表面法向量N和折射相对系数eta,计算折射向量。如果对给定的eta,I和N之间的角度太大,返回(0,0,0)。

纹理映射函数函数

函数功能描述
tex1D(sampler1D tex, float s)一维纹理查询
tex1D(sampler1D tex, float s, float dsdx, float dsdy)使用导数值(derivatives)查询一维纹理
Tex1D(sampler1D tex, float2 sz)一维纹理查询,并进行深度值比较
Tex1D(sampler1D tex, float2 sz, float dsdx,float dsdy)使用导数值(derivatives)查询一维纹理, 并进行深度值比较
Tex1Dproj(sampler1D tex, float2 sq)一维投影纹理查询
Tex1Dproj(sampler1D tex, float3 szq)一维投影纹理查询,并比较深度值
Tex2D(sampler2D tex, float2 s)二维纹理查询
Tex2D(sampler2D tex, float2 s, float2 dsdx, float2 dsdy)使用导数值(derivatives)查询二维纹理
Tex2D(sampler2D tex, float3 sz)二维纹理查询,并进行深度值比较
Tex2D(sampler2D tex, float3 sz, float2 dsdx,float2 dsdy)使用导数值(derivatives)查询二维纹理,并进行深度值比较
Tex2Dproj(sampler2D tex, float3 sq)二维投影纹理查询
Tex2Dproj(sampler2D tex, float4 szq)二维投影纹理查询,并进行深度值比较
tex2Dlod(textureMap, float4(texCoord.xy, 0, lod))使用 mipmap 对 2D 纹理进行采样。 mipmap LOD 在 t.w 中指定,越小细节采样越清晰,默认写0。
texRECT(samplerRECT tex, float2 s)二维非投影矩形纹理查询(OpenGL独有)
texRECT (samplerRECT tex, float3 sz, float2 dsdx,float2 dsdy)二维非投影使用导数的矩形纹理查询(OpenGL独有)
texRECT (samplerRECT tex, float3 sz)二维非投影深度比较矩形纹理查询(OpenGL独有)
texRECT (samplerRECT tex, float3 sz, float2 dsdx,float2 dsdy)二维非投影深度比较并使用导数的矩形纹理查询(OpenGL独有)
texRECT proj(samplerRECT tex, float3 sq)二维投影矩形纹理查询(OpenGL独有)
texRECT proj(samplerRECT tex, float3 szq)二维投影矩形纹理深度比较查询(OpenGL独有)
Tex3D(sampler3D tex, float s)三维纹理查询
Tex3D(sampler3D tex, float3 s, float3 dsdx, float3 dsdy)结合导数值(derivatives)查询三维纹理
Tex3Dproj(sampler3D tex, float4 szq)查询三维投影纹理,并进行深度值比较
texCUBE(samplerCUBE tex, float3 s)查询立方体纹理
texCUBE (samplerCUBE tex, float3 s, float3 dsdx, float3 dsdy)结合导数值(derivatives)查询立方体纹理
texCUBEproj (samplerCUBE tex, float4 sq)查询投影立方体纹理
  1. 其他方法
类型描述
UnityObjectToClipPos(float4)将点从对象空间变换到裁剪空间
clip(any)判断参数的xyzw值,其中有一个小于1,便舍弃该片元

UnityCG.cginc

类型描述
UnityViewToClipPos(float3)将点从视图空间变换到裁剪空间
UnityObjectToViewPos(float3)将点从对象空间变换到摄像机空间
UnityObjectToWorldNormal(float3)将本对象法线从对象空间变换到世界空间 [单位化]
UnityObjectToWorldDir(float3)将本对象方向从对象空间变换到世界空间 [单位化]
UnityWorldSpaceViewDir(float3)根据世界空间位置计算到摄像机的向量
UnityWorldSpaceLightDir(float3)根据世界空间位置计算到光的向量
WorldSpaceViewDir(float3)根据对象空间位置计算到摄像机的向量
WorldSpaceLightDir(float3)根据对象空间位置计算到光的向量
TRANSFORM_TEX(float4[texcoord], name[str])按比例/偏移特性变换2D UV
ObjSpaceLightDir(float4)计算在对象空间中光的向量
ObjSpaceViewDir(float4)计算在对象空间中视图的向量
UnpackNormal(fixed4)根据不同平台解包法线贴图信息,获取法线
UnpackScaleNormal(half4, half[scale])根据不同平台自动对法线贴图使用正确的解码,并缩放法线
4. 可用的静态变量
名称数据类型描述
_WorldSpaceLightPos0float4方向光:(世界空间方向,0),其他光源:(世界空间位置,1)
UNITY_LIGHTMODEL_AMBIENTfixed4环境光照颜色(梯度环境情况下的天空颜色)(旧版变量)
_LightColor0fixed4光源颜色(UnityLightingCommon.cginc)
_WorldSpaceCameraPosfloat3摄像机的世界空间位置。
unity_ObjectToWorldfloat4x4当前模型矩阵。用它乘以对象上的点,可得到对应世界空间的位置
_Timefloat4自关卡加载以来的时间 (t/20, t, t*2, t*3),用于将着色器中的内容动画化。
_Sin[Cos]Timefloat4时间正【余】弦:(t/8, t/4, t/2, t)

更多变量:https://docs.unity3d.com/cn/current/Manual/SL-UnityShaderVariables.html

5. 结构体
  1. 定义结构体
// application to vert
struct a2v
{
    float4 v:POSITION;
    float3 m:NORMAL;
    // 纹理坐标(用模型的第一套uv填充TEXCOORD)
    float4 texcoord:TEXCOORD0;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 将结构体作为入参
      float4 vert (a2v v):SV_POSITION
      {
          return UnityObjectToClipPos(v.v);
      }
  • 1
  • 2
  • 3
  • 4
  1. 将结构体作为返回值,并在其他函数体中使用
      // vert to frag
      struct v2f
      {
          // 裁剪空间的位置信息
          float4 pos:SV_POSITION;
          // 颜色信息
          float3 color:COLOR0;
      };

      v2f vert (a2v v)
      {
          v2f o;
          // 保存顶点信息
          o.pos = UnityObjectToClipPos(v.vertex);
          // 将法线方向转变为颜色表现。(-1~1)转变为(0,1)区间,因为法线的向量可能为负数。
          o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
          return o;
      }

      // 结构体一经定义便可在其他函数中作为参数调用,shader会自动将定义的结构体作为参数传入。
      fixed4 frag (v2f i) : SV_TARGET
      {
          // 颜色信息
          return fixed4(i.color, 1);
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
6. 特殊语句
语句描述
discard舍弃片元
clip(any)判断参数的xyzw值,其中有一个小于1,便舍弃该片元
7. 引入cginc Shader库

Unity会自动导入一些cginc基础库。若想引入其他库,需在CGPROGRAM下使用include注释

CGPROGRAM
#include "UnityCG.cginc"
ENDCG
  • 1
  • 2
  • 3

这样以来,我们便可使用该库内的函数或结构体。如UnityCG.cginc下的appdata_base:

struct appdata_base {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct appdata_full {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    float4 texcoord1 : TEXCOORD1;
    float4 texcoord2 : TEXCOORD2;
    float4 texcoord3 : TEXCOORD3;
    fixed4 color : COLOR;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在顶点着色器中作为入参使用

float4 vert (appdata_base v)
{
    return UnityObjectToClipPos(v.vertex);
}
  • 1
  • 2
  • 3
  • 4
8. 调试工具
  1. 打开 窗口->分析->帧调试器
  2. 启用并查看对象的渲染信息
    在这里插入图片描述
9. 平台差异

在这里插入图片描述

-. Unity的一些基础shader
  1. Legacy Shaders/Particles/Additive 实现部分叠加效果,并非完全叠加,没有Addtive明亮,颜色更加接近贴图自身的颜色。 半透渲染效果。
  2. Sprites/Diffuse 使图像接受全局光

3. Cg语言

1. Cg编译方式
  1. Cg通常采用动态编译的方式(Cg也支持静态编译方式),即在宿主程序运行时,利用Cg运行库(Cg Runtimer Library)动态编译Cg代码。使用动态编译的方式,可以将Cg程序当做一个脚本,随时修改随时运行,节省时间,在OGRE图形引擎中就采用了这种方式。
  2. Cg Profiles:Cg 程序的编译不但依赖于宿主程序所使用的三维编程接口,而且依赖于图形硬件环境,因为图形硬件自身的限制,不一定支持某种 Cg 语句。
2. Cg基本数据类型
  1. float, 32 位浮点数据,一个符号位。浮点数据类型被所有的 profile 支持
  2. half,16 为浮点数据
  3. int,32 位整形数据,有些 profile 会将 int 类型作为 float 类型使用
  4. fixed,12 位定点数,被所有的 fragment profiles 所支持
  5. bool,布尔数据,通常用于 if 和条件操作符( ?: ) ,布尔数据类型被所有的profiles 支持
  6. simpler*, 纹理对象的句柄( the handle to a texture object )
    分为 6 类:
    sampler, sampler1D, sampler2D, sampler3D, samplerCUBE, 和 samplerRECT 。
    DirectX profiles 不支持 samplerRECT 类型, 除此之外这些类型被所有的 pixelprofiles 和 NV40 vertex program profile 所支持( CgUsersManual 30 页) 。由此可见,在不远的未来,顶点程序也将广泛支持纹理操作
  7. string,字符类型,该类型不被当前存在的 profile 所支持,实际上也没有必要在 Cg 程序中用到字符类型,但是你可以通过 Cg runtime API 声明该类型变量,并赋值;因此,该类型变量可以保存 Cg 文件的信息。

前6种类型为常用类型,string类型几乎不使用。此外,Cg还提供了内置的向量数据类型 (built-in vector data types) ,内置的向量数据类型基于基础数据类型。 例如: float4, 表示 float 类型的 4 元向量; bool4, 表示 bool类型 4 元向量。

3. Cg复杂数据类型
1. 数组

数组类型在Cg中的作用:作为函数的形参,用于大量数据的传递,例如:顶点参数数组、光照参数数据等。

  1. 一维数组:
float a[10];//声明了一个数组,包含 10 个 float 类型数据
float a[4] = {1.0, 2.0, 3.0, 4.0}; //初始化一个数组
int length = a.length;//获取数组长度
  • 1
  • 2
  • 3
  1. 多维数组:
float b[2][3] = {{0.0, 0.0, 0.0},{1.0, 1.0, 1.0}};
int length1 = b.length; // length1 值为 2
int length2 = b[0].length; // length2 值为 3
  • 1
  • 2
  • 3
2. 结构体

结构体的声明以关键字 struct 开始,然后紧跟结构体的名字,接下来是一个大括号,并以分号结尾(不要忘了分号) 。大括号中是结构体的定义,分为两大类:成员变量和成员函数。
注意:不要忘记结尾的分号!!!

struct v2f
{
    float4 pos:SV_POSITION;
    float3 color:COLOR0;
};
  • 1
  • 2
  • 3
  • 4
  • 5

4. 光照

1. 光照介绍

在这里插入图片描述

2. 光照模型
1. 简介
  1. 标准光照模型->直接光照
  2. 步骤:光源->物体->摄像机
  3. 光照的四个部分:自发光、高光反射、漫反射、环境光(间接光照)
2. 自发光

直接发光进入摄像机(看起来更亮)。

3. 环境光

为简化模型,环境光不适用原本的完全间接反射光,而仅包括全局环境光。

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
  • 1
4. 漫反射

兰伯特漫反射:
Cdiffuse = C光(color)C物(color)( n(法线向量) · l(光源向量) )
注:原本应该是| n | | l | cosθ,但同时 | n | | l | cosθ = n · l(点乘)
在这里插入图片描述
shader示例:

struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 worldNormal: TEXCOORD0;
};
// 顶点着色器计算此漫发射
v2f vert (appdata_base v)
{
    v2f o;
    // 保模型观察矩阵全部投影到裁剪空间
    o.vertex = UnityObjectToClipPos(v.vertex);
    // 将法线转为世界坐标下的向量,因为只有同坐标系下的向量才能计算,而光源在世界坐标下,法线在模型坐标下。
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    // 世界方向光
    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
    // 计算漫反射
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
    // 添加环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    o.color = diffuse + ambient;
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
// 片元着色器逐像素计算此漫发射
struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 worldNormal: TEXCOORD0;
};

v2f vert (appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldLightDir, i.worldNormal));
    fixed3 color = ambient + diffuse;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
5. 高光反射

Phong高光反射:
Cdiffuse = C光(color)C物(color)( (2 (n(法线向量) · l(光源向量)) n(法线向量) - l(光源向量) ) · v(光源向量) )mgless

  • 原本应该是| r | | v | cosθ,但同时 | r | | v | cosθ = r · v(点乘)
  • ∵ r + l = 2 | l | cosθ n = 2 ( n · l ) n (注:n向量长为1)
    r = 2 ( n · l ) n - l
  • mgless:光泽度,用来决定高光范围(强度,越大亮度越低)
    在这里插入图片描述
    代码样例:
// 顶点着色器计算高光
v2fvert (appdata_base v)
{
    v2f o;
    // 保模型观察矩阵全部投影到裁剪空间
    o.vertex = UnityObjectToClipPos(v.vertex);
    // 将法线转为世界坐标下的向量,因为只有同坐标系下的向量才能计算,而光源在世界坐标下,法线在模型坐标下。
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    // 世界方向光
    fixed3 worldLight = WorldSpaceLightDir(v.vertex);
    // 计算漫反射
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
    // 添加环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

    // 高光反射单位向量(r向量)
    fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));
    // 从物体到摄像机的单位向量(v向量)
    fixed3 viewDir = normalize(WorldSpaceViewDir(v.vertex));
    // 计算高光颜色
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

    o.color = diffuse + ambient + specular;
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
// 片元着色器逐像素计算高光
struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 worldNormal: TEXCOORD0;
    float3 worldPos: TEXCOORD1;
};

v2f vert (appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    // 对应世界空间的位置
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldLightDir, i.worldNormal));
    
    // 高光反射单位向量(r向量)
    fixed3 reflectDir = normalize(reflect(-worldLightDir, i.worldNormal));
    // 从物体到摄像机的单位向量(v向量)
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    // 计算高光颜色
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

    fixed3 color = ambient + diffuse + specular;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Blinn-Phong高光反射:
CBlinn-Phong-diffuse = (C光(color)C物(color)) (n(法线向量) · h(光源和摄像机向量的平均单位向量))mgless
在这里插入图片描述

// 片元着色器逐像素计算高光
struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 worldNormal: TEXCOORD0;
    float3 worldPos: TEXCOORD1;
};

v2f vert (appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldLightDir, i.worldNormal));
    
    // 从物体到摄像机的单位向量
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    // 光源和摄像机向量的平均单位向量(h向量)
    fixed3 halfDir = normalize(worldLightDir + viewDir);
    // 计算高光颜色
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal, halfDir)), _Gloss);

    fixed3 color = ambient + diffuse + specular;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

5. 图像

1. 图片采样
  1. _MainTex ("MainTex", 2D) = "white" {} 即2D类型变量可用于赋值一个2D纹理材质。
  2. 使用这个材质时,定义私有变量sampler2D _MainTex;来获取到这个纹理材质。
  3. 值得注意的是,内部会自动寻找变量名为私有变量+_ST的fixed4变量,作为纹理材质的纹理信息(Tiling与Offset)。
  4. 作为入参的语义为TEXCOORD0变量会储存有该顶点的纹理坐标信息。
  5. 使用函数tex2D(sampler2D tex, float2 s)来获得对应图像在指纹理坐标的颜色。
  6. 若在顶点函数中,需使用tex2Dlod(textureMap, float4(texCoord.xy, 0, lod))进行采样。
// 纹理材质
sampler2D _MainTex;
// 自动赋值对应的纹理信息(Tiling与Offset)
float4 _MainTex_ST;

struct v2f
{
    float4 vertex: SV_POSITION;
    float2 uv: TEXCOORD0;
};

// 顶点着色器计算纹理坐标映射
v2f vert (appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    // v.texcoord.xy 为指定点的纹理坐标,与材质xy相乘模拟缩放,与zw相加模拟偏移
    // o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    return o;
}

// 片元着色器渲染颜色
fixed4 frag (v2f i) : SV_Target
{
    // 查询对应图像在指纹理坐标的颜色
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
    return fixed4(albedo, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
2. 图片设置

主要对图片的两个部分进行设置

  1. 贴图间拼接模式
参数描述
重复重复拼接贴图
钳制贴图只渲染一次,超出的部分重复渲染最后一像素
镜像重复拼接贴图,但每次重复都和上次的y轴反向
每轴自定义xy的上述选项
  1. 过滤模式
    简单来说,滤波越高,透视远处的部分越清晰。

  2. 法线贴图:从灰度创建
    转化为高度映射。

6. 凹凸映射

  1. 用于在不增加模型面数的前提下,表现出模型的凹凸感。
  2. 在Unity中,法线贴图应该在纹理类型中被指定。且对于黑白法线图,可选择从灰度创建转换为法线贴图,凹凸程度决定纹理强烈程度。
1. 高度映射

通过改变法线的值(长度),获得不同的凹凸效果,也就是(r,g,b,a)->1[白]: 高,0[黑]: 低。

2. 法线映射

纹理中的法线,分量范围在(-1,1)中,而贴图的RGB在(0,1)内。
因此需转换公式:

pixel = n * 0.5 + 0.5
n = 2 * pixel -1。

1. 模型空间的法线映射

一个绝对的法线信息集:意味着使用它时不能换模型。(因为模型之间的法线不可能相同,而绝对的法线信息集与原本的法线信息每个点都一一对应)

n = (0,1,0)-> 纹理RGB(0.5,1,0.5)
n = (0,-1,0) -> 纹理RGB(0.5,0,0.5)
n = (1,1,0) -> 纹理RGB(1,1,0.5)

  • 实现简单,更加直观。计算量少。(绝对的法线信息)
  • 在纹理的边角部分,缝隙较少,可提供平滑的边界。是因为所有法线都是在同一坐标空间中,可以在边角处通过插值进行平滑变换。
2. 切线空间的法线映射

切线空间:模型某个顶点的切面作为(x,y)轴,向上的垂直法线作为z轴。
该空间下:我们定义法线为n,切线(x轴)为t,副切线(y轴)为b。,则:

n = b × t
b = t × n

在未发生变动时,切线空间下新的法线:z轴(0,0,1) => RGB(0.5,0.5,1),大部分法线是不变的,Z轴总是正方向,可以只存储XY轴,推到得到Z轴。

  • 自由度高。切线空间下的法线纹理是相对法线纹理,即便在一个不同的网格上也可以得到一个合理的结果。
  • 可进行UV动画。可以通过移动一个纹理的UV坐标来实现一个凹凸移动的效果。
  • 可重用法线纹理。
  • 可压缩。切线空间下,Z轴方向总是正方向,可以只存储XY轴,推导得到Z轴。
3. 切线空间法线映射的两种方式

世界空间->切线空间法线纹理映射

  1. 取得对应坐标点的纹理uv坐标
    TRANSFORM_TEX(v.texcoord, _BumpMap);
    
    • 1
  2. 求副切线向量
    // 等效于:
    // cross(v.normal, v.tangent.xyz * v.tangent.w)
    cross(v.normal, v.tangent.xyz) * v.tangent.w;
    
    • 1
    • 2
    • 3
  3. 求切线空间旋转矩阵,让其他模型空间的向量转换到切线空间。
    而旋转矩阵的值,正如常规坐标系下的单位矩阵(x,y,z)相同,为(切线向量[x],副切线向量[y],法线向量[z])
    float3x3(v.tangent.xyz, binormal, v.normal);
    
    • 1
  4. 求切线空间光源方向及摄像机方向
    mul(rotation, ObjSpaceLightDir(v.vertex));
    mul(rotation, ObjSpaceViewDir(v.vertex));
    
    • 1
    • 2
  5. 获得法线贴图此处的颜色值
    tex2D(_BumpMap, i.normaluv);
    
    • 1
  6. 根据法线贴图的颜色值,转化为法向量。
    // 没有设置成 normal map
    // fixed3 tangentNormal;
    // tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
    // tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
    
    // 设置成 normal map,使用系统函数UnpackNormal:根据不同平台解包法线贴图信息,获取法线
    fixed3 tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  7. 在进行光照计算时,使用切线空间内的光照向量、视图(摄像机)向量,法线向量进行运算即可。

参考示例:

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

sampler2D _MainTex;
float4 _MainTex_ST;

sampler2D _BumpMap;
fixed4 _BumpMap_ST;
float _BumpScale;

// 片元着色器逐像素计算此漫发射
struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 lightDir: TEXCOORD0;
    float3 viewDir: TEXCOORD1;
    float2 uv: TEXCOORD2;

    float2 normaluv : TEXCOORD3;
};

v2f vert (appdata_tan v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

    o.normaluv = TRANSFORM_TEX(v.texcoord, _BumpMap);
    // 求副切线向量
    // float3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
    // float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
    // 系统已经宏定义求副切线向量以及旋转矩阵的此行为
    TANGENT_SPACE_ROTATION;
    //  求切线空间光源方向及视角方向
    o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
    o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed3 tangentLightDir = normalize(i.lightDir);
    fixed3 tangentViewDir = normalize(i.viewDir);
    // 法线贴图
    fixed4 packedNormal = tex2D(_BumpMap, i.normaluv);

    // 法线纹理没有设置成 normal map
    // fixed3 tangentNormal;
    // tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
    // tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

    // 法线纹理设置成 normal map
    fixed3 tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;

    // 环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
    // 漫反射
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo * (saturate(dot(tangentLightDir, tangentNormal)) * 0.5 + 0.5);
    fixed3 reflectDir = normalize(reflect(-tangentLightDir, tangentNormal));
    // 高光反射
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, tangentViewDir)), _Gloss);

    fixed3 color = ambient + diffuse + specular;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

切线空间->世界空间法线纹理映射

  1. 取得对应坐标点的纹理uv坐标
    TRANSFORM_TEX(v.texcoord, _BumpMap);
    
    • 1
  2. 计算世界坐标下的顶点位置,法线,切线,副法线
    float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
    
    • 1
    • 2
    • 3
    • 4
  3. 取得对应坐标点的纹理uv坐标
    TRANSFORM_TEX(v.texcoord, _BumpMap);
    
    • 1
  4. 求世界空间旋转矩阵,让切线空间转换到世界空间。
    而旋转矩阵的值,正如常规坐标系下的单位矩阵(x,y,z)相同,为(切线向量[x],副切线向量[y],法线向量[z])
    由于是从切线空间转换到世界空间而并非是从世界空间转换到切线空间,因此需要使用逆矩阵。
    o.tanToWorld0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
    o.tanToWorld1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
    o.tanToWorld2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z);
    
    • 1
    • 2
    • 3
  5. 计算世界空间下的光照和视角
    float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
    
    • 1
    • 2
  6. 获得法线纹理
    fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
    fixed3tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;
    
    • 1
    • 2
    • 3
  7. 切线空间法线转换到世界坐标(由于矩阵乘法性质,以下两个式子等价)
    fixed3 worldNormal = normalize(float3(dot(i.tanToWorld0, tangentNormal), dot(i.tanToWorld1, tangentNormal), dot(i.tanToWorld2, tangentNormal)));
    fixed3 worldNormal = normalize(mul(float3x3(i.tanToWorld0, i.tanToWorld1, i.tanToWorld2), tangentNormal));
    
    • 1
    • 2

参考示例:

fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;

sampler2D _MainTex;
float4 _MainTex_ST;

sampler2D _BumpMap;
fixed4 _BumpMap_ST;
float _BumpScale;

// 片元着色器逐像素计算此漫发射
struct v2f
{
    float4 vertex: SV_POSITION;
    float4 uv: TEXCOORD0;
    float3 tanToWorld0: TEXCOORD1;
    float3 tanToWorld1: TEXCOORD2;
    float3 tanToWorld2: TEXCOORD3;
    float3 worldPos: TEXCOORD4;
};

v2f vert (appdata_tan v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    // 图像信息
    o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
    // 法线纹理信息
    o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

    // 计算世界坐标下的顶点位置,法线,切线,副法线
    float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

    o.worldPos = worldPos;
    o.tanToWorld0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
    o.tanToWorld1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
    o.tanToWorld2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    // 世界坐标
    float3 worldPos = i.worldPos;
    
    // 计算世界空间下的光照和视角
    float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
    
    // 获得法线纹理
    fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
    fixed3 tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;

    // 切线空间法线转换到世界坐标(由于矩阵乘法性质,以下两个式子等价)
    // fixed3 worldNormal = normalize(float3(dot(i.tanToWorld0, tangentNormal), dot(i.tanToWorld1, tangentNormal), dot(i.tanToWorld2, tangentNormal)));
    fixed3 worldNormal = normalize(mul(float3x3(i.tanToWorld0, i.tanToWorld1, i.tanToWorld2), tangentNormal));

    // 环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb;
    // 漫反射
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo * (saturate(dot(lightDir, worldNormal)) * 0.5 + 0.5);
    fixed3 reflectDir = normalize(reflect(-lightDir, worldNormal));
    // 高光反射
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

    fixed3 color = ambient + diffuse + specular;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

7. 纹理模型

1. 渐变纹理
1. 简介

让影印部的颜色值根据图片约束变化。
在这里插入图片描述

2. 原理
  1. 使用半兰伯特模型获取光照强度,并保证获得的值在(0,1)之内(开区间)
    fixed halfLambert = saturate(dot(worldLightDir, i.worldNormal)) * 0.99 + 0.005;
    
    • 1
  2. 根据光照强度在图片上采样对应的颜色值
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * tex2D(_RampTex, fixed2(halfLambert, 0.5)).rgb;
    
    • 1

示例:

struct v2f
{
     float4 vertex: SV_POSITION;
     fixed3 worldNormal: TEXCOORD0;
     float3 worldPos: TEXCOORD1;
 };

 v2f vert (appdata_base v)
 {
     v2f o;
     o.vertex = UnityObjectToClipPos(v.vertex);
     o.worldNormal = UnityObjectToWorldNormal(v.normal);
     o.worldPos = mul(unity_ObjectToWorld, v.vertex);
     return o;
 }
 
 fixed4 frag (v2f i) : SV_Target
 {
     fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
     fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
     fixed halfLambert = saturate(dot(worldLightDir, i.worldNormal)) * 0.99 + 0.005;
     fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * tex2D(_RampTex, fixed2(halfLambert, 0.5)).rgb;
     
     fixed3 reflectDir = normalize(reflect(-worldLightDir, i.worldNormal));
     fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
     fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

     fixed3 color = diffuse + specular + ambient;
     return fixed4(color, 1);
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
2. 遮罩纹理
1. 简介

让打出的光在对象上形成图像,效果有点类似于法线贴图,但本质是遮罩,也就无法形成法线纹理那样:在光源或摄像机移动时,反光点也随之移动的立体感。
在这里插入图片描述

2. 原理
  1. 取得对应坐标点的纹理uv坐标
    TRANSFORM_TEX(v.texcoord, _SpecularMask);
    
    • 1
  2. 根据图像的某一通道值,决定遮罩程度与强度
    tex2D(_SpecularMask, i.maskuv).r * _SpecularScale;
    
    • 1

示例:

fixed4 _Diffuse;
fixed4 _Specular;
sampler2D _MainTex;
float4 _MainTex_ST;

// 高光范围
float _Gloss;
// 高光强度
float _SpecularScale;
// 高光遮罩
sampler2D _SpecularMask;
fixed4 _SpecularMask_ST;

struct v2f
{
    float4 vertex: SV_POSITION;
    fixed3 worldNormal: TEXCOORD0;
    float3 worldPos: TEXCOORD1;
    float2 uv: TEXCOORD2;
    float2 maskuv : TEXCOORD3;
};

v2f vert (appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    o.maskuv = TRANSFORM_TEX(v.texcoord, _SpecularMask);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;

    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo * (saturate(dot(worldLightDir, i.worldNormal)) * 0.5 + 0.5);
    
    // 高光反射单位向量(r向量)
    fixed3 reflectDir = normalize(reflect(-worldLightDir, i.worldNormal));
    // 从物体到摄像机的单位向量(v向量)
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    
    // 高光遮罩
    fixed3 specularMask = tex2D(_SpecularMask, i.maskuv).r * _SpecularScale;
    // 计算高光颜色
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss) * specularMask;

    fixed3 color = ambient + diffuse + specular;
    return fixed4(color, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
3. 雪地纹理
1. 简介

让地面或建筑上形成雪斑效果,模拟雪景。
在这里插入图片描述

2. 原理
  1. 通常情况下,雪地要和法线贴图结合使用才能达到满意的效果。
  2. 将下雪的方向(对象表面接收雪的方向)与对象法线点乘,结果的若大于某个阈值,则判定此处为有雪的地方。这些地方统一叠加雪的颜色。
    if (dot(worldNormal, _SnowDir.xyz) > lerp(1, -1, _Snow))
    {
        color.rgb += _SnowColor.rgb;
    }
    
    • 1
    • 2
    • 3
    • 4

示例:

fixed4 frag (v2f i) : SV_Target
{
    // 材质
    fixed4 albedo = tex2D(_MainTex, i.uv);
    
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

    // 获得法线纹理
    fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
    fixed3 tangentNormal = UnpackNormal(packedNormal);
    tangentNormal.xy *= _BumpScale;

    // 逆矩阵将切线空间法线旋转到世界空间
    fixed3 worldNormal = normalize(mul(float3x3(i.tanToWorld0, i.tanToWorld1, i.tanToWorld2), tangentNormal));
    
    // 环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    // 漫反射
    float difLight = saturate(dot(worldLightDir, worldNormal)) * 0.5 + 0.5;
    // 层次阴影
    float toon = floor(difLight * _Step) / _Step;
    difLight = lerp(difLight, toon, _ToonEffect);
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo * difLight;
    float4 color = float4(ambient + diffuse, 1);

	// 此处添加下雪效果
    if (dot(worldNormal, _SnowDir.xyz) > lerp(1, -1, _Snow))
    {
        color.rgb += _SnowColor.rgb;
    }
    
    return color;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

8. 透明效果

渲染顺序
名称队列索引号描述
Background1000这个渲染队列会在其他任何队列之前被渲染,通常用来渲染背景
Geometry2000默认的渲染队列,大多数物体使用这个队列,不透明物体使用这个队列
AlphaTest2450需要透明度测试的物体使用的队列
Transparent3000这个队列的物体会在所有Geometry和AlphatTest物体渲染之后,再按照从后往前的顺序进行渲染。任何透明物体都使用该队列
Overlay4000该队列实现一些叠加效果。任何最后渲染的物体在该队列。
1. 透明度测试
1. 简介:
  • 只要一个片元的透明度不满足条件(通常小于某个阈值),那么就舍弃对应的片元。
  • 被舍弃的片元不会在进行任何的处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体来处理,即进行深度测试,深度写入等等。
  • 虽然简单,但是很极端,要么完全透明,要么完全不透明。
2. 原理:
  1. 舍弃指定透明度以下的片元
    if ((albedo.a - _Cutoff) < 0)
    {
    	// 舍弃片元
        discard;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

示例:

Tags 
{ 
    "Queue" = "AlphaTest"
    "IgnoreProjector" = "True"

}
LOD 100

Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"
    #include "UnityLightingCommon.cginc"

    sampler2D _MainTex;
    fixed4 _MainTex_ST;
    fixed4 _Diffuse;

    float _Cutoff;

    // 片元着色器逐像素计算此漫发射
    struct v2f
    {
        float4 vertex: SV_POSITION;
        fixed3 worldNormal: TEXCOORD0;
        float3 worldPos: TEXCOORD1;
        float2 uv: TEXCOORD2;
    };

    v2f vert (appdata_base v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.worldNormal = UnityObjectToWorldNormal(v.normal);
        // 对应世界空间的位置
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
        return o;
    }
    
    fixed4 frag (v2f i) : SV_Target
    {
        fixed4 albedo = tex2D(_MainTex, i.uv).rgba;

        if ((albedo.a - _Cutoff) < 0)
        {
            discard;
        }
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
        fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
        fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo.rgb * (saturate(dot(worldLightDir, i.worldNormal)) * 0.5 + 0.5);
        
        fixed3 color = ambient + diffuse;
        return fixed4(color, 1);
    }
    ENDCG
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
2. 透明度混合
1. 简介:
  • 可以得到真正的半透明效果,它会使当前片元的透明度作为混合因子,与已经储存在颜色缓冲中的颜色值进行混合,得到新的颜色。
  • 透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
  • 至于为什么要关闭深度写入,其一是为了以免直接覆盖,其二是如果在透明物体与不透明物体之间添加新物体时,如果开启了深度写入会使得该点的深度在距离摄像机更近的透明物体上,以至于新物体的深度测试不通过而不渲染。
  • 注意:透明度混合只关闭了深度写入,但没有关闭深度测试。这表示当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果深度值距离摄像机更远,那么就不会在进行混合操作。比如一个不透明物体在透明物体前面,我们先渲染透明物体,可以正常的挡住透明物体。
2. 详解:

源颜色:该片元颜色
目标颜色:已经存在于颜色缓冲的颜色

  1. Render 设置中只要有关键字Blend,就代表开启混合(除Blend off以外)

    Blend SrcFactor DstFactor : 开启混合,并设置混合因子。源颜色乘以SrcFactor,目标颜色会乘以DstFactor,然后把两者相加后在存入颜色缓冲区。

    Blend SrcFactor DstFactor,SrcFactorA DstFactorA : 与上面一样,只是使用不同的因子来混合透明通道。

  2. 混合设置

    参数描述
    One因子为1
    Zero因子为0
    SrcColor因子为元颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合公式的时候,使用SrcColor的A分量作为混合因子。
    SrcAlpha因子为源颜色的透明度值(A通道)
    DstColor因子目标颜色值。相当于混合RGB通道的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A通道的混合等式时,使用DstColor的A分量作为混合因子。
    DstAlpha因子为目标颜色的透明度值(A通道)
    OneMinusSrcColor因子为(1-源颜色)。相当于混合RGB通道的混合等式时,使用结果RGB分量作为混合因子;当用于混合A通道的混合等式时,使用结果的A分量作为混合因子。
    OneMinusSrcAlpha因子为(1-源颜色的透明度值)
    OneMinusDstColor因子为(1-目标颜色)。相当于混合RGB通道的混合等式时,使用结果RGB分量作为混合因子;当用于混合A通道的混合等式时,使用结果的A分量作为混合因子。
    OneMinusDstAlpha因子为(1-目标颜色的透明度值)
  3. 常见混合操作类型

    //正常(Normal)透明度混合
    Blend SrcAlpha OneMinusSrcAlpha
    //柔和相加
    Blend OneMinusDstColor One
    //正片叠底
    Blend DstColor Zero
    //两倍相乘
    Blend DstColor SrcColor
    //变暗
    BlendOp min
    Blend One One
    //变亮
    Blend OneMinusDstColor One
    Blend One OneMinusSrcColor
    //线性减淡
    Blend One One
    Blend SrcAlpha One
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
3. 原理:
  1. 将渲染顺序设置为Transparent(透明层)
    Tags 
    { 
        "Queue" = "Transparent"
        "IgnoreProjector" = "True"
        "RenderType" = "Transparent"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  2. 在Pass通道中,关闭z层写入,设定blend,定义在Unity渲染流水中的角色
    Tags 
    { 
        "LightMode" = "ForwardBase"
    }
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  3. 由于透明通道不写入Zwrite,这会导致相同对象在有双层纹理时远侧覆盖近平面,因此需额外添加一个pass通道写入Zwrite确保更近的地方被正确渲染。设置ColorMask 0来确保这个pass通道不会渲染任何颜色。
    如果是面片可以不写。
    Pass
    {
        ZWrite On
        // 意味着不写入任何颜色通道
        ColorMask 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  4. 返回颜色时启用alpha入参
    示例:
    Tags 
    { 
        "Queue" = "Transparent"
        "IgnoreProjector" = "True"
        "RenderType" = "Transparent"
    }
    LOD 100
    
    Pass
    {
        ZWrite On
        // 意味着不写入任何颜色通道
        ColorMask 0
    }
    Pass
    {
        Tags 
        { 
            "LightMode" = "ForwardBase"
        }
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
    
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
    
        #include "UnityCG.cginc"
        #include "UnityLightingCommon.cginc"
    
        sampler2D _MainTex;
        fixed4 _MainTex_ST;
        fixed4 _Diffuse;
    
        float _AlphaScale;
    
        // 片元着色器逐像素计算此漫发射
        struct v2f
        {
            float4 vertex: SV_POSITION;
            fixed3 worldNormal: TEXCOORD0;
            float3 worldPos: TEXCOORD1;
            float2 uv: TEXCOORD2;
        };
    
        v2f vert (appdata_base v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            // 对应世界空间的位置
            o.worldPos = mul(unity_ObjectToWorld, v.vertex);
            o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
            return o;
        }
        
        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 texColor = tex2D(_MainTex, i.uv);
    
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * texColor.rgb * (saturate(dot(worldLightDir, i.worldNormal)) * 0.5 + 0.5);
            
            fixed3 color = ambient + diffuse;
            return fixed4(color, texColor.a * _AlphaScale);
        }
        ENDCG
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
4. 关于两层pass通道先后的问题:
  1. 越靠后的pass通道越后被渲染。
  2. 在上示的pass通道中,若ZWrite On的pass通道在下方,将会导致透明混合被叠加,如图
    在这里插入图片描述
    这是不合理的,因此需要将ZWrite On的pass通道放在上方。
    在这里插入图片描述

9. 卡通着色

1. 描边
1. 原理

由于模型正反两面中,正面总是会覆盖背面的特性,可用模型背面来渲染描边。

2. 详解
  1. 让模型背面单独使用一个Pass通道,并设置Cull Front仅渲染背面。
  2. 让所有顶点沿法线外扩一个距离,渲染即可得到描边。
  3. 当设置zwrite为off时,由于外层遮挡内层的特性会使得仅有最外层有描边。而启用时,每个模型块的边缘会因为zwrite的写入,外层无法遮挡而含有描边。

物体空间外拓:

v2f vert(appdata_base v)
{
    v2f o;
    v.vertex.xyz += v.normal * _Outline;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

视角空间外拓

v2f vert(appdata_base v)
{
    v2f o;
    // 从 UnityObjectToViewPos 演变而来
    // UNITY_MATRIX_V	当前视图矩阵。
    float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, v.vertex));
    // 计算视图法线
    // normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
    // UNITY_MATRIX_IT_MV	模型逆转置 * 视图矩阵。
    // 从模型空间 -> 逆转置到非模型空间 -> 视图空间
    float3 normal = COMPUTE_VIEW_NORMAL; 
    pos = pos + float4(normal, 0) * _Outline;
    // 从 UnityViewToClipPos 演变而来
    // UNITY_MATRIX_P 当前投影矩阵
    o.vertex = mul(UNITY_MATRIX_P, pos);
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

裁剪空间外拓

v2f vert(appdata_base v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    // 计算视图法线
    float3 normal = COMPUTE_VIEW_NORMAL;
    // 从视图空间转换到裁剪空间。由于转换到裁剪空间后z轴只是简单的深度,因此没有意义。
    float2 viewNormal = TransformViewToProjection(normal.xy);
    o.vertex.xy += viewNormal * _Outline;
    
    return o;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
1. 颜色处理
1. 原理

通过floor函数让颜色分层,实现类似三渲二的效果

2. 详解
  1. floor 入参向下取整,将值分块
    floor(difLight * _Steps) / _Steps;
    
    • 1
  2. lerp设置分割程度,值越低保留柔滑(常规投影)效果越大。
    lerp(difLight, toon, _ToonEffect)
    
    • 1
3. 示例
fixed4 frag (v2f i) : SV_Target
{
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed4 albedo = tex2D(_MainTex, i.uv);
    fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
    float difLight = saturate(dot(worldLightDir, i.worldNormal)) * 0.5 + 0.5;
    
    // floor 入参向下取整,将值分块
    float toon = floor(difLight * _Steps) / _Steps;
    // 分割程度
    difLight = lerp(difLight, toon, _ToonEffect);
    
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * albedo * difLight;
    return float4(ambient + diffuse, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2. 边缘光
1. 原理
  • 法线与视角向量夹角垂直时,可判定此处为边缘。
  • 因此我们可以使用内积来对此进行判断:值为0时夹角垂直。
2. 示例
  1. 根据n次方函数决定边缘光面积,这样得到的边缘光更加柔和
// 法线
float3 worldNormal = UnityObjectToWorldNormal(v.normal)
// 单位化视角向量
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

float rim = 1 - dot(worldNormal, viewDir);
fixed3 rimColor = _RimColor * pow(rim, _RimPower);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

  1. 根据余弦函数决定边缘光面积,这样得到的边缘光更加锐利,呈现出明显的条状
// 法线
float3 worldNormal = UnityObjectToWorldNormal(v.normal)
// 单位化视角向量
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

float radian = 90 / 57.296;
float rim = dot(worldNormal, viewDir) * radian;
fixed3 rimColor = _RimColor * cos(min(rim * _RimPower, radian));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

3. X-ray
1. 原理
  • 深度测试,原本是小于等于才会被画出,现在让它只能在大于时画出。这样就会在原本遮挡掉的模型区域绘制出X-ray。ZTest Greater

效果图:
在这里插入图片描述

2. 详解
  1. 通道无需阴影。
  2. 深度测试修改为大于时画出。
  3. 关闭深度写入,以防因为绘制Xray覆盖了深度写入,导致在该对象和更靠近摄像机的对象中间的对象被渲染到最上层。
  4. 设置为透明通道,原通道为原alpha,目标通道颜色保持不变,形成叠加颜色。
  5. X-ray边缘颜色的实现与边缘光的实现大同小异,因此不再赘述。

示例:

Pass
{
    Name "XRay"
    // 该通道无需阴影
    Tags{ "ForceNoShadowCasting" = "true" }
    // 深度测试,原本是小于等于才会被画出,现在让它只能在大于时画出
    ZTest Greater
    // 关闭深度写入,以防因为绘制Xray覆盖了深度写入,导致在该对象和更靠近摄像机的对象中间的对象被渲染到最上层。
    ZWrite Off
    Blend SrcAlpha One
    
    CGPROGRAM

    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"

    float4 _XRayColor;
    float _XRayPower;

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float3 viewDir : TEXCOORD1;
        float3 normal : TEXCOORD2;
    };

    v2f vert(appdata_base v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        // 模型空间视图的向量
        o.viewDir = normalize(ObjSpaceViewDir(v.vertex));
        // 模型空间法向量
        o.normal = normalize(v.normal);
        return o;
    }
    
    float4 frag(v2f i): SV_TARGET
    {
        float rim = 1 - dot(i.normal, i.viewDir);
        return _XRayColor * pow(rim, _XRayPower);
    }

    ENDCG
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

10. Shader宏

1. 用途
  1. 用于开启或关闭某个shader效果功能的启用,节省资源消耗。
  2. shader宏是全局的,因此不同shader的不同功能要分开命名。
  3. 对于某种效果,使用全局宏变量进行整体调整而不用遍历所有材质的shader。
2. 原理
1. shader中
  1. 定义一个Shader宏,__必须要写,此处名字为SNOW_ON
#pragma multi_compile __ SNOW_ON
  • 1
  1. 将对应功能语句用#if语句包裹
    注:_SNOWHIGH变量在pass通道内定义了,但并没有在该shader的公有属性中定义,这样的变量会自动与全局shader的全局变量关联,方可通过全局shader的变量控制函数调整。
#if SNOW_ON
if (dot(worldNormal, _SnowDir.xyz) > lerp(1, -1, _Snow * _SNOWHIGH))
{
    color.rgb += _SnowColor.rgb;
}
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2. c#中
  1. 判断某个名称的Shader宏是否被起启用
    Shader.IsKeywordEnabled(SNOW_ON);
    
    • 1
  2. 启用Shader宏
    Shader.EnableKeyword(SNOW_ON);
    
    • 1
  3. 禁用Shader宏
    Shader.DisableKeyword(SNOW_ON);
    
    • 1
  4. 修改Shader宏变量值,这样的值必须是没有在shader的公有属性中定义,但在pass通道中被使用的变量
    Shader.SetGlobalFloat(_SNOWHIGH, 0.5f);
    
    • 1
  5. 修改特定材质的Shader宏
    Material mat;
    mat.EnableKeyword("DB_ON");
    
    • 1
    • 2

11. 其他

1. 顶点颜色
struct VertexInput {
    float4 vertex : POSITION;
    float2 texcoord : TEXCOORD0;
    float4 vertexColor : COLOR;
};
struct v2f
{
    float4 vertex: SV_POSITION;
    float2 uv: TEXCOORD0;
    float4 vertexColor : COLOR;
};

v2f vertBase (VertexInput v) 
{ 
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    o.vertexColor = v.vertexColor;
    return o;
}

half4 fragBase (v2f i) : SV_Target 
{ 
    // 查询对应图像在指纹理坐标的颜色
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb;
    return i.vertexColor * fixed4(albedo, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/107682?site
推荐阅读
相关标签
  

闽ICP备14008679号