1.代码基础
1.代码结构
Shader "Custom/Test" {
Properties{
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
SubShader{
...
}
FallBack "Diffuse"
}
2.语义
Unity Shader 中使用的语义(Semantics)定义了Shader变量如何与渲染管线中的其他部分相联系。以下是Unity Shader中常见的一些语义:
-
POSITION - 用于顶点着色器的输出,表示顶点的位置信息。
-
NORMAL - 表示顶点的法线方向。
-
TEXCOORDn - 用于纹理坐标,
n
可以是0, 1, 2, … 等数字,表示不同的纹理坐标集。 -
COLOR - 用于顶点颜色或者材质的颜色。
-
SV_Position(System-Value Position)- 在HLSL中用于顶点着色器的输出和像素着色器的输入,表示裁剪空间中的顶点位置。
-
SV_Target - 在像素着色器的输出中,表示最终渲染到屏幕或者渲染目标的颜色。
-
SV_InstanceID - 在顶点着色器中,用于获取当前实例的ID,常用于实例渲染。
-
SV_VertexID - 在顶点着色器中,用于获取当前顶点的ID。
-
TANGENT - 表示顶点的切线方向,通常用于法线贴图。
-
BITANGENT - 表示副切线方向,同样用于法线贴图。
-
BLENDWEIGHT - 用于顶点的骨骼权重,常用于蒙皮动画。
-
BLENDINDICES - 用于顶点的骨骼索引,同样用于蒙皮动画。
-
SV_Depth - 在像素着色器的输出中,表示像素的深度值。
-
SV_ClipDistance - 用于指定裁剪平面,可以用来进行裁剪操作。
这些语义帮助Unity的渲染管线理解Shader变量代表的数据类型和用途,从而正确地将这些变量用于渲染流程中。在编写Shader代码时,正确使用语义是至关重要的。
Unity中,输入输出变量并不需要有特殊的含义,变量本身存储了什么,shader流水线并不关心,但是有些语义有着特殊的含义规定,例如a2f中 TEXCOORD0即把模型的第一组纹理存储在该变量中。但在v2f中,其修饰的变量含义可以有我们自己来定。
SV_XXX代表系统数值。
例子:
struct a2v{//a2v代表应用到顶点
float4 vertex : POSITION;//用模型空间的顶点来填充vertex变量
float3 normal : NORMAL;//用模型空间的发现来填充normal变量
float4 texcoord : TEXCOORD0;//用模型的第一套纹理填充texcoord变量
}
Unity会根据这些语义来填充结构体,对于顶点着色器来说,Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORDN,COLOR等
struct v2f{
float4 pos : SV_POSITION;//告诉Unity,pos里包含了顶点在 裁剪空间中的位置信息
fixed4 color : COLOR0;//可以用于存储颜色信息
}
3.常用函数及定义
2.光照
UnityObjectToClipPos(float4 vertex):模型坐标转化到裁剪坐标下
UNITY_LIGHTMODEL_AMBIENT:环境关照
UnityObjectToWorldNormal(float3 normal):法线从模型空间下转化到世界空间下
=========================================================================
_WorladSpaceLightPos0:“是 Unity 游戏引擎中的一个内置着色器变量,用于在渲染过程中处理光照。这个变量表示世界空间中的光源位置或方向向量。具体来说:
- 如果 “_WorldSpaceLightPos0.w” 的值为 0,这表示光源是平行光,此时 “_WorldSpaceLightPos0.xyz” 代表的是平行光的方向向量。
- 如果 “_WorldSpaceLightPos0.w” 的值不为 0,这通常表示光源是点光源或聚光灯,此时 “_WorldSpaceLightPos0.xyz” 代表的是光源在世界空间中的位置。
--------------------------------------------------------------------------------------------------------------------------------
-
_WorldSpaceLightPos0:
- 它是一个内置变量,而不是一个函数。
- 它存储的是光源在世界空间中的位置(对于点光源和聚光灯)或方向(对于平行光)。
- 当处理平行光时,
_WorldSpaceLightPos0.w
被设置为 0,并且_WorldSpaceLightPos0.xyz
存储的是光线的方向。 - 对于点光源和聚光灯,
_WorldSpaceLightPos0.w
被设置为 1,并且_WorldSpaceLightPos0.xyz
存储的是光源的位置。
-
WorldSpaceLightDir:
- 这是一个函数,通常用于在顶点着色器或片元着色器中计算从顶点到光源的方向。
- 它需要一个世界空间中的顶点位置作为参数,并返回从该顶点到光源的方向向量。
- 这个函数通常用于计算逐顶点的光照,特别是在需要考虑顶点到光源的方向时。
=========================================================================
_LightColor0:第一个平行光找的颜色
reflect(float3 lightDir,float3 normal):返回反射光线,注意这里的反射光线是从光源到顶点,如果是从_WorldSpaceLightPos0获得的话要取反
Shader "Unlit/Chapter5_DiffuseLight"
{
Properties{
_Diffuse("DiffuseColor",Color)= (1,1,1,1)
_Specular("SpecularColor",Color)=(1,1,1,1)
_Gloss("Gloss",Range(8,256)) = 20
}
SubShader{
Pass{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//DIFFUSE
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLight));
//SPECULAR
fixed3 reflectDir = reflect(-worldLight,worldNormal);
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_WorldToObject,v.vertex).xyz);
fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
o.color = diffuse + ambient + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target{
return fixed4(i.color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
3.纹理
1.单一纹理:
TRANSFORM_TEX(v.texcoord, _MainTex):进行纹理变换,等价于v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw
Shader "Unlit/Single_Texture"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8,256)) = 20
}
SubShader
{
Pass{
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 vieDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + vieDir);
fixed3 specular = _LightColor0.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
Fallback "Diffuse"
}
2.法线纹理:
模型法线存储优点:直观,实现简单,纹理坐标边界处更加平滑。
切线空间存储优点:自由度高,可以复用在其他模型上面,可以实现UV动画,可压缩(仅需存储xy方向,因为z轴都为正方向,可由xy方向向量叉乘推导得到z方向)。
TANGENT_SPACE_ROTATION:获得从模型到切线空间下的旋转矩阵
tex2D():对图像进行采样
UnpackNormal():Unity Shader中用于处理法线纹理的一个函数。这个函数主要作用是对法线纹理的采样结果进行反压缩操作。
流程:将光照向量和视线方向转化成切线空间坐标下=》法线采样和映射=》控制凹凸度=》添加光照计算(用切线下法线计算)
Shader "Unlit/TangentSpaceMat"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpMapScale("Bump Scale", Float) = 1.0
_Specular("Specular", Color) = (0.5, 0.5, 0.5, 1)
_Gloss("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpMapScale;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;
float3 lightDir:TEXCOORD1;
float3 viewDir:TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord.xy, _BumpMap);
//float3 normal = normalize(v.normal);
//float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
//float3x3 rotation = float3x3(v.tangent.xyz, binormal, normal);
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed4 packNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
tangentNormal = UnpackNormal(packNormal);
tangentNormal.xy *= _BumpMapScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
为了理解为什么将切线空间中的向量 bump
与矩阵相乘可以得到其在世界空间中的坐标,我们需要回顾一下线性代数中的坐标变换原理。
坐标变换的基本概念
当一个向量从一个坐标系变换到另一个坐标系时,我们使用一个变换矩阵来执行这个操作。这个变换矩阵是由源坐标系的基向量在新坐标系中的表示组成的。
在3D图形学中,切线空间到世界空间的变换涉及以下步骤:
-
定义切线空间基向量:在切线空间中,我们有三个互相垂直的基向量,分别是切线(T),副切线(B),和法线(N)。
-
表示基向量在世界空间中:这些基向量在世界空间中的表示组成了变换矩阵的列。即:
i.TtoW0
是切线(T)在世界空间中的表示。i.TtoW1
是副切线(B)在世界空间中的表示。i.TtoW2
是法线(N)在世界空间中的表示。
变换矩阵的构成
变换矩阵是由上述基向量的世界空间表示作为列向量组成的,如下所示:
[ i.TtoW0.x i.TtoW1.x i.TtoW2.x ]
[ i.TtoW0.y i.TtoW1.y i.TtoW2.y ]
[ i.TtoW0.z i.TtoW1.z i.TtoW2.z ]
这个矩阵表示了从切线空间到世界空间的变换。
矩阵乘法的意义
当我们有一个切线空间中的向量 bump
,它表示为:
[ bump.x ]
[ bump.y ]
[ bump.z ]
将其与变换矩阵相乘,我们实际上是在计算 bump
在世界空间基向量上的投影。具体来说,矩阵乘法的每一行对应于变换后的向量的一个分量,计算如下:
bump_world_space.x = bump.x * i.TtoW0.x + bump.y * i.TtoW1.x + bump.z * i.TtoW2.x
bump_world_space.y = bump.x * i.TtoW0.y + bump.y * i.TtoW1.y + bump.z * i.TtoW2.y
bump_world_space.z = bump.x * i.TtoW0.z + bump.y * i.TtoW1.z + bump.z * i.TtoW2.z
这个计算过程完成了以下操作:
- 将
bump
向量投影到世界空间的X轴上,即i.TtoW0
。 - 将
bump
向量投影到世界空间的Y轴上,即i.TtoW1
。 - 将
bump
向量投影到世界空间的Z轴上,即i.TtoW2
。
通过这三个投影的和,我们得到了 bump
向量在世界空间中的表示,也就是在三个轴上的投影。
Shader "Unlit/WorldTangentMat"
{
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
// Compute the matrix that transform directions from tangent space to world space
// Put the world position in w component for optimization
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get the position in world space
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
// Compute the light and view dir in world space
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
// Transform the narmal from tangent space to world space
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
3.渐变纹理
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
将乘积从[-1,1]映射到[0,1],然后纹理颜色由深到浅映射,即法线与光照方向夹角越小越亮。
Shader "Unity Shaders Book/Chapter 7/Ramp Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RampTex ("Ramp Tex", 2D) = "white" {}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Use the texture to sample the diffuse color
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
4.遮罩纹理
遮罩纹理(Mask Texture),在计算机图形学和游戏开发中,是一种用于控制渲染效果的纹理。它通常是一个灰度图像,其中每个像素的亮度值(通常在0到1之间)代表了一种特定属性的程度或者是否启用该属性。遮罩纹理可以用于多种目的,以下是一些常见的应用:
-
透明度遮罩:控制物体的透明度,其中白色表示完全不透明,黑色表示完全透明,灰度值表示不同程度的半透明。
-
细节遮罩:用于在着色器中添加细节,如凹凸贴图(bump mapping)或法线贴图(normal mapping),通过遮罩纹理可以控制细节的强度和范围。
-
光滑度遮罩:在PBR(基于物理的渲染)工作流中,遮罩纹理可以用来控制表面的光滑程度,影响光照和反射。
-
散射遮罩:在皮肤渲染中,遮罩纹理可以用来控制皮肤散射的效果,比如在脸颊或鼻尖等区域增强散射。
-
颜色遮罩:用于调整或混合不同区域的颜色,可以用来做颜色变化或者添加污渍、磨损等效果。
-
环境遮罩(Ambient Occlusion):用于模拟物体几何体之间或者物体自身不同部分之间的遮挡效果,增强阴影和深度感。
Shader "Unity Shaders Book/Chapter 7/Mask Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale("Bump Scale", Float) = 1.0
_SpecularMask ("Specular Mask", 2D) = "white" {}
_SpecularScale ("Specular Scale", Float) = 1.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
该处用遮罩主要为了使某些部分反光效果更好,某些部分反光效果差。
4.透明效果
1.透明度测试:
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
IgnoreProjector:在Unity中,IgnoreProjector
是一个属性,可以应用于材质或Shader,用于控制该材质或Shader是否应该被投影器(Projector)影响。当一个物体使用了带有IgnoreProjector
属性的Shader,并且这个属性被设置为true
时,任何投影器对该物体的投影都将被忽略。这意味着即使投影器在场景中对该物体所在的区域进行了投影,该物体的外观也不会因为投影器的灯光或纹理而发生变化。
clip():判断内部函数,如果>0则进行下面的程序,反之则舍弃该片元输出。
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
// Alpha test
clip (texColor.a - _Cutoff);
// Equal to
// if ((texColor.a - _Cutoff) < 0.0) {
// discard;
// }
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
2.透明度混合
Blend Off: 关闭混合。
Blend SrcFactor DstFactor: 开启混合,设置混合因子。该片元中产生的颜色会乘以SrcFactor,颜色缓冲区中的颜色会乘以DstFactor,然后两者相加。
Blend SrcFactor DstFactor,SrcFactorA SrcFactorB:类上,只是使用不同因子来混合透明通道。
BlendOp BlendOperation:使用BlendOperation进行操作。
Blend SrcAlpha OneMinusSrcAlpha:原颜色(片元颜色)的透明度设为第一个因子,1-srcalpha设为第二个英子
ZWrite Off:关闭深度写入
Shader "Unity Shaders Book/Chapter 8/Alpha Blend" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
3.开启深度写入半透明效果
两个pass,第一个开启仅开启深度写入,第二个开启透明度测试,从而解决透明物体重叠的排序问题。
ColorMask RGB | A | 0 等,用于控制哪些通道会被写入,RGB即只有RGB通道被写入
ColorMask 0:意味着该Pass不写入任何颜色通道
Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
// Extra pass that renders to depth buffer only
Pass {
ZWrite On
ColorMask 0
}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
开启深度写入
未开启深度写入
4.ShaderLab混合命令
ShaderLab中的混合因子
-
Zero:结果颜色不会包含源颜色或目标颜色的任何部分。
-
One:结果颜色将包含源颜色或目标颜色的全部。
-
SrcColor:结果颜色是源颜色乘以源颜色的RGB值。
-
SrcAlpha:结果颜色是源颜色乘以源颜色的alpha值。
-
DstColor:结果颜色是源颜色乘以目标颜色的RGB值。
-
DstAlpha:结果颜色是源颜色乘以目标颜色的alpha值。
-
OneMinusSrcColor:结果颜色是源颜色乘以(1 - 源颜色的RGB值)。
-
OneMinusSrcAlpha:结果颜色是源颜色乘以(1 - 源颜色的alpha值),这是最常用的混合因子之一,用于实现半透明效果。
-
OneMinusDstColor:结果颜色是源颜色乘以(1 - 目标颜色的RGB值)。
-
OneMinusDstAlpha:结果颜色是源颜色乘以(1 - 目标颜色的alpha值)。
ShaderLab中的混合操作
(O:OUTPUT 输出颜色; S :SOURCE 片元颜色; D:DESTINATION深度缓冲中的颜色)
Add:O_RGE = S_RGB+D_RGB O_ALPHA = S_ALPHA + D_ALPHA
Sub:O_RGE = S_RGB-D_RGB O_ALPHA = S_ALPHA - D_ALPHA
RevSub:O_RGE = -S_RGB+D_RGB O_ALPHA = -S_ALPHA + D_ALPHA
Min:O_ RGBA = (MIN(S_R,D_R),MIN(S_G,D_G),MIN(S_B,D_B),MIN(S_A,D_A))
Max:O_ RGBA = (MAX(S_R,D_R),MAX(S_G,D_G),MAX(S_B,D_B),MAX(S_A,D_A))
ShaderLab中常见的混合类型
-
通 过 混 合 操 作 和 混 合 因 子 命 令 的 组 合, 我 们 可 以 得 到 一 些 类 似 Photoshop 混 合 模 式 中 的 混 合 效 果:
-
// 正 常( Normal), 即 透 明 度 混 合
-
Blend SrcAlpha OneMinusSrcAlpha
-
-
// 柔 和 相 加( Soft Additive)
-
Blend OneMinusDstColor One
-
-
// 正 片 叠 底( Multiply), 即 相 乘
-
Blend DstColor Zero
-
-
// 两 倍 相 乘( 2x Multiply)
-
Blend DstColor SrcColor
-
-
// 变 暗( Darken)
-
BlendOp Min
-
Blend One One
-
-
// 变 亮( Lighten)
-
BlendOp Max
-
Blend One One
-
-
// 滤 色( Screen)
-
Blend OneMinusDstColor One
-
// 等 同 于
-
Blend One OneMinusSrcColor
-
-
-
// 线 性 减 淡( Linear Dodge)
-
Blend One One
-
-
5.双面渲染透明效果
Cull Back | Front | Off : 剔除背面 | 剔除前面 | 无剔除 渲染。
a.关闭剔除
Shader "Unity Shaders Book/Chapter 8/Alpha Test With Both Side" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
// Turn off culling
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
clip (texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
无背面剔除
有背面剔除
6.透明度混合的双面渲染
两个Pass.第一个只渲染背面,第二个Pass只渲染正面。
Shader "Unity Shaders Book/Chapter 8/Alpha Blend With Both Side" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
// First pass renders only back faces
Cull Front
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardBase" }
// Second pass renders only front faces
Cull Back
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
5.复杂光照
1.渲染路径
Unity中的渲染路径定义了Unity如何渲染场景中的每个物体。以下是Unity中主要的渲染路径:
-
前向渲染路径(Forward Rendering):
- 在这个路径中,每个灯光对每个对象的影响都是实时计算的,但是对灯光的数量有限制。对于每个对象,只有有限数量的灯光会被计算(通常是4个),超出这个数量的灯光将以简化方式处理。
- 适合场景中灯光数量不多的情况。
-
延迟渲染路径(Deferred Rendering):
- 延迟渲染首先将物体的几何信息、材质属性等信息渲染到一个称为G-Buffer的缓冲区中,然后再在一个单独的渲染阶段计算灯光。
- 支持更多的灯光而不会显著降低性能,但是它需要支持特定图形硬件(需要支持多个渲染目标(MRT))。
- 对于复杂的光照效果和大量灯光的场景比较合适。
-
顶点照明渲染路径(Vertex Lit Rendering):
- 这是一个较老的渲染路径,它主要使用顶点着色器来计算光照,不适用于像素光照。
- 由于它不计算像素级别的光照,所以渲染速度快,但是光照效果较为简单。
- 这个路径在现代的Unity开发中已经很少使用。
-
自定义渲染路径:
- Unity允许开发者通过编写Shader和修改渲染设置来创建自定义的渲染路径。
- 这可以用于实现一些特殊的效果或者优化,但是需要较高的技术能力。
-
轻量级渲染路径( Lightweight Render Pipeline, LWRP):
- 是Unity推出的一个较新的渲染管线,设计用于移动设备和VR,专注于性能和轻量级。
- 它提供了一种更灵活、更高效的渲染方式。
-
高清渲染路径(High Definition Render Pipeline, HDRP):
- 针对高端平台,如PC和主机,提供高质量的视觉效果。
- 它支持多种高级渲染功能,如物理照明、反射探针、环境遮挡等。
选择哪种渲染路径取决于项目的需求、目标平台以及性能预算。随着Unity版本的更新,这些渲染路径可能会有所改进或者新增。开发者应根据具体情况进行选择和优化。
2.LightMode标签支持的渲染路径设置选项
Always:该pass总会被渲染,但不计算任何光照
ForwardBase:用于前向渲染。该pass会计算环境光、平行光、逐顶点/SH光源和LightMaps
ForwardAdd:用于前向渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源
Deferred:用于延迟渲染。该pass会渲染G-buffer
ShadowCaster:把物体的深度信息渲染到阴影映射纹理或一张深度纹理中
PrePassBase:用于遗留的延迟渲染。该pass会渲染法线和高光反射的指数部分。
PrePassFinal:用于遗留的延迟渲染。该pass会通过合并纹理、光照和自发光来渲染得到最后的颜色。
Vertex\VertexLMRGBM\VertexLM:用于遗留的顶点照明渲染。
3.前向渲染路径:
原理:对通过深度测试的片元进行光照计算,这算一个渲染流程。并且对于每个逐像素的光源都要进行一次该渲染流程。即一个物体需要执行多个Pass,然后在帧缓冲区合并得到最后的颜色。由于受光源数目影响大,所有通常渲染引擎会限制每个物体的逐像素光照数目。
光照处理方式:逐顶点、逐像素、球谐函数(SH),光源处理方式取决于光源类型(Type)和渲染模式(render mode(比如是否是Important))。
4.顶点照明渲染路径
顶点照明渲染对硬件要求最少、运算新能最高,效果最差的一种类型。不支持例如法线映射、阴影等逐像素才能得到的效果。
顶点渲染路径可以使用的内置变量:
5.延迟渲染路径
原理:共两个Pass。在 第 一 个 Pass 中, 我 们 不 进 行 任 何 光 照 计 算, 而 是 仅 仅 计 算 哪 些 片 元 是 可 见 的, 这 主 要 是 通 过 深 度 缓 冲 技 术 来 实 现, 当 发 现 一 个 片 元 是 可 见 的, 我 们 就 把 它 的 相 关 信 息 存 储 到 G 缓 冲 区 中。然 后, 在 第 二 个 Pass 中, 我 们 利 用 G 缓 冲 区 的 各 个 片 元 信 息, 例 如 表 面 法 线、 视 角 方 向、 漫 反 射 系 数 等, 进 行 真 正 的 光 照 计 算。
6.前向渲染中处理不同光源
前向渲染有两种,一种是Bass Pass,一种是Additional Pass,Bass Pass一般计算环境光、自发光(所有逐像素的平行光和所有逐顶点和SH光源)。Additional Pass则是根据光源类型被多次调用(其他影响盖屋体的逐像素光源)
#pragma multi_compile_fwdbase:
是 Unity Shader 中的一个编译指令,用于在 前向渲染路径(Forward Rendering) 中生成多个变体(Variants),以支持不同的光照和渲染配置。它的作用是确保 Shader 能够正确处理前向渲染路径中的不同光照情况。
在前向渲染路径中,Unity 会根据场景中的光源数量和类型,对物体进行多次渲染:
-
Base Pass:渲染主方向光(通常是平行光)和逐顶点光照(Vertex Lit)。
-
Additional Passes:渲染其他光源(如点光源、聚光灯等),每个光源会单独渲染一次。
为了支持这些不同的渲染情况,Shader 需要生成多个变体(Variants),以处理不同的光照配置。
如果没有 #pragma multi_compile_fwdbase
,Shader 可能无法正确处理以下情况:
-
场景中没有主方向光。
-
物体使用了光照贴图。
-
物体需要接收阴影。
-
场景中有多个光源。
通过使用 #pragma multi_compile_fwdbase
,Unity 会自动生成这些变体,确保 Shader 能够适应不同的渲染情况。
光源属性:位置、方向、颜色、强度、衰减
Base Pass中处理的逐像素光源类型一定是平行光,所以:
_WorldSpaceLightPos0:获得平行光的方向;
_LightColor0:获得平行光的强度和颜色;
衰减值为1.0(没有衰减)
shader变体:在shader中会定义多个宏,根据不同宏来编译生成多种组合形式的shader源码,每一种组合就叫做shader变体。
Bass Pass:
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
为其他逐像素光源定义Additional Pass:
Blend One One:希望每次计算得到的光照结果会叠加在前面的结果上,而不是覆盖掉。
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
7.Unity阴影
Shadow Map:将摄像机放在光源方向,摄像机看不到的地方就是阴影部分。阴影映射纹理本质上也是一张深度图。在Unity中,调用Light Mode为ShadowCaster这个pass来更新光源的阴影映射纹理。
屏幕空间阴影映射技术:
屏幕空间阴影映射技术(Screen Space Shadow Mapping,简称SSSM)是一种在计算机图形学中用于渲染阴影效果的技术。这种技术主要是在屏幕空间(即最终渲染到屏幕上的像素空间)中计算和渲染阴影,而不是在世界空间或光源空间中。以下是屏幕空间阴影映射技术的基本原理和步骤:
原理:
屏幕空间阴影映射技术通常利用深度纹理(Depth Texture)来在屏幕空间中计算阴影。深度纹理包含了场景中每个像素的深度信息,这些信息可以用来判断一个像素是否在阴影中。
步骤:
-
渲染深度纹理:
- 首先,从光源的视角渲染场景,只存储每个像素的深度信息到深度纹理中。
-
屏幕空间阴影计算:
- 在像素着色器中,对于屏幕上的每个像素,计算它到光源的向量。
- 使用这个向量在深度纹理上进行采样,得到从光源视角看到的最近深度值。
- 将这个深度值与当前像素的实际深度值进行比较。如果当前像素的深度值大于采样得到的深度值,那么这个像素就在阴影中。
FallBack “VerterLit”:将深度信息写入阴影映射纹理中。
#include AutoLight.cgnic:包含用于计算阴影的宏
SHADOW_COORDS(2):声明一个用于对阴影纹理采样的坐标。这个宏的参数需要是下一个可用插值寄存器的索引值。‘
TRANSFER_SHADOW(o):用于在顶点着色器中计算上一步中声明的阴影纹理坐标。
SHADOW_ATTENUATION(i):对相关纹理进行采样获得相关信息。
主要注意第一个Pass
Shader "Unity Shaders Book/Chapter 9/Shadow" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
// Need these files to get built-in macros
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
fixed shadow = SHADOW_ATTENUATION(i);
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 position : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
8.统一管理光照衰减和阴影
UNITY_LIGHT_ATTENUATION:用来计算光照衰减和阴影。
注意两个Pass的片元部分。
Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
// Need these files to get built-in macros
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
9.透明度物体的阴影
FallBack “Transparent/Cutout/VertexLit”
Shader "Unity Shaders Book/Chapter 9/Alpha Test With Shadow" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Off
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
SHADOW_COORDS(3)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
clip (texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + diffuse * atten, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}