🌈TAAAAAAAAA
视频教程请看B站UP:
庄懂-BoyanTata
📚主旨
美术向TA基础能力培养,使大家可以用技术手段去表达美术诉求。
📚主要内容
📍.Unity 引擎基础;
📍.ShaderForge连连看;
📍.ShaderLab图形编程;
📍.渲染相关的C#脚本;
📚学习路径
根据某一个🍱技术专题进行🍳技术演示,我们再通过学习模仿🍛完成老师布置的作业。
📚HalfLambert卡通着色
💡实现思路
📍.先实现Lambert(兰伯特)光照模型:法线与光反方向的点乘即夹角cos值(-1~1)
float hLambert = dot(lDirWS,nDirWS) * 0.5 +0.5;
📍.然后把兰伯特
转换(Lambert)成半兰伯特
(HalfLambert):(-1~1) =>(0~1)
📍.最后用半兰伯特组装成一个uv坐标采样贴图就大功告成啦🍭
💡连连看
🍖.加个屏幕坐标UV点阵
💡效果
💡连连看
📚最最最简的shader
Shader "ZYF/Homework/Helloworld"
{
Properties
{
}
SubShader
{
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
struct VertexInput {
float4 vertex : POSITION;
};
struct VertexOutput {
float4 pos : SV_POSITION;
};
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex );
return o;
}
fixed4 frag (VertexOutput i) : COLOR
{
return fixed4(1,0,0,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
📚 Shader技巧
💡面板参数声明格式
类型 | 格式 |
---|---|
数值 | _Name(“标签名”,Float) = 0 |
范围 | _Name(“标签名”,range(0,1)) = 0 |
位置、向量 | _Name(“标签名”,Vector) = (0,0,0,0) |
颜色 | _Name(“标签名”,Color) = (1.0,1.0,1.0,1.0) |
2D纹理 | _Name(“标签名”,2D) = “White”{} |
3D纹理 | _Name(“标签名”,3D) = “White”{} |
2D纹理 | _Name(“标签名”,Cube) = “_Skybox”{} |
💡 参数属性
属性 | 用途 | 范围 | 示例 |
---|---|---|---|
[HideInInspector] | 在面板上隐藏该参数 | 任何参数 | [HideInInspector] _FakeLightDir(“假光方向”,Vector)=(0,0,0,0) |
[NoScaleOffset] | 禁用纹理的TilingOffset面板;不需要做TilingOffset的纹理,比如大部分角色纹理,防止美术误设置 | 纹理参数 | [NoScaleOffset] _MainTex(“主贴图”,2D)=“White”{} |
Normal | 标记该纹理参数为法线贴图,以激活自检测功能 | 2D纹理参数 | [Normal] _NormalTex(“法线贴图”,2D)=“bump”{} |
[HDR] | 用于设置高动态范围颜色值,如:灯光颜色、自发光颜色等 | 颜色参数 | [HDR] _EmitCol(“自发光颜色”,Color) =(1.0,1.0,1.0,1.0,1.0) |
[Gamma] | 用于颜色参数的色彩空间的转换;一般用于色彩空间为Linear的项目 | 颜色参数 | [Gamma] _EmitCol(“自发光颜色”,Color) = (1.0,1.0,1.0,1.0) |
[PowerSlider(2)] | 对范围参数做Power处理后再传入Shader;纠正部分参数调节手感; | 范围参数 | [PowerSlider(2)] _SpecPow(“高光次幂”,Range(1.90))=30 |
Header(xxx标签)] | 标签,用于排版 | 单独使用 | [Header(Texture)] |
Space(10)] | 空行,用于排版 | 单独使用 | [Space(10)] |
其它:[Toggle]、[Enum]、[Keyword] |
💡 参数类型
💡 原则上优先
使用精度最低
的数据类型。
💡 世界空间位置和UV坐标:float
。
💡 向量、HDR颜色使用half
;视情况升到float
。
💡 LDR颜色、简单乘子可使用fixed
。
不同平台对数据类型的支持情况不同,一般会自动转换,极少数情况下自动转换会带来问题。
部分平台上数据类型精度转换消耗也不小,fixed也要慎用。
类型 | 位数 | 范围 | 精度 |
---|---|---|---|
fixed | 11 | –2.0~2.0 | 1/256 |
half | 16 | -60000~60000 | 3位小数 |
float | 32 | -3.4e38~3.4e38 | 6,7位小数 |
Int | 32 | 较少使用 | _ |
bool | 布尔型较少使用 | _ | _ |
矩阵:float2x2,float3x3,float4x4 | _ | _ | _ |
sampler2D | 2D纹理 | _ | _ |
sampler3D | 3D纹理 | _ | _ |
samplerCUBE | Cube纹理 | _ | _ |
💡 可访问的顶点Input数据
标签 | 含义 | 类型 |
---|---|---|
POSITION | 顶点位置 | float3 、float4 |
TEXCOORD0 | UV通道1 | float2、float3、float4 |
TEXCOORD1 | UV通道2 | float2、float3、float4 |
TEXCOORD2 | UV通道3 | float2、float3、float4 |
TEXCOORD3 | UV通道4 | float2、float3、float4 |
NORMAL | 法线方向 | float3 |
TANGENT | 切线方向 | float4 |
COLOR | 顶点色 | float4 |
💡常用的顶点Output数据
标签 | 含义 | 类型 |
---|---|---|
pos | 顶点位置CS (ClipSpace) | float4 |
uv0 | 一般纹理UV | float2 |
uv1 | LightmapUV | float2 |
posWS | 顶点位置WS(WorldSpace) | float3 |
nDirWS | 法线方向WS | half3 |
tDirWS | 切线方向WS | half3 |
bDirWS | 副切线方向WS | half3 |
color | 顶点色 | fixed4 |
📚 光照组成
🥠向量准备
📚法线(nDir)
🔄转世界空间法线
struct VertexInput {
float3 normal :NORMAL;//模型顶点法线
};
struct VertexOutput {
float3 worldSpaceNormal : TEXCOORD0;
};
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.normalDir = UnityObjectToWorldNormal(v.normal);//转世界空间法线
return o;
}
🎞法线贴图采样TBN
💡 利用构造的矩阵TBN
(模型)变换法线贴图法线(切线空间
到世界空间
)。
💡 利用模型自带的法线和切线叉乘
计算副切线(注意方向为:v.tangent.w * unity_WorldTransformParams.w)。
💡 v.tangent:值为-1或者1,由DCC软件中的切线自动生成,和顶点的环绕顺序有关。
💡 unity_WorldTransformParams.w:定义于unityShaderVariables.cginc中.模型的Scale值是三维向量,即xyz,当这三个值中有奇数个值为负时(1个或者3个值全为负时),unity_WorldTransformParams.w = -1,否则为1.
Properties
{
_NormalMap("法线贴图",2D) ="bump"{}
}
uniform sampler2D _NormalMap;
struct VertexInput {
float4 vertex : POSITION;
float2 texcoord :TEXCOORD0;
float3 normal:NORMAL;
float4 tangent :TANGENT;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv :TEXCOORD0;
float3 nDirWS :TEXCOORD1;
float3 bDirWS :TEXCOORD2;
float3 tDirWS:TEXCOORD3;
};
//采样法线贴图
float3 NormalMapSamplerWS(float3 tDirWS,float3 bDirWS,float3 nDirWS,sampler2D normalMap,float2 uv) {
float3 nDirTS = UnpackNormal(tex2D(normalMap,uv));
float3x3 tbn = float3x3(tDirWS,bDirWS,nDirWS);
float3 normalWS = normalize(mul(nDirTS,tbn));
return normalWS;
}
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex );
o.uv = v.texcoord;
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.tDirWS = UnityObjectToWorldDir(v.tangent.xyz);
//副切线方向。v.tangent:值为-1或者1,由DCC软件中的切线自动生成,和顶点的环绕顺序有关。
//unity_WorldTransformParams.w:定义于unityShaderVariables.cginc中.模型的Scale值是三维向量,
//即xyz,当这三个值中有奇数个值为负时(1个或者3个值全为负时),unity_WorldTransformParams.w = -1,否则为1.
fixed sign = v.tangent.w * unity_WorldTransformParams.w;
o.bDirWS =normalize(cross(o.nDirWS,o.tDirWS)*sign);
return o;
}
fixed4 frag (VertexOutput i) : COLOR
{
float3 nDirWS = NormalMapSamplerWS(i.tDirWS,i.bDirWS,i.nDirWS, _NormalMap,i.uv);
...
}
📚光方向 (lDir)
📍._WorldSpaceLightPos0.w
:用来区分平行光🌞和点光 💡
📍._WorldSpaceLightPos0.xyz
:方向(平行光)or 位置(点光)
🚧注意啦 ~光方向是指向光的方向而不是从光源出发的方向👇
📚视线方向 (vDir)
🚧注意啦 ~视线方向是指向摄像机的方向而不是从摄像机出发的方向👇
struct VertexInput {
float4 vertex : POSITION;
...
};
struct VertexOutput {
float4 posCS : SV_POSITION;
float3 posWS : TEXCOORD0;
...
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.posCS = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld,v.vertex);
...
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 vDir = normalize(_WorldSpaceCameraPos.xyz-i.posWS.xyz);
...
}
📚顶点世界坐标 (posWS)
struct VertexInput {
float4 vertex : POSITION;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 posWS : TEXCOORD0;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld,v.vertex);
return o;
}
📚光反射方向 (lrDir)
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
...
};
struct VertexOutput {
float4 posCS : SV_POSITION;
float3 nDirWS :TEXCOORD0;
...
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.posCS = UnityObjectToClipPos(v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
...
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 lDir = _WorldSpaceLightPos0.xyz;
float3 lrDir =normalize(reflect(lDir *-1,i.nDirWS));
...
}
📚半角方向 (hDir)
😱两个向量一加就是半角方向
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 posCS : SV_POSITION;
float3 posWS : TEXCOORD0;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.posCS = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 lDir = _WorldSpaceLightPos0.xyz;
float3 vDir = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
float3 hDir = normalize(lDir + vDir);
...
}
📚 贴图使用TilingOffset
📍. 在对应的贴图的下面声明一个相同名字+“_ST”的变量
:如 uniform float4 _MainTex_ST。
📍.纹理采样时使用 TRANSFORM_TEX
(uv0,_MainTex)
📚贴图采样
Properties
{
_Tex("图图",2D) ="white"{}
}
Pass{
...
uniform sampler2D _Tex;
uniform float4 _Tex_ST;
struct VertexInput {
float4 vertex : POSITION;
float2 texcoord :TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv :TEXCOORD0;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag (VertexOutput i) : COLOR
{
float2 uv = float2(0.5,0.5)
float4 color = tex2D(_Tex,TRANSFORM_TEX(uv,_Tex));//采样贴图颜色
return color;
}
}
📚 漫反射与高光混合
🍤半兰伯特混合基础色(乘法),🥧高光完全反射颜色所以用加法
float3 finalCol = _MainCol * hlambert + blinnPhongPower;
🥗 光源
💡hLambert(漫反射)
fixed4 frag (VertexOutput i) : COLOR
{
float lDirWS = _WorldSpaceLightPos0.xyz;
float hLambert = dot(lDirWS,nDirWS) * 0.5 +0.5;
...
}
💡Phong(高光)
fixed4 frag (VertexOutput i) : COLOR
{
float3 lDirWS = _WorldSpaceLightPos0.xyz;
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
float3 vrDirWS =normalize(reflect(-vDirWS,nDirWS));
float phongPow = 20;
float phong = pow(max(0,dot(vrDirWS, lDirWS)),phongPow);
}
📚 光照投影
💡 LIGHTING_COORDS(1,2)
:这里的参数是需要根据当前已使用的TEXCOORD0
来计算的,如果当前最大的已经使用了TEXCOORD2
那么参数应该是LIGHTING_COORDS(3,4)
。
💡 FallBack "Diffuse"
:在光照投影时FallBack调用一个叫ShadowCaster
的pass(也可以自己写这个pass)。
#include "AutoLight.cginc" //光照投影必备库
#include "Lighting.cginc"//光照投影必备库
struct VertexOutput {
float4 pos : SV_POSITION;
float3 posWS : TEXCOORD0;
LIGHTING_COORDS(1,2)
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float attenuation = LIGHT_ATTENUATION(i);
...
}
ENDCG
}
}
FallBack "Diffuse"
📚 环境光
💡 漫反射:3ColAmbient(3向环境光)
💡 因为环境光方向不确定,为了简化我们把环境光分为三个不同方向:上、下、侧面
Properties
{
_AmbientCol_Top("环境光_Top",Color) = (1.0,1.0,1.0,1.0)
_AmbientCol_Bottom("环境光_Bottom",Color) = (1.0,1.0,1.0,1.0)
_AmbientCol_Side("环境光_Side",Color) = (1.0,1.0,1.0,1.0)
_AmbientScale("环境光强度",Range(0,1)) =0.5
_AmbientOcclusion("环境光遮蔽",2D) = "White"{}
...
}
struct VertexInput {
float4 vertex : POSITION;
float3 normal:NORMAL;
float2 texcoord :TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 normalWS : TEXCOORD0;
float2 uv : TEXCOORD1;
};
uniform float3 _AmbientCol_Top;
uniform float3 _AmbientCol_Bottom;
uniform float3 _AmbientCol_Side;
uniform float _AmbientScale;
uniform sampler2D _AmbientOcclusion;
uniform float4 _AmbientOcclusion_ST;
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.normalWS = UnityObjectToWorldNormal(v.normal);
o.uv = v.texcoord;
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 normal = i.normalWS;
//Ambient
float mask_Top = max(0, normal.g);
float mask_Bottom = max(0, -normal.g);
float mask_Side = max(0, 1 - mask_Top - mask_Bottom);
float3 ambientCol_Top = _AmbientCol_Top.xyz * mask_Top;
float3 ambientCol_Bottom = _AmbientCol_Bottom.xyz * mask_Bottom;
float3 ambientCol_Side = _AmbientCol_Side.xyz * mask_Side;
float3 ao = tex2D(_AmbientOcclusion,TRANSFORM_TEX(uv, _AmbientOcclusion)).xyz;
float3 ambientCol = (ambientCol_Top + ambientCol_Bottom + ambientCol_Side).xyz * _AmbientScale * ao.xyz;
...
}
💡镜面反射:Fresnel(菲涅尔)
📢 如果你站在湖边,低头看脚下
的水,你会发现水是透明
的,反射不是特别强烈;如果你看远处
的湖面,你会发现水并不是透明
的,但反射非常强烈。这就是菲涅尔效应
。视线垂直
于表面时,反射较弱
,而当视线非垂直表面时,夹角越小,反射越明显
。如果你看向一个圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。
📢 在真实世界中,除了金属之外
,其它物质均有
不同程度的“菲涅尔效应”。
Properties
{
_FresnelPow("菲涅尔pow",Float)=1
}
uniform float _FresnelPow;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 nDirWS :TEXCOORD0;
float3 posWS :TEXCOORD1;
};
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex );
o.posWS = mul(unity_ObjectToWorld,v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (VertexOutput i) : COLOR
{
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
//夹角越大菲涅尔越强,所以这里要“1-”
float fresnel =pow(1-dot(vDirWS,i.nDirWS),_FresnelPow);
return fresnel;
}
💡 Matcap(材质捕捉)
📢 将BRDF渲染结果烘焙到贴图上,用View空间法线rg
分量构成uv采样。能很真实地表现出各类材质信息。它不考虑光源信息,也就没有实际的光照计算。
📢 由于使用的法线向量是经过归一化的,因此r r + gg =1 即圆方程,所以采样的范围始终为一个圆。
📢 制作Matcap贴图(就是用Blender渲染一个球)。
Properties
{
_MatcapTex("material capture",2D) = "white"{}
}
uniform sampler2D _MatcapTex;
struct VertexInput {
float4 vertex : POSITION;
float2 texcoord :TEXCOORD0;
float3 normal:NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 nDirWS :TEXCOORD0;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 nDirWS = i.nDirWS;
//世界=》视角空间
float3 nDirVS = mul(UNITY_MATRIX_V,nDirWS).rgb;
//提取rg通道并重新映射作为uv采样matcap贴图(-1,1) =>(0,1),matcap即material capture 材质捕捉
float2 uvVS = nDirVS.rg * 0.5 + 0.5;
float3 finalCol = tex2D(_MatcapTex,uvVS).rgb;
return fixed4(finalCol,1);
}
💡 Cubemap
下图配置是用于材质上的一个低分辨率的Cubemap
📢 CubeMap通常被用来作为具有反射属性物体的反射源。
Properties
{
_Cubemap("Cubemap",Cube) = "_Skybox"{}
_CubemapMip("CubeMap模糊程度",Range(0,7)) = 0.0
}
uniform float _CubemapMip;
uniform samplerCUBE _Cubemap;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 posWS : TEXCOORD0;
float3 nDirWS : TEXCOORD1;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld,v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(VertexOutput i) : COLOR
{
float3 vDirWS = normalize(i.posWS.xyz - _WorldSpaceCameraPos.xyz);
float3 vrDirWS= reflect(vDirWS,i.nDirWS);
float3 final = texCUBElod(_Cubemap, float4(vrDirWS, _CubemapMip)).rgb;
return fixed4(final,1);
}
🥓混合模式
AB(Alpha Blend)、AC(Alpha Cutoff)、AD(Alpha Add) 是混合模式中常用的几种。
🥞混合原理
💡 Src * SrcFactor op Dst*DstFactor
🥙 Alpha Cutoff
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Cutoff ("透切阈值", range(0.0, 1.0)) = 0.5
}
SubShader {
Tags {
"RenderType"="TransparentCutout" // 对应改为Cutout
"ForceNoShadowCasting"="True" // 关闭阴影投射
"IgnoreProjector"="True" // 不响应投射器
}
}
Pass{
// 输入参数
uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
uniform half _Cutoff;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // UV信息 支持TilingOffset
return o;
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
half4 var_MainTex = tex2D(_MainTex, i.uv); // 采样贴图 RGB颜色 A透贴
clip(var_MainTex.a - _Cutoff); // 透明剪切
return var_MainTex; // 返回值
}
}
🌮 Alpha Blend
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
}
SubShader {
Tags {
"Queue"="Transparent" // 调整渲染顺序
"RenderType"="Transparent" // 对应改为Cutout
"ForceNoShadowCasting"="True" // 关闭阴影投射
"IgnoreProjector"="True" // 不响应投射器
}
Pass {
Blend One OneMinusSrcAlpha // 修改混合方式One/SrcAlpha OneMinusSrcAlpha
// 输入参数
uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
uniform half _Opacity;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // UV信息 支持TilingOffset
return o;
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
half4 var_MainTex = tex2D(_MainTex, i.uv); // 采样贴图 RGB颜色 A透贴
half3 finalRGB = var_MainTex.rgb;
half opacity = var_MainTex.a * _Opacity;
return half4(finalRGB * opacity, opacity); // 返回值
}
}
Alpha Add
与Alpha Blend
差不多,只需要改混合模式为:Blend One One
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
}
SubShader {
Tags {
"Queue"="Transparent" // 调整渲染顺序
"RenderType"="Transparent" // 对应改为Cutout
"ForceNoShadowCasting"="True" // 关闭阴影投射
"IgnoreProjector"="True" // 不响应投射器
}
Pass {
Blend One One // 修改混合方式
// 输入参数
uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
uniform half _Opacity;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // UV信息 支持TilingOffset
return o;
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
half4 var_MainTex = tex2D(_MainTex, i.uv); // 采样贴图 RGB颜色 A透贴
half3 finalRGB = var_MainTex.rgb;
half opacity = var_MainTex.a * _Opacity;
return half4(finalRGB * opacity, opacity); // 返回值
}
}
📚 屏幕UV
- 计算顶点在ViewSpace位置,其xy轴即对应屏幕UV坐标;
- 除以屏幕深度校正畸变;
- 计算模型原点在ViewSpace位置,取Z轴深度即距离;
- 乘以screenUV 以固定屏幕纹理Tiling尺寸;
- 启用_ScreenTex 的ST功能,乘以ScreenTex_ST.xy以支持缩放;
- 加强Offset功能为flow动画,参照15课内容;
struct VertexInput{
float4 vertex :POSITION;//顶点位置 OS
float2 uv : TEXCOORD0; //uv信息
};
struct VertexOutput{
float4 pos : SV_POSITION; //顶点位置 CS
float2 uv : TEXCOORD0; //UV信息
float2 screenUV : TEXCOORD1; //屏幕uv
};
VertexOutput vert(VertexInput v){
VertexOutput o = (VertexOutput) o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
float3 posVS =UnityObjectToViewPos(v.vertex).xyz; //顶点位置 OS > VS
float originDist = UnityObjectToViewPos(float3(0.0,0.0,0.0)).z;//原点位置 OS > VS
o.screenUV = posVS.xy / posVS.z; //VS空间畸变校正
o.screenUV *= originDist; //纹理大小按距离锁定
o.screenUV = o.screenUV * _ScreenTex_ST.xy - frac(_Time.x * _ScreenTex_ST.zw);//启用屏幕纹理ST
return o;
}
half4 frag(VertexOutput i):COLOR{
half4 var_MainTex = tex2D(_MainTex,i.uv);//采样 基本纹理 RGB颜色 A透贴
half var_ScreenTex = tex2D(_screenTex,i.screenUV).r; //采样 屏幕纹理
//FinalRGB 不透明度
half3 finalRGB = var_MainTex.rgb;
half opacity = var_MainTex.a * _Opacity * var_ScreenTex;
return half4(finalRGB * opacity,opacity);
}
📚 GrabPass获取背景纹理
方便、消耗大。其它方式获取背景纹理:1.CommandBuffer:前Srp时代管线自定义方法。2.LWRP/URP:后Srp时代管线自定义方法(推荐)
SubShader{
GrabPass{
"_BGTex"
}
Pass{
uniform sampler2D _BGTex;//取到背景纹理
struct VertexInput{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
};
struct VertexOutput{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
float4 grabPos:TEXCOORD1;
};
VertexOutput vert(VertexInput v){
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv= v.uv;
o.grabPos= ComputeGrabScreenPos(o.pos);
return o;
}
half4 frag(VertexInput i):COLOR{
half3 var_BGTex =tex2Dproj(_BGTex,i.grabPos).rgb;
...
}
}
}
📚 走位走位
💡 透贴阴影问题
计算投影必须要有一个ShadowCastPass(有的FallBack:Diffuse 包含了ShadowCastPass),所以可以FallBack到一个支持AlphaCutout的Shader上比如:Legacy Shaders/Transparent/Cutout/VertexLit
,需要注意必须包含Shader中透明相关的所有面板属性!!!
💡 透贴双面问题
关闭剔除背面,在声明BlendMode的地方加入Cull Off
即可;
💡绕Y轴旋转
Properties
{
_RotateSpeed("RotateSpeed",Float) = 1
_Rotation("InitAngle", Range( 0 , 360)) = 0
_RotateCenterPos("RotateAxisPos", Vector) = (0,0,0,0)
}
uniform float _RotateSpeed;
uniform float _Rotation;
uniform float3 _RotateCenterPos;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 posWS : TEXCOORD0;
float3 nDirWS : TEXCOORD1;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld,v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
//posWS:顶点世界坐标,rotateCenterPosWS:旋转轴世界坐标,一般设置成模型在场景中的世界坐标
//返回的是经过旋转的顶点世界坐标
float3 RotateAroundYInDegrees (float3 posWS,float3 rotateCenterPosWS, float degrees)
{
posWS = posWS - rotateCenterPosWS;
float alpha = degrees * UNITY_PI / 180.0;
float sina, cosa;
sincos(alpha / 2, sina, cosa);
float3x3 m = float3x3(cosa, 0, sina, 0, 1, 0, -sina, 0, cosa);
float3 r = float3(mul(m, posWS.xyz) ).rgb;
return r;
}
fixed4 frag(VertexOutput i) : COLOR
{
float degree =_Rotation + _RotateSpeed * _Time.y ;
//旋转坐标
float3 posWS= RotateAroundYInDegrees(i.posWS,_RotateCenterPos,degree);
...
}
💡Cginc
封装库
#ifndef zyf_cginc
#define zyf_cginc
float3 calculate(){
return float3(0,0,0);
}
#endif
🥡使用库复用函数
#include "库相对shader文件路径"
例:#include "../xxx/zyf.cginc"
📝待待待待待待待待待待待待待待待待待待待续🚩
🍗大作业
第11课
帖子看我👈
💡 金属度会让漫反射的金属部分变暗。
💡 镜面反射颜色