前置知识:基础的图形学。
参考链接:
一个基础的 shader ,纯色。
Shader "Custom/TestShader"{ // Shader 命名
// 定义属性
Properties{
m_color("Color",Color)=(1,1,1,1) // 定义一个 Color 属性,可以在 Inspector 中调整
}
// 定义着色器,系统会选第一个可以用的 SubShader 进行着色
SubShader{
Pass{
CGPROGRAM
float4 m_color; // 从 Unity 的属性拿出值
// 顶点函数(对模型顶点处理)
#pragma vertex vert
float4 vert(float4 v:POSITION):SV_POSITION{
// POSITION: 表明 v 是顶点坐标,系统会传过来
// SV_POSITION: 表明该函数返回值是剪裁空间下的坐标
// UnityObjectToClipPos(v): 把 v 从模型空间->裁剪空间
// 等价于 mul(UNITY_MATRIX_MVP, v); 乘上 MVP 矩阵
return UnityObjectToClipPos(v);
}
// 片元函数(对屏幕像素处理)
#pragma fragment frag
float4 frag():SV_TARGET{
// SV_TARGET: 着色的结果
return m_color;
}
ENDCG
}
}
// SubShader 全炸了的备用方案
Fallback "VertexLit"
}
从系统传给顶点函数的语义(模型空间下):
POSITION
:顶点位置NORMAL
:顶点法线TANGENT
:顶点切线TEXCOORD0~TEXCOORDn
:纹理坐标(可以有好几套)COLOR
:顶点颜色从顶点函数传给片元函数的语义:
SV_POSITION
:裁剪空间中的顶点坐标COLOR0/COLOR1
:用户自定义,可以传递一个float4
TEXCOORD0~TEXCOORD7
:传递纹理坐标(可以是处理后的)从片元函数传给系统的语义:
SV_TARGET
:片元函数返回的颜色Shader "Custom/NormalShader"{
Properties{
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 用结构体 struct ,可以从 Unity 接收更多信息
// 成员需要有语义
// 传给 vert 的信息
struct a2v{
float4 p:POSITION; // 顶点的位置
float3 n:NORMAL; // 顶点的法线
float2 tex:TEXCOORD0; // 顶点的第一套纹理坐标
};
// 从 vert 传给 frag 的信息
struct v2f{
float4 p:SV_POSITION; // 裁剪空间的坐标
float3 color:COLOR0; // 可以用户定义的颜色
};
v2f vert(a2v v){
static v2f f;
f.p=UnityObjectToClipPos(v.p);
f.color=v.n;
return f;
}
fixed4 frag(v2f f):SV_TARGET{
return fixed4(f.color,1); // 显示法线
}
ENDCG
}
}
Fallback "VertexLit"
}
定义LightMode
后,shader 才能获得 Unity 的内置光照变量。
有了光照(平行光)之后就可以写一个Blinn-Phong
模型的纯色 shader 。
例:ForwardBase
,在前向渲染中使用,应用环境光、主方向光、顶点/SH 光源和光照贴图。
Shader "Custom/BlinnPhongShader"{
Properties{
m_kd("Diffuse",Color)=(1,1,1,1) // 物体表面漫反射颜色 (kd)
m_ks("Specular",Color)=(1,1,1,1) // 镜面反射系数 (ks) 默认白色
m_gloss("Gloss",Range(1,1000))=10 // 镜面反射指数
}
SubShader{
Pass{
// 定义 LightMode
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
// Unity 内置变量头文件
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 m_kd;
fixed4 m_ks;
half m_gloss;
struct a2v{
float4 p:POSITION;
float3 n:NORMAL;
float2 uv:TEXCOORD0;
};
struct v2f{
float4 p:SV_POSITION;
float3 worldP:COLOR0; // 传递世界空间下的坐标
float3 worldN:COLOR1; // 传递世界空间下的法线
};
// Unity 给的光源在世界空间,因此需要把模型空间转换到世界空间
v2f vert(a2v v){
static v2f f;
f.p=UnityObjectToClipPos(v.p);
// 把位置从模型空间转换到世界空间
f.worldP=mul(unity_ObjectToWorld,v.p);
// 把法线从模型空间转换到世界空间
f.worldN=normalize(UnityObjectToWorldNormal(v.n));
return f;
}
// 用 Blinn-Phong 模型对像素着色
fixed4 frag(v2f f):SV_TARGET{
// 环境光
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射
float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP)); // 光照方向(着色点到光源)
fixed3 diffuse=_LightColor0.rgb*m_kd.rgb*max(0,dot(lightDir,f.worldN));
// 镜面反射
float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP)); // 观察方向(着色点到相机)
float3 halfDir=normalize(lightDir+viewDir); // 半程向量
fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);
return fixed4(ambient+diffuse+specular,1); // 最终着色
}
ENDCG
}
}
Fallback "VertexLit"
}
纹理缩放和偏移之后,纹理坐标 $(u,v)$ 会超出 $[0,1]$ 范围,根据设置不同有不同处理方式,例如:
Repeat
:循环,坐标对 $1$ “取模”Clamp
:剪切,小于 $0$ 变为 $0$ ,大于 $1$ 变为 $1$还有其他属性,比如插值模式,可以指定不插值,双线性插值,三线性插值。
Shader "Custom/TextureShader"{
Properties{
m_MainTex("Main Tex",2D)="white"{} // 物体表面贴图 (kd)
m_ks("Specular",Color)=(1,1,1,1) // 镜面反射系数 (ks) 默认白色
m_gloss("Gloss",Range(1,1000))=10 // 镜面反射指数
}
SubShader{
Pass{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
sampler2D m_MainTex;
// _ST 后缀是固定的,表示贴图的四个参数
// 前两个值为缩放Tilling,后两个值为偏移Offset
float4 m_MainTex_ST;
fixed4 m_ks;
half m_gloss;
struct a2v{
float4 p:POSITION;
float3 n:NORMAL;
float2 uv:TEXCOORD0;
};
struct v2f{
float4 p:SV_POSITION;
float3 worldP:COLOR0;
float3 worldN:COLOR1;
float2 uv:TEXCOORD0; // 传递纹理坐标
};
v2f vert(a2v v){
static v2f f;
f.p=UnityObjectToClipPos(v.p);
f.worldP=mul(unity_ObjectToWorld,v.p);
f.worldN=normalize(UnityObjectToWorldNormal(v.n));
// 乘上贴图缩放倍数(xy),然后加上偏移(zw)
f.uv=v.uv*m_MainTex_ST.xy+m_MainTex_ST.zw;
return f;
}
fixed4 frag(v2f f):SV_TARGET{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP));
// 使用纹理贴图颜色代替漫反射系数
float3 tex_color=tex2D(m_MainTex,f.uv.xy);
fixed3 diffuse=_LightColor0.rgb*tex_color*max(0,dot(lightDir,f.worldN));
float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP));
float3 halfDir=normalize(lightDir+viewDir);
fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);
return fixed4(ambient+diffuse+specular,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
渲染队列Queue
:Unity 会按照渲染队列的顺序渲染,Queue
越小越先渲染。
用法:Tags { "Queue" = "[QueueName]+[Offset]" }
,QueueName
是预设的一些队列名,Offset
是整数,表示预设的值加上偏移。
QueueName | 值 | 功能 |
---|---|---|
Background | 1000 | 背景渲染队列 |
Geometry | 2000 | 不透明渲染队列(默认) |
AlphaTest | 2450 | 透明度测试渲染队列 |
Transparent | 3000 | 半透明渲染队列 |
Overlay | 4000 | 覆盖效果渲染队列 |
ZTest
:深度测试,即像素上和当前的深度缓存进行比较的方式,满足条件的才进行绘制。
Less
:深度 $<$ 缓存LEqual
(默认):深度 $\le$ 缓存Equal
:深度 $=$ 缓存GEqual
:深度 $\ge$ 缓存Greater
:深度 $>$ 缓存NotEqual
:深度 $\not=$ 缓存Always
:任何情况都绘制ZWrite
:深度写入,即通过深度测试之后,是否把深度写入缓存。通常不透明物体要写入,而半透明物体不写入(因为半透明即使更近也不会遮挡更远的物体,但能被更远的遮挡)。
On
:启用深度写入Off
:不启用深度写入Blend
:混合模式,Blend SrcAlpha OneMinusSrcAlpha
是最普通的透明度混合模式。
Shader "Custom/TransparentShader"{
Properties{
m_kd("Diffuse",Color)=(1,1,1,1)
m_ks("Specular",Color)=(1,1,1,1)
m_gloss("Gloss",Range(1,1000))=10
m_alpha("Alpha",Range(0,1))=0.5
}
SubShader{
Tags{
"Queue"="Transparent" // 将渲染队列改为 Transparent
"IgnoreProjector"="True" // 忽略投影器(投影器不兼容半透明的)
"RenderType"="Transparent" // 给着色器分类为 Transparent(可以在 C# 脚本中选取)
}
Pass{
Tags {"LightMode"="ForwardBase"}
// 对半透明对象禁用深度写入
// 这样半透明对象只会进行深度测试(能被正确遮挡),不进行深度写入(不会挡住其他的)
ZWrite Off
ZTest LEqual
// 一般的 Alpha 混合模式
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 m_kd;
fixed4 m_ks;
half m_gloss;
fixed m_alpha;
struct a2v{
float4 p:POSITION;
float3 n:NORMAL;
float2 uv:TEXCOORD0;
};
struct v2f{
float4 p:SV_POSITION;
float3 worldP:COLOR0;
float3 worldN:COLOR1;
};
v2f vert(a2v v){
static v2f f;
f.p=UnityObjectToClipPos(v.p);
f.worldP=mul(unity_ObjectToWorld,v.p);
f.worldN=normalize(UnityObjectToWorldNormal(v.n));
return f;
}
fixed4 frag(v2f f):SV_TARGET{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldP));
fixed3 diffuse=_LightColor0.rgb*m_kd.rgb*max(0,dot(lightDir,f.worldN));
float3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldP));
float3 halfDir=normalize(lightDir+viewDir);
fixed3 specular=_LightColor0.rgb*m_ks.rgb*pow(max(0,dot(halfDir,f.worldN)),m_gloss);
return fixed4(ambient+diffuse+specular,m_alpha); // 透明度为 m_alpha
}
ENDCG
}
}
Fallback "VertexLit"
}