【UnityShader】 三渲二渲染学习笔记
跟B站上的一个大大学的【虚幻&Unity】两种引擎 原神风格基础卡通渲染 完整流程,现趁忘记之前赶紧记下所有细节()
完成画面↓
【Unity/Shader】三渲二纳西妲
文章目录
- 参数
- 环境颜色 Ambient Color
- 漫反射颜色 Diffuse Color
- 阴影颜色 Shadow Color
- 基础贴图 Base Tex
- 基础贴图影响因子 Base Tex Fac
- 卡通贴图 Toon Tex
- 卡通贴图影响因子 Toon Tex Fac
- 球形纹理 Sphere Tex
- 球形纹理影响因子 Sphere Tex Fac
- 球形纹理乘或加 Sphere Mul/Add
- 双面渲染 Double Sided
- 透明度 Alpha
- 金属贴图 Metal Tex
- 高光分布 Spec Exponent
- 非金属反射 Non-Metallic
- 金属反射 Metallic
- 法线贴图 Normal Map
- ID图 ILM
- 阴影分割图和对应行 Ramp Tex
- 描边粗度和对应颜色
- 实现部分
首先是重中之重,Shader的参数部分
参数
可控参数如下
环境颜色 Ambient Color
用于调整整体色彩倾向
漫反射颜色 Diffuse Color
用于调整亮面色彩倾向
阴影颜色 Shadow Color
用于调整暗面色彩倾向
基础贴图 Base Tex
用于最基础的漫反射贴图
基础贴图影响因子 Base Tex Fac
用于调整漫反射贴图的贡献(影响大小)
卡通贴图 Toon Tex
用于将法向朝向地面的面的亮度压低,提高立体感
卡通贴图影响因子 Toon Tex Fac
调整卡通贴图贡献
球形纹理 Sphere Tex
提供球形纹理的反射颜色
球形纹理影响因子 Sphere Tex Fac
调整球形纹理贡献
球形纹理乘或加 Sphere Mul/Add
更改球形纹理混合方式,通常Add会使影响更加明显
双面渲染 Double Sided
控制是否进行双面渲染
透明度 Alpha
控制是否透明
金属贴图 Metal Tex
控制确认金属部分
高光分布 Spec Exponent
控制高光的散步范围(越小越集中,类似镜面反射)
非金属反射 Non-Metallic
控制非金属反射强度
金属反射 Metallic
控制金属反射强度
法线贴图 Normal Map
表面细节
ID图 ILM
区分各种部分
阴影分割图和对应行 Ramp Tex
提高光线的层次感
描边粗度和对应颜色
进行描边
实现部分
ShadowCaster
该Pass负责Unity中的阴影投射和阴影接收,以下是其默认实现
Pass
{
Name "ShadowCaster"
Tags{"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
ColorMask 0
Cull Off
HLSLPROGRAM
#pragma exclude_renderers gles gles3 glcore
#pragma target 4.5
// -------------------------------------
// Material Keywords
#pragma shader_feature_local_fragment _ALPHATEST_ON
#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
// -------------------------------------
// Universal Pipeline keywords
// This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias
#pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
ENDHLSL
}
DepthNormals
这个pass通常用于需要获取摄像机、屏幕深度的场景,比如rim轮廓光
// This pass is used when drawing to a _CameraNormalsTexture texture
Pass
{
Name "DepthNormals"
Tags{"LightMode" = "DepthNormals"}
ZWrite On
Cull Off
HLSLPROGRAM
#pragma exclude_renderers gles gles3 glcore
#pragma target 4.5
#pragma vertex DepthNormalsVertex
#pragma fragment DepthNormalsFragment
// -------------------------------------
// Material Keywords
#pragma shader_feature_local _NORMALMAP
#pragma shader_feature_local _PARALLAXMAP
#pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED
#pragma shader_feature_local_fragment _ALPHATEST_ON
#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitDepthNormalsPass.hlsl"
ENDHLSL
}
DrawObject
该Pass负责进行模型的绘制
数据结构部分
首先是带入顶点着色器的appdata
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal : NORMAL;
half4 tangent : TANGENT;
half4 color : COLOR0;
};
POSITION代表着顶点的位置信息
TEXCOORD0代表着第一套UV信息,Shader中支持多个UV信息,如漫反射UV,光照贴图UV,动态光照UV等。该参数在内置管线限制为4个,在URP中没有限制,具体视平台限制而定
NORMAL即法线信息
TANGENT是顶点的切线信息,Normal垂直于表面,切线切过表面,二者互相垂直
COLOR0即顶点颜色
接着是片元着色器所需要的数据结构v2f
struct v2f
{
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 positionVS : TEXCOORD2;
float4 positionCS : SV_POSITION;
float4 positionNDC : TEXCOORD3;
float3 normalWS : TEXCOORD4;
float3 tangentWS : TEXCOORD5;
float3 bitangentWS : TEXCOORD6;
float fogCoord : TEXCOORD7;
float4 shadowCoord : TEXCOORD8;
};
uv负责采样大部分贴图
世界,模型,摄像机,标准屏幕坐标系四个空间下的片元位置
世界空间下的法线,切线,副切线的片元所在面朝向向量
fogCoord和shadowCoord是两个雾效
顶点着色器
v2f vert(appdata v)
{
v2f o;
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
o.uv = TRANSFORM_TEX(v.uv, _BaseTex);
o.positionWS = vertexInput.positionWS;
o.positionVS = vertexInput.positionVS;
o.positionCS = vertexInput.positionCS;
o.positionNDC = vertexInput.positionNDC;
VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(v.normal, v.tangent);
o.tangentWS = vertexNormalInput.tangentWS;
o.bitangentWS = vertexNormalInput.bitangentWS;
o.normalWS = vertexNormalInput.normalWS;
o.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
o.shadowCoord = TransformWorldToShadowCoord(vertexInput.positionWS);
return o;
}
GetVertexPositionInputs函数,通过输入顶点坐标,获得对应的顶点信息,所有顶点位置都由其获得
GetVertexNormalInputs函数,通过输入法向和切线,返回对应的顶点的法线相关信息
后面通过对应函数计算fogCoord和shadowCoord,提交返回构造好的v2f
片元着色器
定义函数参数
float4 frag(v2f i, bool isFacing : SV_IsFrontFace) : SV_Target
返回一个rgba值表示片元颜色,接收收到的v2f,接收isFacing判断正反面
SV_Target用于多目标渲染,在后面加上序号(从0开始,不加也是0),可一次渲染到多个渲染目标
获取基础信息
先获取光照信息,使用如下函数
Light light = GetMainLight(i.shadowCoord);
接着,采样法线贴图,并将其转换为切线空间
float4 normalMap = tex2D(_NormalMap, i.uv);
float3 normalTS = float3(normalMap.ag * 2 - 1, 0);
normalTS.z = sqrt(1 - dot(normalTS.xy, normalTS.xy));
-
normalMap.ag:这表示从法线贴图中采样出的红色(a通道)和绿色(g通道)分量。
-
normalMap.ag * 2 - 1:这一步将采样到的a和g通道值进行缩放和偏移,将其映射到[-1, 1]的范围内。这是因为法线贴图的a和g通道通常存储在[0, 1]范围内,而法线向量在计算中常常需要在[-1, 1]范围内表示。
-
float3(…, 0):将得到的a和g通道值作为X和Y分量,构成一个三维向量,Z分量设置为0。这样构造的normalTS向量表示了法线向量在切线空间(Tangent Space)的局部方向。
-
normalTS.xy :这是normalTS向量的X和Y分量,即法线在切线空间中的水平和垂直分量。
-
**dot(normalTS.xy, normalTS.xy) **:这是法线在切线空间中水平和垂直分量的长度的平方。
-
**1 - dot(normalTS.xy, normalTS.xy) **:这是用来计算法线在切线空间中Z分量的平方。因为法线向量在切线空间中是单位向量,所以其X、Y、Z分量的平方和应该等于1。
-
**sqrt(…) :**对上述值进行平方根运算,得到Z分量的实际值。
然后求出基本数据和对应点乘
//N 世界空间法线向量 V 摄像机向量 L 光线向量 H 半角向量
float3 N = normalize(mul(normalTS, float3x3(i.tangentWS, i.bitangentWS, i.normalWS)));
float3 V = normalize(mul((float3x3)UNITY_MATRIX_I_V, i.positionVS * (-1)));
float3 L = normalize(light.direction);
float3 H = normalize(V + L);
float NoL = dot(N, L);
float NoH = dot(N, H);
float NoV = dot(N, V);
float3 normalVS = normalize(mul((float3x3)UNITY_MATRIX_V, N));
float2 matcapUV = normalVS.xy * 0.5 + 0.5;
N:一个3x3的浮点数矩阵,用来将世界空间中的切线、副切线和法线向量组合起来。这种方式通常被用来构建一个从世界空间到切线空间(Tangent Space)的转换矩阵。将其与切线normalTS相乘,最终可得到世界空间下的法线。
V:这是将逆视图矩阵的旋转-缩放部分应用于顶点位置的处理。通过这个乘法,我们得到了一个在世界空间中相对于相机视角反向的方向向量。
L:将光线方向规格化。
H:半角向量,计算了光源方向向量L和视角方向向量V的平均方向,即半向量H,并确保它是一个单位向量。这在光照模型中是常见的步骤,用于确定表面的高光反射方向。
NoL:表示光线与表面的夹角关系,越大表明光线与表面越垂直,越小表示越平行,如果直接点乘,则结果为负时,照在表面正方向,为正则照在表面负方向。在光照模型中,NoL用来计算漫反射光的强度,即光线在表面上的投影对光照强度的影响。
NoH:半向量H代表了光源和视角的平均方向,对于镜面反射效果尤为重要。NoH的值告诉我们光线和视角之间的夹角余弦值,这在光照模型中用来计算高光反射的强度。较大的NoH值表示更强的镜面高光效果。
NoV:NoV告诉我们观察者视角下表面的朝向情况,其值越大表明表面法线越朝向观察者,越小则越背向。在光照模型中,NoV通常用来模拟视角相关的效果,如环境反射或透视效果的强度。
normalVS:
- UNITY_MATRIX_V:这是Unity中的一个预定义变量,表示视图矩阵。视图矩阵用来将物体从世界空间转换到相机空间或观察空间。
- N:法线向量,通常在顶点着色器中计算并传递给片元着色器。
- (float3x3)UNITY_MATRIX_V:将UNITY_MATRIX_V强制转换为float3x3类型的矩阵,用于乘法操作。
- mul((float3x3)UNITY_MATRIX_V, N):这行代码将法线向量N从物体空间(或世界空间)变换到相机空间。因为UNITY_MATRIX_V是视图矩阵,它的作用是将物体坐标变换到相机坐标系下。
- **normalize(…):**对变换后的法线向量进行归一化,确保其长度为1,保持单位长度的方向向量。
经过视图矩阵变换并归一化后的法线向量,在视图空间中表示的表面法线方向。
matcapUV:
- normalVS.xy:这是视图空间中的法线向量的xy分量,即水平和垂直分量。
- 0.5:这个数值用来将法线向量的范围缩小到0到1之间。
- + 0.5:这个操作将归一化后的法线向量范围从[-1, 1]映射到[0, 1]之间。
是法线向量在Matcap贴图中的UV坐标。Matcap贴图通常用来实现表面上的高光效果,其UV坐标的计算基于表面法线向量在视图空间中的投影。