实际上如果我们完整经历过之前的学习,可以说shader着色器对于大家来说完全就算入门了。后面的着色器效果实现,可以自行研究,或看书或看源码等学习。
之后呢我会持续更新一些常用、炫酷的shader实现,从简单到复杂,从单一到复合,从我已经写过的到未来见识到的(基本上更新到不搞这一行了,上百篇应该是有的),这里来一篇最最最常用的shader。
——网格描边
开发们在unity中随便创建一个cube,在Scene面板就看得到网格描边的效果,pick到这个cube后就显示出来,真的是相当实用,如图:
这里说一下这个实现原理,我们可以将这种效果分开为:
1.中间的正常渲染的立方体
2.中间立方体网格顶点沿着法向量方向扩展x个单位后渲染一个橘黄色的立方体(也就是描边)
实现代码如下:
Shader "Unlit/OutlineUnlitShader"
{
Properties
{
_CubeColor("color",color) = (1,1,1,1)
_EdgeColor("edge",color) = (1,1,1,1)
_EdgeThick("thick",Range(0.01,1)) = 0.1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
//正常渲染一个立方体的pass
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
//渲染cube的颜色
float4 _CubeColor;
v2f vert (appdata v)
{
v2f o;
//只进行最简单的MVP变换
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//返回单色调
return _CubeColor;
}
ENDCG
}
//渲染描边的pass
Pass
{
//这里剔除正面渲染,是因为会遮挡掉cube渲染,所以剔除掉
Cull front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 normal : NORMAL; /*使用NORMAL语义绑定顶点法向量*/
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 normal : TEXCOORD1;
};
float4 _EdgeColor; /*描边的颜色*/
float _EdgeThick; /*描边的粗细*/
v2f vert (appdata v)
{
v2f o;
//对顶点坐标进行法向量方向偏移
v.vertex += v.normal * _EdgeThick;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//返回描边颜色
return _EdgeColor;
}
ENDCG
}
}
}
效果图如下:
给予了一个红色的外描边。
但是实际会发现一个问题,如下的gif:
一眼就看出问题所在了,因为我们只将顶点沿着法向量方向平移,所以在平移距离过大的情况下,给人的视觉感受就是“面片分离”了,同时也能看出,unity自带的cube并非是8个顶点12个三角面,而是24个顶点12个三角面,共用的顶点全部都是分离开的,这也和引擎理解共用顶点和法向量相关,同时也涉及到渲染优化的问题,如下图:
回到正题,为了解决上面那种“面片分离”的情况,我们给每个模型顶点一个“虚拟法向量”,法向量方向为模型中心点到顶点的朝向向量,示意图如下:
接着我们实现我们的“虚拟法向量”描边代码,如下:
//渲染描边的pass
Pass
{
//这里剔除正面渲染,是因为会遮挡掉cube渲染,所以剔除掉
Cull front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 normal : NORMAL; /*使用NORMAL语义绑定顶点法向量*/
};
struct v2f
{
float4 vertex : SV_POSITION;
};
vector _CubeCenter; /*cube的世界空间中心*/
float4 _EdgeColor; /*描边的颜色*/
float _EdgeThick; /*描边的粗细*/
v2f vert (appdata v)
{
v2f o;
//起算这个奇异的法向量
//首先将世界空间center变换到建模空间,然后计算“虚拟”法向量
float4 cube_model_center = mul(unity_WorldToObject,_CubeCenter);
float3 strange_normal = normalize(v.vertex.xyz - cube_model_center.xyz);
//计算法向量偏移量
float4 normal_offset = float4(stangne_normal * _EdgeThick,0);
v.vertex += normal_offset;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//返回描边颜色
return _EdgeColor;
}
ENDCG
}
这里我只贴上描边的pass渲染,最后的效果图如下:
是不是正常了,美中不足的就是模型的世界空间中心点需要使用脚本实时传入。
想起来还可以使用缩放矩阵进行扩展,所以继续来实现,pass渲染代码如下:
Pass {
Cull front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float4 normal : NORMAL;
};
struct v2f {
float4 vertex : SV_POSITION;
};
vector _CubeCenter;
float4 _EdgeColor;
float _EdgeScale; /*缩放系数*/
v2f vert (appdata v) {
v2f o;
//构建缩放矩阵
float4x4 _matscale = float4x4(_EdgeScale, 0, 0, 0,
0, _EdgeScale, 0, 0,
0, 0, _EdgeScale, 0,
0, 0, 0, 1);
float4 vs = mul(_matscale, v.vertex);
o.vertex = UnityObjectToClipPos(vs);
return o;
}
fixed4 frag (v2f i) : SV_Target {
return _EdgeColor;
}
ENDCG
}
效果图如下:
顺便说一下,还有一种方法可以得到相同的效果,后面再来实现。
so,我闲着无聊就继续更新着色Shader。